##// END OF EJS Templates
fixes #762, LDAP and container created users are now activated based on...
marcink -
r3470:649ca0cc default
parent child Browse files
Show More
@@ -1,195 +1,191 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.login
3 rhodecode.controllers.login
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Login controller for rhodeocode
6 Login controller for rhodeocode
7
7
8 :created_on: Apr 22, 2010
8 :created_on: Apr 22, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import formencode
27 import formencode
28 import datetime
28 import datetime
29 import urlparse
29 import urlparse
30
30
31 from formencode import htmlfill
31 from formencode import htmlfill
32 from webob.exc import HTTPFound
32 from webob.exc import HTTPFound
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons import request, response, session, tmpl_context as c, url
35 from pylons import request, response, session, tmpl_context as c, url
36
36
37 import rhodecode.lib.helpers as h
37 import rhodecode.lib.helpers as h
38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
39 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.base import BaseController, render
40 from rhodecode.model.db import User
40 from rhodecode.model.db import User
41 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
41 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43 from rhodecode.model.meta import Session
43 from rhodecode.model.meta import Session
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class LoginController(BaseController):
49 class LoginController(BaseController):
50
50
51 def __before__(self):
51 def __before__(self):
52 super(LoginController, self).__before__()
52 super(LoginController, self).__before__()
53
53
54 def index(self):
54 def index(self):
55 # redirect if already logged in
55 # redirect if already logged in
56 c.came_from = request.GET.get('came_from')
56 c.came_from = request.GET.get('came_from')
57 not_default = self.rhodecode_user.username != 'default'
57 not_default = self.rhodecode_user.username != 'default'
58 ip_allowed = self.rhodecode_user.ip_allowed
58 ip_allowed = self.rhodecode_user.ip_allowed
59 if self.rhodecode_user.is_authenticated and not_default and ip_allowed:
59 if self.rhodecode_user.is_authenticated and not_default and ip_allowed:
60 return redirect(url('home'))
60 return redirect(url('home'))
61
61
62 if request.POST:
62 if request.POST:
63 # import Login Form validator class
63 # import Login Form validator class
64 login_form = LoginForm()
64 login_form = LoginForm()
65 try:
65 try:
66 session.invalidate()
66 session.invalidate()
67 c.form_result = login_form.to_python(dict(request.POST))
67 c.form_result = login_form.to_python(dict(request.POST))
68 # form checks for username/password, now we're authenticated
68 # form checks for username/password, now we're authenticated
69 username = c.form_result['username']
69 username = c.form_result['username']
70 user = User.get_by_username(username, case_insensitive=True)
70 user = User.get_by_username(username, case_insensitive=True)
71 auth_user = AuthUser(user.user_id)
71 auth_user = AuthUser(user.user_id)
72 auth_user.set_authenticated()
72 auth_user.set_authenticated()
73 cs = auth_user.get_cookie_store()
73 cs = auth_user.get_cookie_store()
74 session['rhodecode_user'] = cs
74 session['rhodecode_user'] = cs
75 user.update_lastlogin()
75 user.update_lastlogin()
76 Session().commit()
76 Session().commit()
77
77
78 # If they want to be remembered, update the cookie
78 # If they want to be remembered, update the cookie
79 if c.form_result['remember'] is not False:
79 if c.form_result['remember'] is not False:
80 _year = (datetime.datetime.now() +
80 _year = (datetime.datetime.now() +
81 datetime.timedelta(seconds=60 * 60 * 24 * 365))
81 datetime.timedelta(seconds=60 * 60 * 24 * 365))
82 session._set_cookie_expires(_year)
82 session._set_cookie_expires(_year)
83
83
84 session.save()
84 session.save()
85
85
86 log.info('user %s is now authenticated and stored in '
86 log.info('user %s is now authenticated and stored in '
87 'session, session attrs %s' % (username, cs))
87 'session, session attrs %s' % (username, cs))
88
88
89 # dumps session attrs back to cookie
89 # dumps session attrs back to cookie
90 session._update_cookie_out()
90 session._update_cookie_out()
91
91
92 # we set new cookie
92 # we set new cookie
93 headers = None
93 headers = None
94 if session.request['set_cookie']:
94 if session.request['set_cookie']:
95 # send set-cookie headers back to response to update cookie
95 # send set-cookie headers back to response to update cookie
96 headers = [('Set-Cookie', session.request['cookie_out'])]
96 headers = [('Set-Cookie', session.request['cookie_out'])]
97
97
98 allowed_schemes = ['http', 'https']
98 allowed_schemes = ['http', 'https']
99 if c.came_from:
99 if c.came_from:
100 parsed = urlparse.urlparse(c.came_from)
100 parsed = urlparse.urlparse(c.came_from)
101 server_parsed = urlparse.urlparse(url.current())
101 server_parsed = urlparse.urlparse(url.current())
102 if parsed.scheme and parsed.scheme not in allowed_schemes:
102 if parsed.scheme and parsed.scheme not in allowed_schemes:
103 log.error(
103 log.error(
104 'Suspicious URL scheme detected %s for url %s' %
104 'Suspicious URL scheme detected %s for url %s' %
105 (parsed.scheme, parsed))
105 (parsed.scheme, parsed))
106 c.came_from = url('home')
106 c.came_from = url('home')
107 elif server_parsed.netloc != parsed.netloc:
107 elif server_parsed.netloc != parsed.netloc:
108 log.error('Suspicious NETLOC detected %s for url %s'
108 log.error('Suspicious NETLOC detected %s for url %s'
109 'server url is: %s' %
109 'server url is: %s' %
110 (parsed.netloc, parsed, server_parsed))
110 (parsed.netloc, parsed, server_parsed))
111 c.came_from = url('home')
111 c.came_from = url('home')
112 raise HTTPFound(location=c.came_from, headers=headers)
112 raise HTTPFound(location=c.came_from, headers=headers)
113 else:
113 else:
114 raise HTTPFound(location=url('home'), headers=headers)
114 raise HTTPFound(location=url('home'), headers=headers)
115
115
116 except formencode.Invalid, errors:
116 except formencode.Invalid, errors:
117 return htmlfill.render(
117 return htmlfill.render(
118 render('/login.html'),
118 render('/login.html'),
119 defaults=errors.value,
119 defaults=errors.value,
120 errors=errors.error_dict or {},
120 errors=errors.error_dict or {},
121 prefix_error=False,
121 prefix_error=False,
122 encoding="UTF-8")
122 encoding="UTF-8")
123
123
124 return render('/login.html')
124 return render('/login.html')
125
125
126 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
126 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
127 'hg.register.manual_activate')
127 'hg.register.manual_activate')
128 def register(self):
128 def register(self):
129 c.auto_active = False
129 c.auto_active = 'hg.register.auto_activate' in User.get_by_username('default')\
130 for perm in User.get_by_username('default').user_perms:
130 .AuthUser.permissions['global']
131 if perm.permission.permission_name == 'hg.register.auto_activate':
132 c.auto_active = True
133 break
134
131
135 if request.POST:
132 if request.POST:
136
137 register_form = RegisterForm()()
133 register_form = RegisterForm()()
138 try:
134 try:
139 form_result = register_form.to_python(dict(request.POST))
135 form_result = register_form.to_python(dict(request.POST))
140 form_result['active'] = c.auto_active
136 form_result['active'] = c.auto_active
141 UserModel().create_registration(form_result)
137 UserModel().create_registration(form_result)
142 h.flash(_('You have successfully registered into rhodecode'),
138 h.flash(_('You have successfully registered into RhodeCode'),
143 category='success')
139 category='success')
144 Session().commit()
140 Session().commit()
145 return redirect(url('login_home'))
141 return redirect(url('login_home'))
146
142
147 except formencode.Invalid, errors:
143 except formencode.Invalid, errors:
148 return htmlfill.render(
144 return htmlfill.render(
149 render('/register.html'),
145 render('/register.html'),
150 defaults=errors.value,
146 defaults=errors.value,
151 errors=errors.error_dict or {},
147 errors=errors.error_dict or {},
152 prefix_error=False,
148 prefix_error=False,
153 encoding="UTF-8")
149 encoding="UTF-8")
154
150
155 return render('/register.html')
151 return render('/register.html')
156
152
157 def password_reset(self):
153 def password_reset(self):
158 if request.POST:
154 if request.POST:
159 password_reset_form = PasswordResetForm()()
155 password_reset_form = PasswordResetForm()()
160 try:
156 try:
161 form_result = password_reset_form.to_python(dict(request.POST))
157 form_result = password_reset_form.to_python(dict(request.POST))
162 UserModel().reset_password_link(form_result)
158 UserModel().reset_password_link(form_result)
163 h.flash(_('Your password reset link was sent'),
159 h.flash(_('Your password reset link was sent'),
164 category='success')
160 category='success')
165 return redirect(url('login_home'))
161 return redirect(url('login_home'))
166
162
167 except formencode.Invalid, errors:
163 except formencode.Invalid, errors:
168 return htmlfill.render(
164 return htmlfill.render(
169 render('/password_reset.html'),
165 render('/password_reset.html'),
170 defaults=errors.value,
166 defaults=errors.value,
171 errors=errors.error_dict or {},
167 errors=errors.error_dict or {},
172 prefix_error=False,
168 prefix_error=False,
173 encoding="UTF-8")
169 encoding="UTF-8")
174
170
175 return render('/password_reset.html')
171 return render('/password_reset.html')
176
172
177 def password_reset_confirmation(self):
173 def password_reset_confirmation(self):
178 if request.GET and request.GET.get('key'):
174 if request.GET and request.GET.get('key'):
179 try:
175 try:
180 user = User.get_by_api_key(request.GET.get('key'))
176 user = User.get_by_api_key(request.GET.get('key'))
181 data = dict(email=user.email)
177 data = dict(email=user.email)
182 UserModel().reset_password(data)
178 UserModel().reset_password(data)
183 h.flash(_('Your password reset was successful, '
179 h.flash(_('Your password reset was successful, '
184 'new password has been sent to your email'),
180 'new password has been sent to your email'),
185 category='success')
181 category='success')
186 except Exception, e:
182 except Exception, e:
187 log.error(e)
183 log.error(e)
188 return redirect(url('reset_password'))
184 return redirect(url('reset_password'))
189
185
190 return redirect(url('login_home'))
186 return redirect(url('login_home'))
191
187
192 def logout(self):
188 def logout(self):
193 session.delete()
189 session.delete()
194 log.info('Logging out and deleting session for user')
190 log.info('Logging out and deleting session for user')
195 redirect(url('home'))
191 redirect(url('home'))
@@ -1,1008 +1,1012 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.auth
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import random
26 import random
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import hashlib
29 import hashlib
30
30
31 from tempfile import _RandomNameSequence
31 from tempfile import _RandomNameSequence
32 from decorator import decorator
32 from decorator import decorator
33
33
34 from pylons import config, url, request
34 from pylons import config, url, request
35 from pylons.controllers.util import abort, redirect
35 from pylons.controllers.util import abort, redirect
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37 from sqlalchemy.orm.exc import ObjectDeletedError
37 from sqlalchemy.orm.exc import ObjectDeletedError
38
38
39 from rhodecode import __platform__, is_windows, is_unix
39 from rhodecode import __platform__, is_windows, is_unix
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41
41
42 from rhodecode.lib.utils2 import str2bool, safe_unicode
42 from rhodecode.lib.utils2 import str2bool, safe_unicode
43 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
43 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
44 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
44 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
45 from rhodecode.lib.auth_ldap import AuthLdap
45 from rhodecode.lib.auth_ldap import AuthLdap
46
46
47 from rhodecode.model import meta
47 from rhodecode.model import meta
48 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
49 from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap
49 from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap
50 from rhodecode.lib.caching_query import FromCache
50 from rhodecode.lib.caching_query import FromCache
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class PasswordGenerator(object):
55 class PasswordGenerator(object):
56 """
56 """
57 This is a simple class for generating password from different sets of
57 This is a simple class for generating password from different sets of
58 characters
58 characters
59 usage::
59 usage::
60
60
61 passwd_gen = PasswordGenerator()
61 passwd_gen = PasswordGenerator()
62 #print 8-letter password containing only big and small letters
62 #print 8-letter password containing only big and small letters
63 of alphabet
63 of alphabet
64 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
65 """
65 """
66 ALPHABETS_NUM = r'''1234567890'''
66 ALPHABETS_NUM = r'''1234567890'''
67 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
67 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
68 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
68 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
69 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
69 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
70 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
70 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
71 + ALPHABETS_NUM + ALPHABETS_SPECIAL
71 + ALPHABETS_NUM + ALPHABETS_SPECIAL
72 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
72 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
73 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
74 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
74 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
75 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
75 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
76
76
77 def __init__(self, passwd=''):
77 def __init__(self, passwd=''):
78 self.passwd = passwd
78 self.passwd = passwd
79
79
80 def gen_password(self, length, type_=None):
80 def gen_password(self, length, type_=None):
81 if type_ is None:
81 if type_ is None:
82 type_ = self.ALPHABETS_FULL
82 type_ = self.ALPHABETS_FULL
83 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
83 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
84 return self.passwd
84 return self.passwd
85
85
86
86
87 class RhodeCodeCrypto(object):
87 class RhodeCodeCrypto(object):
88
88
89 @classmethod
89 @classmethod
90 def hash_string(cls, str_):
90 def hash_string(cls, str_):
91 """
91 """
92 Cryptographic function used for password hashing based on pybcrypt
92 Cryptographic function used for password hashing based on pybcrypt
93 or pycrypto in windows
93 or pycrypto in windows
94
94
95 :param password: password to hash
95 :param password: password to hash
96 """
96 """
97 if is_windows:
97 if is_windows:
98 from hashlib import sha256
98 from hashlib import sha256
99 return sha256(str_).hexdigest()
99 return sha256(str_).hexdigest()
100 elif is_unix:
100 elif is_unix:
101 import bcrypt
101 import bcrypt
102 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
102 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
103 else:
103 else:
104 raise Exception('Unknown or unsupported platform %s' \
104 raise Exception('Unknown or unsupported platform %s' \
105 % __platform__)
105 % __platform__)
106
106
107 @classmethod
107 @classmethod
108 def hash_check(cls, password, hashed):
108 def hash_check(cls, password, hashed):
109 """
109 """
110 Checks matching password with it's hashed value, runs different
110 Checks matching password with it's hashed value, runs different
111 implementation based on platform it runs on
111 implementation based on platform it runs on
112
112
113 :param password: password
113 :param password: password
114 :param hashed: password in hashed form
114 :param hashed: password in hashed form
115 """
115 """
116
116
117 if is_windows:
117 if is_windows:
118 from hashlib import sha256
118 from hashlib import sha256
119 return sha256(password).hexdigest() == hashed
119 return sha256(password).hexdigest() == hashed
120 elif is_unix:
120 elif is_unix:
121 import bcrypt
121 import bcrypt
122 return bcrypt.hashpw(password, hashed) == hashed
122 return bcrypt.hashpw(password, hashed) == hashed
123 else:
123 else:
124 raise Exception('Unknown or unsupported platform %s' \
124 raise Exception('Unknown or unsupported platform %s' \
125 % __platform__)
125 % __platform__)
126
126
127
127
128 def get_crypt_password(password):
128 def get_crypt_password(password):
129 return RhodeCodeCrypto.hash_string(password)
129 return RhodeCodeCrypto.hash_string(password)
130
130
131
131
132 def check_password(password, hashed):
132 def check_password(password, hashed):
133 return RhodeCodeCrypto.hash_check(password, hashed)
133 return RhodeCodeCrypto.hash_check(password, hashed)
134
134
135
135
136 def generate_api_key(str_, salt=None):
136 def generate_api_key(str_, salt=None):
137 """
137 """
138 Generates API KEY from given string
138 Generates API KEY from given string
139
139
140 :param str_:
140 :param str_:
141 :param salt:
141 :param salt:
142 """
142 """
143
143
144 if salt is None:
144 if salt is None:
145 salt = _RandomNameSequence().next()
145 salt = _RandomNameSequence().next()
146
146
147 return hashlib.sha1(str_ + salt).hexdigest()
147 return hashlib.sha1(str_ + salt).hexdigest()
148
148
149
149
150 def authfunc(environ, username, password):
150 def authfunc(environ, username, password):
151 """
151 """
152 Dummy authentication wrapper function used in Mercurial and Git for
152 Dummy authentication wrapper function used in Mercurial and Git for
153 access control.
153 access control.
154
154
155 :param environ: needed only for using in Basic auth
155 :param environ: needed only for using in Basic auth
156 """
156 """
157 return authenticate(username, password)
157 return authenticate(username, password)
158
158
159
159
160 def authenticate(username, password):
160 def authenticate(username, password):
161 """
161 """
162 Authentication function used for access control,
162 Authentication function used for access control,
163 firstly checks for db authentication then if ldap is enabled for ldap
163 firstly checks for db authentication then if ldap is enabled for ldap
164 authentication, also creates ldap user if not in database
164 authentication, also creates ldap user if not in database
165
165
166 :param username: username
166 :param username: username
167 :param password: password
167 :param password: password
168 """
168 """
169
169
170 user_model = UserModel()
170 user_model = UserModel()
171 user = User.get_by_username(username)
171 user = User.get_by_username(username)
172
172
173 log.debug('Authenticating user using RhodeCode account')
173 log.debug('Authenticating user using RhodeCode account')
174 if user is not None and not user.ldap_dn:
174 if user is not None and not user.ldap_dn:
175 if user.active:
175 if user.active:
176 if user.username == 'default' and user.active:
176 if user.username == 'default' and user.active:
177 log.info('user %s authenticated correctly as anonymous user' %
177 log.info('user %s authenticated correctly as anonymous user' %
178 username)
178 username)
179 return True
179 return True
180
180
181 elif user.username == username and check_password(password,
181 elif user.username == username and check_password(password,
182 user.password):
182 user.password):
183 log.info('user %s authenticated correctly' % username)
183 log.info('user %s authenticated correctly' % username)
184 return True
184 return True
185 else:
185 else:
186 log.warning('user %s tried auth but is disabled' % username)
186 log.warning('user %s tried auth but is disabled' % username)
187
187
188 else:
188 else:
189 log.debug('Regular authentication failed')
189 log.debug('Regular authentication failed')
190 user_obj = User.get_by_username(username, case_insensitive=True)
190 user_obj = User.get_by_username(username, case_insensitive=True)
191
191
192 if user_obj is not None and not user_obj.ldap_dn:
192 if user_obj is not None and not user_obj.ldap_dn:
193 log.debug('this user already exists as non ldap')
193 log.debug('this user already exists as non ldap')
194 return False
194 return False
195
195
196 ldap_settings = RhodeCodeSetting.get_ldap_settings()
196 ldap_settings = RhodeCodeSetting.get_ldap_settings()
197 #======================================================================
197 #======================================================================
198 # FALLBACK TO LDAP AUTH IF ENABLE
198 # FALLBACK TO LDAP AUTH IF ENABLE
199 #======================================================================
199 #======================================================================
200 if str2bool(ldap_settings.get('ldap_active')):
200 if str2bool(ldap_settings.get('ldap_active')):
201 log.debug("Authenticating user using ldap")
201 log.debug("Authenticating user using ldap")
202 kwargs = {
202 kwargs = {
203 'server': ldap_settings.get('ldap_host', ''),
203 'server': ldap_settings.get('ldap_host', ''),
204 'base_dn': ldap_settings.get('ldap_base_dn', ''),
204 'base_dn': ldap_settings.get('ldap_base_dn', ''),
205 'port': ldap_settings.get('ldap_port'),
205 'port': ldap_settings.get('ldap_port'),
206 'bind_dn': ldap_settings.get('ldap_dn_user'),
206 'bind_dn': ldap_settings.get('ldap_dn_user'),
207 'bind_pass': ldap_settings.get('ldap_dn_pass'),
207 'bind_pass': ldap_settings.get('ldap_dn_pass'),
208 'tls_kind': ldap_settings.get('ldap_tls_kind'),
208 'tls_kind': ldap_settings.get('ldap_tls_kind'),
209 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
209 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
210 'ldap_filter': ldap_settings.get('ldap_filter'),
210 'ldap_filter': ldap_settings.get('ldap_filter'),
211 'search_scope': ldap_settings.get('ldap_search_scope'),
211 'search_scope': ldap_settings.get('ldap_search_scope'),
212 'attr_login': ldap_settings.get('ldap_attr_login'),
212 'attr_login': ldap_settings.get('ldap_attr_login'),
213 'ldap_version': 3,
213 'ldap_version': 3,
214 }
214 }
215 log.debug('Checking for ldap authentication')
215 log.debug('Checking for ldap authentication')
216 try:
216 try:
217 aldap = AuthLdap(**kwargs)
217 aldap = AuthLdap(**kwargs)
218 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
218 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
219 password)
219 password)
220 log.debug('Got ldap DN response %s' % user_dn)
220 log.debug('Got ldap DN response %s' % user_dn)
221
221
222 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
222 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
223 .get(k), [''])[0]
223 .get(k), [''])[0]
224
224
225 user_attrs = {
225 user_attrs = {
226 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
226 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
227 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
227 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
228 'email': get_ldap_attr('ldap_attr_email'),
228 'email': get_ldap_attr('ldap_attr_email'),
229 'active': 'hg.register.auto_activate' in User\
230 .get_by_username('default').AuthUser.permissions['global']
229 }
231 }
230
232
231 # don't store LDAP password since we don't need it. Override
233 # don't store LDAP password since we don't need it. Override
232 # with some random generated password
234 # with some random generated password
233 _password = PasswordGenerator().gen_password(length=8)
235 _password = PasswordGenerator().gen_password(length=8)
234 # create this user on the fly if it doesn't exist in rhodecode
236 # create this user on the fly if it doesn't exist in rhodecode
235 # database
237 # database
236 if user_model.create_ldap(username, _password, user_dn,
238 if user_model.create_ldap(username, _password, user_dn,
237 user_attrs):
239 user_attrs):
238 log.info('created new ldap user %s' % username)
240 log.info('created new ldap user %s' % username)
239
241
240 Session().commit()
242 Session().commit()
241 return True
243 return True
242 except (LdapUsernameError, LdapPasswordError,):
244 except (LdapUsernameError, LdapPasswordError,):
243 pass
245 pass
244 except (Exception,):
246 except (Exception,):
245 log.error(traceback.format_exc())
247 log.error(traceback.format_exc())
246 pass
248 pass
247 return False
249 return False
248
250
249
251
250 def login_container_auth(username):
252 def login_container_auth(username):
251 user = User.get_by_username(username)
253 user = User.get_by_username(username)
252 if user is None:
254 if user is None:
253 user_attrs = {
255 user_attrs = {
254 'name': username,
256 'name': username,
255 'lastname': None,
257 'lastname': None,
256 'email': None,
258 'email': None,
259 'active': 'hg.register.auto_activate' in User\
260 .get_by_username('default').AuthUser.permissions['global']
257 }
261 }
258 user = UserModel().create_for_container_auth(username, user_attrs)
262 user = UserModel().create_for_container_auth(username, user_attrs)
259 if not user:
263 if not user:
260 return None
264 return None
261 log.info('User %s was created by container authentication' % username)
265 log.info('User %s was created by container authentication' % username)
262
266
263 if not user.active:
267 if not user.active:
264 return None
268 return None
265
269
266 user.update_lastlogin()
270 user.update_lastlogin()
267 Session().commit()
271 Session().commit()
268
272
269 log.debug('User %s is now logged in by container authentication',
273 log.debug('User %s is now logged in by container authentication',
270 user.username)
274 user.username)
271 return user
275 return user
272
276
273
277
274 def get_container_username(environ, config, clean_username=False):
278 def get_container_username(environ, config, clean_username=False):
275 """
279 """
276 Get's the container_auth username (or email). It tries to get username
280 Get's the container_auth username (or email). It tries to get username
277 from REMOTE_USER if container_auth_enabled is enabled, if that fails
281 from REMOTE_USER if container_auth_enabled is enabled, if that fails
278 it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled
282 it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled
279 is enabled. clean_username extracts the username from this data if it's
283 is enabled. clean_username extracts the username from this data if it's
280 having @ in it.
284 having @ in it.
281
285
282 :param environ:
286 :param environ:
283 :param config:
287 :param config:
284 :param clean_username:
288 :param clean_username:
285 """
289 """
286 username = None
290 username = None
287
291
288 if str2bool(config.get('container_auth_enabled', False)):
292 if str2bool(config.get('container_auth_enabled', False)):
289 from paste.httpheaders import REMOTE_USER
293 from paste.httpheaders import REMOTE_USER
290 username = REMOTE_USER(environ)
294 username = REMOTE_USER(environ)
291 log.debug('extracted REMOTE_USER:%s' % (username))
295 log.debug('extracted REMOTE_USER:%s' % (username))
292
296
293 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
297 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
294 username = environ.get('HTTP_X_FORWARDED_USER')
298 username = environ.get('HTTP_X_FORWARDED_USER')
295 log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username))
299 log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username))
296
300
297 if username and clean_username:
301 if username and clean_username:
298 # Removing realm and domain from username
302 # Removing realm and domain from username
299 username = username.partition('@')[0]
303 username = username.partition('@')[0]
300 username = username.rpartition('\\')[2]
304 username = username.rpartition('\\')[2]
301 log.debug('Received username %s from container' % username)
305 log.debug('Received username %s from container' % username)
302
306
303 return username
307 return username
304
308
305
309
306 class CookieStoreWrapper(object):
310 class CookieStoreWrapper(object):
307
311
308 def __init__(self, cookie_store):
312 def __init__(self, cookie_store):
309 self.cookie_store = cookie_store
313 self.cookie_store = cookie_store
310
314
311 def __repr__(self):
315 def __repr__(self):
312 return 'CookieStore<%s>' % (self.cookie_store)
316 return 'CookieStore<%s>' % (self.cookie_store)
313
317
314 def get(self, key, other=None):
318 def get(self, key, other=None):
315 if isinstance(self.cookie_store, dict):
319 if isinstance(self.cookie_store, dict):
316 return self.cookie_store.get(key, other)
320 return self.cookie_store.get(key, other)
317 elif isinstance(self.cookie_store, AuthUser):
321 elif isinstance(self.cookie_store, AuthUser):
318 return self.cookie_store.__dict__.get(key, other)
322 return self.cookie_store.__dict__.get(key, other)
319
323
320
324
321 class AuthUser(object):
325 class AuthUser(object):
322 """
326 """
323 A simple object that handles all attributes of user in RhodeCode
327 A simple object that handles all attributes of user in RhodeCode
324
328
325 It does lookup based on API key,given user, or user present in session
329 It does lookup based on API key,given user, or user present in session
326 Then it fills all required information for such user. It also checks if
330 Then it fills all required information for such user. It also checks if
327 anonymous access is enabled and if so, it returns default user as logged
331 anonymous access is enabled and if so, it returns default user as logged
328 in
332 in
329 """
333 """
330
334
331 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
335 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
332
336
333 self.user_id = user_id
337 self.user_id = user_id
334 self.api_key = None
338 self.api_key = None
335 self.username = username
339 self.username = username
336 self.ip_addr = ip_addr
340 self.ip_addr = ip_addr
337
341
338 self.name = ''
342 self.name = ''
339 self.lastname = ''
343 self.lastname = ''
340 self.email = ''
344 self.email = ''
341 self.is_authenticated = False
345 self.is_authenticated = False
342 self.admin = False
346 self.admin = False
343 self.inherit_default_permissions = False
347 self.inherit_default_permissions = False
344 self.permissions = {}
348 self.permissions = {}
345 self._api_key = api_key
349 self._api_key = api_key
346 self.propagate_data()
350 self.propagate_data()
347 self._instance = None
351 self._instance = None
348
352
349 def propagate_data(self):
353 def propagate_data(self):
350 user_model = UserModel()
354 user_model = UserModel()
351 self.anonymous_user = User.get_by_username('default', cache=True)
355 self.anonymous_user = User.get_by_username('default', cache=True)
352 is_user_loaded = False
356 is_user_loaded = False
353
357
354 # try go get user by api key
358 # try go get user by api key
355 if self._api_key and self._api_key != self.anonymous_user.api_key:
359 if self._api_key and self._api_key != self.anonymous_user.api_key:
356 log.debug('Auth User lookup by API KEY %s' % self._api_key)
360 log.debug('Auth User lookup by API KEY %s' % self._api_key)
357 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
361 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
358 # lookup by userid
362 # lookup by userid
359 elif (self.user_id is not None and
363 elif (self.user_id is not None and
360 self.user_id != self.anonymous_user.user_id):
364 self.user_id != self.anonymous_user.user_id):
361 log.debug('Auth User lookup by USER ID %s' % self.user_id)
365 log.debug('Auth User lookup by USER ID %s' % self.user_id)
362 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
366 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
363 # lookup by username
367 # lookup by username
364 elif self.username and \
368 elif self.username and \
365 str2bool(config.get('container_auth_enabled', False)):
369 str2bool(config.get('container_auth_enabled', False)):
366
370
367 log.debug('Auth User lookup by USER NAME %s' % self.username)
371 log.debug('Auth User lookup by USER NAME %s' % self.username)
368 dbuser = login_container_auth(self.username)
372 dbuser = login_container_auth(self.username)
369 if dbuser is not None:
373 if dbuser is not None:
370 log.debug('filling all attributes to object')
374 log.debug('filling all attributes to object')
371 for k, v in dbuser.get_dict().items():
375 for k, v in dbuser.get_dict().items():
372 setattr(self, k, v)
376 setattr(self, k, v)
373 self.set_authenticated()
377 self.set_authenticated()
374 is_user_loaded = True
378 is_user_loaded = True
375 else:
379 else:
376 log.debug('No data in %s that could been used to log in' % self)
380 log.debug('No data in %s that could been used to log in' % self)
377
381
378 if not is_user_loaded:
382 if not is_user_loaded:
379 # if we cannot authenticate user try anonymous
383 # if we cannot authenticate user try anonymous
380 if self.anonymous_user.active is True:
384 if self.anonymous_user.active is True:
381 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
385 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
382 # then we set this user is logged in
386 # then we set this user is logged in
383 self.is_authenticated = True
387 self.is_authenticated = True
384 else:
388 else:
385 self.user_id = None
389 self.user_id = None
386 self.username = None
390 self.username = None
387 self.is_authenticated = False
391 self.is_authenticated = False
388
392
389 if not self.username:
393 if not self.username:
390 self.username = 'None'
394 self.username = 'None'
391
395
392 log.debug('Auth User is now %s' % self)
396 log.debug('Auth User is now %s' % self)
393 user_model.fill_perms(self)
397 user_model.fill_perms(self)
394
398
395 @property
399 @property
396 def is_admin(self):
400 def is_admin(self):
397 return self.admin
401 return self.admin
398
402
399 @property
403 @property
400 def ip_allowed(self):
404 def ip_allowed(self):
401 """
405 """
402 Checks if ip_addr used in constructor is allowed from defined list of
406 Checks if ip_addr used in constructor is allowed from defined list of
403 allowed ip_addresses for user
407 allowed ip_addresses for user
404
408
405 :returns: boolean, True if ip is in allowed ip range
409 :returns: boolean, True if ip is in allowed ip range
406 """
410 """
407 #check IP
411 #check IP
408 allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
412 allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
409 if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
413 if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
410 log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
414 log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
411 return True
415 return True
412 else:
416 else:
413 log.info('Access for IP:%s forbidden, '
417 log.info('Access for IP:%s forbidden, '
414 'not in %s' % (self.ip_addr, allowed_ips))
418 'not in %s' % (self.ip_addr, allowed_ips))
415 return False
419 return False
416
420
417 def __repr__(self):
421 def __repr__(self):
418 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
422 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
419 self.is_authenticated)
423 self.is_authenticated)
420
424
421 def set_authenticated(self, authenticated=True):
425 def set_authenticated(self, authenticated=True):
422 if self.user_id != self.anonymous_user.user_id:
426 if self.user_id != self.anonymous_user.user_id:
423 self.is_authenticated = authenticated
427 self.is_authenticated = authenticated
424
428
425 def get_cookie_store(self):
429 def get_cookie_store(self):
426 return {'username': self.username,
430 return {'username': self.username,
427 'user_id': self.user_id,
431 'user_id': self.user_id,
428 'is_authenticated': self.is_authenticated}
432 'is_authenticated': self.is_authenticated}
429
433
430 @classmethod
434 @classmethod
431 def from_cookie_store(cls, cookie_store):
435 def from_cookie_store(cls, cookie_store):
432 """
436 """
433 Creates AuthUser from a cookie store
437 Creates AuthUser from a cookie store
434
438
435 :param cls:
439 :param cls:
436 :param cookie_store:
440 :param cookie_store:
437 """
441 """
438 user_id = cookie_store.get('user_id')
442 user_id = cookie_store.get('user_id')
439 username = cookie_store.get('username')
443 username = cookie_store.get('username')
440 api_key = cookie_store.get('api_key')
444 api_key = cookie_store.get('api_key')
441 return AuthUser(user_id, api_key, username)
445 return AuthUser(user_id, api_key, username)
442
446
443 @classmethod
447 @classmethod
444 def get_allowed_ips(cls, user_id, cache=False):
448 def get_allowed_ips(cls, user_id, cache=False):
445 _set = set()
449 _set = set()
446 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
450 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
447 if cache:
451 if cache:
448 user_ips = user_ips.options(FromCache("sql_cache_short",
452 user_ips = user_ips.options(FromCache("sql_cache_short",
449 "get_user_ips_%s" % user_id))
453 "get_user_ips_%s" % user_id))
450 for ip in user_ips:
454 for ip in user_ips:
451 try:
455 try:
452 _set.add(ip.ip_addr)
456 _set.add(ip.ip_addr)
453 except ObjectDeletedError:
457 except ObjectDeletedError:
454 # since we use heavy caching sometimes it happens that we get
458 # since we use heavy caching sometimes it happens that we get
455 # deleted objects here, we just skip them
459 # deleted objects here, we just skip them
456 pass
460 pass
457 return _set or set(['0.0.0.0/0', '::/0'])
461 return _set or set(['0.0.0.0/0', '::/0'])
458
462
459
463
460 def set_available_permissions(config):
464 def set_available_permissions(config):
461 """
465 """
462 This function will propagate pylons globals with all available defined
466 This function will propagate pylons globals with all available defined
463 permission given in db. We don't want to check each time from db for new
467 permission given in db. We don't want to check each time from db for new
464 permissions since adding a new permission also requires application restart
468 permissions since adding a new permission also requires application restart
465 ie. to decorate new views with the newly created permission
469 ie. to decorate new views with the newly created permission
466
470
467 :param config: current pylons config instance
471 :param config: current pylons config instance
468
472
469 """
473 """
470 log.info('getting information about all available permissions')
474 log.info('getting information about all available permissions')
471 try:
475 try:
472 sa = meta.Session
476 sa = meta.Session
473 all_perms = sa.query(Permission).all()
477 all_perms = sa.query(Permission).all()
474 except Exception:
478 except Exception:
475 pass
479 pass
476 finally:
480 finally:
477 meta.Session.remove()
481 meta.Session.remove()
478
482
479 config['available_permissions'] = [x.permission_name for x in all_perms]
483 config['available_permissions'] = [x.permission_name for x in all_perms]
480
484
481
485
482 #==============================================================================
486 #==============================================================================
483 # CHECK DECORATORS
487 # CHECK DECORATORS
484 #==============================================================================
488 #==============================================================================
485 class LoginRequired(object):
489 class LoginRequired(object):
486 """
490 """
487 Must be logged in to execute this function else
491 Must be logged in to execute this function else
488 redirect to login page
492 redirect to login page
489
493
490 :param api_access: if enabled this checks only for valid auth token
494 :param api_access: if enabled this checks only for valid auth token
491 and grants access based on valid token
495 and grants access based on valid token
492 """
496 """
493
497
494 def __init__(self, api_access=False):
498 def __init__(self, api_access=False):
495 self.api_access = api_access
499 self.api_access = api_access
496
500
497 def __call__(self, func):
501 def __call__(self, func):
498 return decorator(self.__wrapper, func)
502 return decorator(self.__wrapper, func)
499
503
500 def __wrapper(self, func, *fargs, **fkwargs):
504 def __wrapper(self, func, *fargs, **fkwargs):
501 cls = fargs[0]
505 cls = fargs[0]
502 user = cls.rhodecode_user
506 user = cls.rhodecode_user
503 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
507 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
504
508
505 #check IP
509 #check IP
506 ip_access_ok = True
510 ip_access_ok = True
507 if not user.ip_allowed:
511 if not user.ip_allowed:
508 from rhodecode.lib import helpers as h
512 from rhodecode.lib import helpers as h
509 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
513 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
510 category='warning')
514 category='warning')
511 ip_access_ok = False
515 ip_access_ok = False
512
516
513 api_access_ok = False
517 api_access_ok = False
514 if self.api_access:
518 if self.api_access:
515 log.debug('Checking API KEY access for %s' % cls)
519 log.debug('Checking API KEY access for %s' % cls)
516 if user.api_key == request.GET.get('api_key'):
520 if user.api_key == request.GET.get('api_key'):
517 api_access_ok = True
521 api_access_ok = True
518 else:
522 else:
519 log.debug("API KEY token not valid")
523 log.debug("API KEY token not valid")
520
524
521 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
525 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
522 if (user.is_authenticated or api_access_ok) and ip_access_ok:
526 if (user.is_authenticated or api_access_ok) and ip_access_ok:
523 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
527 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
524 log.info('user %s is authenticated and granted access to %s '
528 log.info('user %s is authenticated and granted access to %s '
525 'using %s' % (user.username, loc, reason)
529 'using %s' % (user.username, loc, reason)
526 )
530 )
527 return func(*fargs, **fkwargs)
531 return func(*fargs, **fkwargs)
528 else:
532 else:
529 log.warn('user %s NOT authenticated on func: %s' % (
533 log.warn('user %s NOT authenticated on func: %s' % (
530 user, loc)
534 user, loc)
531 )
535 )
532 p = url.current()
536 p = url.current()
533
537
534 log.debug('redirecting to login page with %s' % p)
538 log.debug('redirecting to login page with %s' % p)
535 return redirect(url('login_home', came_from=p))
539 return redirect(url('login_home', came_from=p))
536
540
537
541
538 class NotAnonymous(object):
542 class NotAnonymous(object):
539 """
543 """
540 Must be logged in to execute this function else
544 Must be logged in to execute this function else
541 redirect to login page"""
545 redirect to login page"""
542
546
543 def __call__(self, func):
547 def __call__(self, func):
544 return decorator(self.__wrapper, func)
548 return decorator(self.__wrapper, func)
545
549
546 def __wrapper(self, func, *fargs, **fkwargs):
550 def __wrapper(self, func, *fargs, **fkwargs):
547 cls = fargs[0]
551 cls = fargs[0]
548 self.user = cls.rhodecode_user
552 self.user = cls.rhodecode_user
549
553
550 log.debug('Checking if user is not anonymous @%s' % cls)
554 log.debug('Checking if user is not anonymous @%s' % cls)
551
555
552 anonymous = self.user.username == 'default'
556 anonymous = self.user.username == 'default'
553
557
554 if anonymous:
558 if anonymous:
555 p = url.current()
559 p = url.current()
556
560
557 import rhodecode.lib.helpers as h
561 import rhodecode.lib.helpers as h
558 h.flash(_('You need to be a registered user to '
562 h.flash(_('You need to be a registered user to '
559 'perform this action'),
563 'perform this action'),
560 category='warning')
564 category='warning')
561 return redirect(url('login_home', came_from=p))
565 return redirect(url('login_home', came_from=p))
562 else:
566 else:
563 return func(*fargs, **fkwargs)
567 return func(*fargs, **fkwargs)
564
568
565
569
566 class PermsDecorator(object):
570 class PermsDecorator(object):
567 """Base class for controller decorators"""
571 """Base class for controller decorators"""
568
572
569 def __init__(self, *required_perms):
573 def __init__(self, *required_perms):
570 available_perms = config['available_permissions']
574 available_perms = config['available_permissions']
571 for perm in required_perms:
575 for perm in required_perms:
572 if perm not in available_perms:
576 if perm not in available_perms:
573 raise Exception("'%s' permission is not defined" % perm)
577 raise Exception("'%s' permission is not defined" % perm)
574 self.required_perms = set(required_perms)
578 self.required_perms = set(required_perms)
575 self.user_perms = None
579 self.user_perms = None
576
580
577 def __call__(self, func):
581 def __call__(self, func):
578 return decorator(self.__wrapper, func)
582 return decorator(self.__wrapper, func)
579
583
580 def __wrapper(self, func, *fargs, **fkwargs):
584 def __wrapper(self, func, *fargs, **fkwargs):
581 cls = fargs[0]
585 cls = fargs[0]
582 self.user = cls.rhodecode_user
586 self.user = cls.rhodecode_user
583 self.user_perms = self.user.permissions
587 self.user_perms = self.user.permissions
584 log.debug('checking %s permissions %s for %s %s',
588 log.debug('checking %s permissions %s for %s %s',
585 self.__class__.__name__, self.required_perms, cls, self.user)
589 self.__class__.__name__, self.required_perms, cls, self.user)
586
590
587 if self.check_permissions():
591 if self.check_permissions():
588 log.debug('Permission granted for %s %s' % (cls, self.user))
592 log.debug('Permission granted for %s %s' % (cls, self.user))
589 return func(*fargs, **fkwargs)
593 return func(*fargs, **fkwargs)
590
594
591 else:
595 else:
592 log.debug('Permission denied for %s %s' % (cls, self.user))
596 log.debug('Permission denied for %s %s' % (cls, self.user))
593 anonymous = self.user.username == 'default'
597 anonymous = self.user.username == 'default'
594
598
595 if anonymous:
599 if anonymous:
596 p = url.current()
600 p = url.current()
597
601
598 import rhodecode.lib.helpers as h
602 import rhodecode.lib.helpers as h
599 h.flash(_('You need to be a signed in to '
603 h.flash(_('You need to be a signed in to '
600 'view this page'),
604 'view this page'),
601 category='warning')
605 category='warning')
602 return redirect(url('login_home', came_from=p))
606 return redirect(url('login_home', came_from=p))
603
607
604 else:
608 else:
605 # redirect with forbidden ret code
609 # redirect with forbidden ret code
606 return abort(403)
610 return abort(403)
607
611
608 def check_permissions(self):
612 def check_permissions(self):
609 """Dummy function for overriding"""
613 """Dummy function for overriding"""
610 raise Exception('You have to write this function in child class')
614 raise Exception('You have to write this function in child class')
611
615
612
616
613 class HasPermissionAllDecorator(PermsDecorator):
617 class HasPermissionAllDecorator(PermsDecorator):
614 """
618 """
615 Checks for access permission for all given predicates. All of them
619 Checks for access permission for all given predicates. All of them
616 have to be meet in order to fulfill the request
620 have to be meet in order to fulfill the request
617 """
621 """
618
622
619 def check_permissions(self):
623 def check_permissions(self):
620 if self.required_perms.issubset(self.user_perms.get('global')):
624 if self.required_perms.issubset(self.user_perms.get('global')):
621 return True
625 return True
622 return False
626 return False
623
627
624
628
625 class HasPermissionAnyDecorator(PermsDecorator):
629 class HasPermissionAnyDecorator(PermsDecorator):
626 """
630 """
627 Checks for access permission for any of given predicates. In order to
631 Checks for access permission for any of given predicates. In order to
628 fulfill the request any of predicates must be meet
632 fulfill the request any of predicates must be meet
629 """
633 """
630
634
631 def check_permissions(self):
635 def check_permissions(self):
632 if self.required_perms.intersection(self.user_perms.get('global')):
636 if self.required_perms.intersection(self.user_perms.get('global')):
633 return True
637 return True
634 return False
638 return False
635
639
636
640
637 class HasRepoPermissionAllDecorator(PermsDecorator):
641 class HasRepoPermissionAllDecorator(PermsDecorator):
638 """
642 """
639 Checks for access permission for all given predicates for specific
643 Checks for access permission for all given predicates for specific
640 repository. All of them have to be meet in order to fulfill the request
644 repository. All of them have to be meet in order to fulfill the request
641 """
645 """
642
646
643 def check_permissions(self):
647 def check_permissions(self):
644 repo_name = get_repo_slug(request)
648 repo_name = get_repo_slug(request)
645 try:
649 try:
646 user_perms = set([self.user_perms['repositories'][repo_name]])
650 user_perms = set([self.user_perms['repositories'][repo_name]])
647 except KeyError:
651 except KeyError:
648 return False
652 return False
649 if self.required_perms.issubset(user_perms):
653 if self.required_perms.issubset(user_perms):
650 return True
654 return True
651 return False
655 return False
652
656
653
657
654 class HasRepoPermissionAnyDecorator(PermsDecorator):
658 class HasRepoPermissionAnyDecorator(PermsDecorator):
655 """
659 """
656 Checks for access permission for any of given predicates for specific
660 Checks for access permission for any of given predicates for specific
657 repository. In order to fulfill the request any of predicates must be meet
661 repository. In order to fulfill the request any of predicates must be meet
658 """
662 """
659
663
660 def check_permissions(self):
664 def check_permissions(self):
661 repo_name = get_repo_slug(request)
665 repo_name = get_repo_slug(request)
662
666
663 try:
667 try:
664 user_perms = set([self.user_perms['repositories'][repo_name]])
668 user_perms = set([self.user_perms['repositories'][repo_name]])
665 except KeyError:
669 except KeyError:
666 return False
670 return False
667
671
668 if self.required_perms.intersection(user_perms):
672 if self.required_perms.intersection(user_perms):
669 return True
673 return True
670 return False
674 return False
671
675
672
676
673 class HasReposGroupPermissionAllDecorator(PermsDecorator):
677 class HasReposGroupPermissionAllDecorator(PermsDecorator):
674 """
678 """
675 Checks for access permission for all given predicates for specific
679 Checks for access permission for all given predicates for specific
676 repository. All of them have to be meet in order to fulfill the request
680 repository. All of them have to be meet in order to fulfill the request
677 """
681 """
678
682
679 def check_permissions(self):
683 def check_permissions(self):
680 group_name = get_repos_group_slug(request)
684 group_name = get_repos_group_slug(request)
681 try:
685 try:
682 user_perms = set([self.user_perms['repositories_groups'][group_name]])
686 user_perms = set([self.user_perms['repositories_groups'][group_name]])
683 except KeyError:
687 except KeyError:
684 return False
688 return False
685 if self.required_perms.issubset(user_perms):
689 if self.required_perms.issubset(user_perms):
686 return True
690 return True
687 return False
691 return False
688
692
689
693
690 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
694 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
691 """
695 """
692 Checks for access permission for any of given predicates for specific
696 Checks for access permission for any of given predicates for specific
693 repository. In order to fulfill the request any of predicates must be meet
697 repository. In order to fulfill the request any of predicates must be meet
694 """
698 """
695
699
696 def check_permissions(self):
700 def check_permissions(self):
697 group_name = get_repos_group_slug(request)
701 group_name = get_repos_group_slug(request)
698
702
699 try:
703 try:
700 user_perms = set([self.user_perms['repositories_groups'][group_name]])
704 user_perms = set([self.user_perms['repositories_groups'][group_name]])
701 except KeyError:
705 except KeyError:
702 return False
706 return False
703 if self.required_perms.intersection(user_perms):
707 if self.required_perms.intersection(user_perms):
704 return True
708 return True
705 return False
709 return False
706
710
707
711
708 #==============================================================================
712 #==============================================================================
709 # CHECK FUNCTIONS
713 # CHECK FUNCTIONS
710 #==============================================================================
714 #==============================================================================
711 class PermsFunction(object):
715 class PermsFunction(object):
712 """Base function for other check functions"""
716 """Base function for other check functions"""
713
717
714 def __init__(self, *perms):
718 def __init__(self, *perms):
715 available_perms = config['available_permissions']
719 available_perms = config['available_permissions']
716
720
717 for perm in perms:
721 for perm in perms:
718 if perm not in available_perms:
722 if perm not in available_perms:
719 raise Exception("'%s' permission is not defined" % perm)
723 raise Exception("'%s' permission is not defined" % perm)
720 self.required_perms = set(perms)
724 self.required_perms = set(perms)
721 self.user_perms = None
725 self.user_perms = None
722 self.repo_name = None
726 self.repo_name = None
723 self.group_name = None
727 self.group_name = None
724
728
725 def __call__(self, check_Location=''):
729 def __call__(self, check_Location=''):
726 user = request.user
730 user = request.user
727 cls_name = self.__class__.__name__
731 cls_name = self.__class__.__name__
728 check_scope = {
732 check_scope = {
729 'HasPermissionAll': '',
733 'HasPermissionAll': '',
730 'HasPermissionAny': '',
734 'HasPermissionAny': '',
731 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
735 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
732 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
736 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
733 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
737 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
734 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
738 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
735 }.get(cls_name, '?')
739 }.get(cls_name, '?')
736 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
740 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
737 self.required_perms, user, check_scope,
741 self.required_perms, user, check_scope,
738 check_Location or 'unspecified location')
742 check_Location or 'unspecified location')
739 if not user:
743 if not user:
740 log.debug('Empty request user')
744 log.debug('Empty request user')
741 return False
745 return False
742 self.user_perms = user.permissions
746 self.user_perms = user.permissions
743 if self.check_permissions():
747 if self.check_permissions():
744 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
748 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
745 check_Location or 'unspecified location')
749 check_Location or 'unspecified location')
746 return True
750 return True
747
751
748 else:
752 else:
749 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
753 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
750 check_Location or 'unspecified location')
754 check_Location or 'unspecified location')
751 return False
755 return False
752
756
753 def check_permissions(self):
757 def check_permissions(self):
754 """Dummy function for overriding"""
758 """Dummy function for overriding"""
755 raise Exception('You have to write this function in child class')
759 raise Exception('You have to write this function in child class')
756
760
757
761
758 class HasPermissionAll(PermsFunction):
762 class HasPermissionAll(PermsFunction):
759 def check_permissions(self):
763 def check_permissions(self):
760 if self.required_perms.issubset(self.user_perms.get('global')):
764 if self.required_perms.issubset(self.user_perms.get('global')):
761 return True
765 return True
762 return False
766 return False
763
767
764
768
765 class HasPermissionAny(PermsFunction):
769 class HasPermissionAny(PermsFunction):
766 def check_permissions(self):
770 def check_permissions(self):
767 if self.required_perms.intersection(self.user_perms.get('global')):
771 if self.required_perms.intersection(self.user_perms.get('global')):
768 return True
772 return True
769 return False
773 return False
770
774
771
775
772 class HasRepoPermissionAll(PermsFunction):
776 class HasRepoPermissionAll(PermsFunction):
773 def __call__(self, repo_name=None, check_Location=''):
777 def __call__(self, repo_name=None, check_Location=''):
774 self.repo_name = repo_name
778 self.repo_name = repo_name
775 return super(HasRepoPermissionAll, self).__call__(check_Location)
779 return super(HasRepoPermissionAll, self).__call__(check_Location)
776
780
777 def check_permissions(self):
781 def check_permissions(self):
778 if not self.repo_name:
782 if not self.repo_name:
779 self.repo_name = get_repo_slug(request)
783 self.repo_name = get_repo_slug(request)
780
784
781 try:
785 try:
782 self._user_perms = set(
786 self._user_perms = set(
783 [self.user_perms['repositories'][self.repo_name]]
787 [self.user_perms['repositories'][self.repo_name]]
784 )
788 )
785 except KeyError:
789 except KeyError:
786 return False
790 return False
787 if self.required_perms.issubset(self._user_perms):
791 if self.required_perms.issubset(self._user_perms):
788 return True
792 return True
789 return False
793 return False
790
794
791
795
792 class HasRepoPermissionAny(PermsFunction):
796 class HasRepoPermissionAny(PermsFunction):
793 def __call__(self, repo_name=None, check_Location=''):
797 def __call__(self, repo_name=None, check_Location=''):
794 self.repo_name = repo_name
798 self.repo_name = repo_name
795 return super(HasRepoPermissionAny, self).__call__(check_Location)
799 return super(HasRepoPermissionAny, self).__call__(check_Location)
796
800
797 def check_permissions(self):
801 def check_permissions(self):
798 if not self.repo_name:
802 if not self.repo_name:
799 self.repo_name = get_repo_slug(request)
803 self.repo_name = get_repo_slug(request)
800
804
801 try:
805 try:
802 self._user_perms = set(
806 self._user_perms = set(
803 [self.user_perms['repositories'][self.repo_name]]
807 [self.user_perms['repositories'][self.repo_name]]
804 )
808 )
805 except KeyError:
809 except KeyError:
806 return False
810 return False
807 if self.required_perms.intersection(self._user_perms):
811 if self.required_perms.intersection(self._user_perms):
808 return True
812 return True
809 return False
813 return False
810
814
811
815
812 class HasReposGroupPermissionAny(PermsFunction):
816 class HasReposGroupPermissionAny(PermsFunction):
813 def __call__(self, group_name=None, check_Location=''):
817 def __call__(self, group_name=None, check_Location=''):
814 self.group_name = group_name
818 self.group_name = group_name
815 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
819 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
816
820
817 def check_permissions(self):
821 def check_permissions(self):
818 try:
822 try:
819 self._user_perms = set(
823 self._user_perms = set(
820 [self.user_perms['repositories_groups'][self.group_name]]
824 [self.user_perms['repositories_groups'][self.group_name]]
821 )
825 )
822 except KeyError:
826 except KeyError:
823 return False
827 return False
824 if self.required_perms.intersection(self._user_perms):
828 if self.required_perms.intersection(self._user_perms):
825 return True
829 return True
826 return False
830 return False
827
831
828
832
829 class HasReposGroupPermissionAll(PermsFunction):
833 class HasReposGroupPermissionAll(PermsFunction):
830 def __call__(self, group_name=None, check_Location=''):
834 def __call__(self, group_name=None, check_Location=''):
831 self.group_name = group_name
835 self.group_name = group_name
832 return super(HasReposGroupPermissionAll, self).__call__(check_Location)
836 return super(HasReposGroupPermissionAll, self).__call__(check_Location)
833
837
834 def check_permissions(self):
838 def check_permissions(self):
835 try:
839 try:
836 self._user_perms = set(
840 self._user_perms = set(
837 [self.user_perms['repositories_groups'][self.group_name]]
841 [self.user_perms['repositories_groups'][self.group_name]]
838 )
842 )
839 except KeyError:
843 except KeyError:
840 return False
844 return False
841 if self.required_perms.issubset(self._user_perms):
845 if self.required_perms.issubset(self._user_perms):
842 return True
846 return True
843 return False
847 return False
844
848
845
849
846 #==============================================================================
850 #==============================================================================
847 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
851 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
848 #==============================================================================
852 #==============================================================================
849 class HasPermissionAnyMiddleware(object):
853 class HasPermissionAnyMiddleware(object):
850 def __init__(self, *perms):
854 def __init__(self, *perms):
851 self.required_perms = set(perms)
855 self.required_perms = set(perms)
852
856
853 def __call__(self, user, repo_name):
857 def __call__(self, user, repo_name):
854 # repo_name MUST be unicode, since we handle keys in permission
858 # repo_name MUST be unicode, since we handle keys in permission
855 # dict by unicode
859 # dict by unicode
856 repo_name = safe_unicode(repo_name)
860 repo_name = safe_unicode(repo_name)
857 usr = AuthUser(user.user_id)
861 usr = AuthUser(user.user_id)
858 try:
862 try:
859 self.user_perms = set([usr.permissions['repositories'][repo_name]])
863 self.user_perms = set([usr.permissions['repositories'][repo_name]])
860 except Exception:
864 except Exception:
861 log.error('Exception while accessing permissions %s' %
865 log.error('Exception while accessing permissions %s' %
862 traceback.format_exc())
866 traceback.format_exc())
863 self.user_perms = set()
867 self.user_perms = set()
864 self.username = user.username
868 self.username = user.username
865 self.repo_name = repo_name
869 self.repo_name = repo_name
866 return self.check_permissions()
870 return self.check_permissions()
867
871
868 def check_permissions(self):
872 def check_permissions(self):
869 log.debug('checking VCS protocol '
873 log.debug('checking VCS protocol '
870 'permissions %s for user:%s repository:%s', self.user_perms,
874 'permissions %s for user:%s repository:%s', self.user_perms,
871 self.username, self.repo_name)
875 self.username, self.repo_name)
872 if self.required_perms.intersection(self.user_perms):
876 if self.required_perms.intersection(self.user_perms):
873 log.debug('permission granted for user:%s on repo:%s' % (
877 log.debug('permission granted for user:%s on repo:%s' % (
874 self.username, self.repo_name
878 self.username, self.repo_name
875 )
879 )
876 )
880 )
877 return True
881 return True
878 log.debug('permission denied for user:%s on repo:%s' % (
882 log.debug('permission denied for user:%s on repo:%s' % (
879 self.username, self.repo_name
883 self.username, self.repo_name
880 )
884 )
881 )
885 )
882 return False
886 return False
883
887
884
888
885 #==============================================================================
889 #==============================================================================
886 # SPECIAL VERSION TO HANDLE API AUTH
890 # SPECIAL VERSION TO HANDLE API AUTH
887 #==============================================================================
891 #==============================================================================
888 class _BaseApiPerm(object):
892 class _BaseApiPerm(object):
889 def __init__(self, *perms):
893 def __init__(self, *perms):
890 self.required_perms = set(perms)
894 self.required_perms = set(perms)
891
895
892 def __call__(self, check_location='unspecified', user=None, repo_name=None):
896 def __call__(self, check_location='unspecified', user=None, repo_name=None):
893 cls_name = self.__class__.__name__
897 cls_name = self.__class__.__name__
894 check_scope = 'user:%s, repo:%s' % (user, repo_name)
898 check_scope = 'user:%s, repo:%s' % (user, repo_name)
895 log.debug('checking cls:%s %s %s @ %s', cls_name,
899 log.debug('checking cls:%s %s %s @ %s', cls_name,
896 self.required_perms, check_scope, check_location)
900 self.required_perms, check_scope, check_location)
897 if not user:
901 if not user:
898 log.debug('Empty User passed into arguments')
902 log.debug('Empty User passed into arguments')
899 return False
903 return False
900
904
901 ## process user
905 ## process user
902 if not isinstance(user, AuthUser):
906 if not isinstance(user, AuthUser):
903 user = AuthUser(user.user_id)
907 user = AuthUser(user.user_id)
904
908
905 if self.check_permissions(user.permissions, repo_name):
909 if self.check_permissions(user.permissions, repo_name):
906 log.debug('Permission to %s granted for user: %s @ %s', repo_name,
910 log.debug('Permission to %s granted for user: %s @ %s', repo_name,
907 user, check_location)
911 user, check_location)
908 return True
912 return True
909
913
910 else:
914 else:
911 log.debug('Permission to %s denied for user: %s @ %s', repo_name,
915 log.debug('Permission to %s denied for user: %s @ %s', repo_name,
912 user, check_location)
916 user, check_location)
913 return False
917 return False
914
918
915 def check_permissions(self, perm_defs, repo_name):
919 def check_permissions(self, perm_defs, repo_name):
916 """
920 """
917 implement in child class should return True if permissions are ok,
921 implement in child class should return True if permissions are ok,
918 False otherwise
922 False otherwise
919
923
920 :param perm_defs: dict with permission definitions
924 :param perm_defs: dict with permission definitions
921 :param repo_name: repo name
925 :param repo_name: repo name
922 """
926 """
923 raise NotImplementedError()
927 raise NotImplementedError()
924
928
925
929
926 class HasPermissionAllApi(_BaseApiPerm):
930 class HasPermissionAllApi(_BaseApiPerm):
927 def __call__(self, user, check_location=''):
931 def __call__(self, user, check_location=''):
928 return super(HasPermissionAllApi, self)\
932 return super(HasPermissionAllApi, self)\
929 .__call__(check_location=check_location, user=user)
933 .__call__(check_location=check_location, user=user)
930
934
931 def check_permissions(self, perm_defs, repo):
935 def check_permissions(self, perm_defs, repo):
932 if self.required_perms.issubset(perm_defs.get('global')):
936 if self.required_perms.issubset(perm_defs.get('global')):
933 return True
937 return True
934 return False
938 return False
935
939
936
940
937 class HasPermissionAnyApi(_BaseApiPerm):
941 class HasPermissionAnyApi(_BaseApiPerm):
938 def __call__(self, user, check_location=''):
942 def __call__(self, user, check_location=''):
939 return super(HasPermissionAnyApi, self)\
943 return super(HasPermissionAnyApi, self)\
940 .__call__(check_location=check_location, user=user)
944 .__call__(check_location=check_location, user=user)
941
945
942 def check_permissions(self, perm_defs, repo):
946 def check_permissions(self, perm_defs, repo):
943 if self.required_perms.intersection(perm_defs.get('global')):
947 if self.required_perms.intersection(perm_defs.get('global')):
944 return True
948 return True
945 return False
949 return False
946
950
947
951
948 class HasRepoPermissionAllApi(_BaseApiPerm):
952 class HasRepoPermissionAllApi(_BaseApiPerm):
949 def __call__(self, user, repo_name, check_location=''):
953 def __call__(self, user, repo_name, check_location=''):
950 return super(HasRepoPermissionAllApi, self)\
954 return super(HasRepoPermissionAllApi, self)\
951 .__call__(check_location=check_location, user=user,
955 .__call__(check_location=check_location, user=user,
952 repo_name=repo_name)
956 repo_name=repo_name)
953
957
954 def check_permissions(self, perm_defs, repo_name):
958 def check_permissions(self, perm_defs, repo_name):
955
959
956 try:
960 try:
957 self._user_perms = set(
961 self._user_perms = set(
958 [perm_defs['repositories'][repo_name]]
962 [perm_defs['repositories'][repo_name]]
959 )
963 )
960 except KeyError:
964 except KeyError:
961 log.warning(traceback.format_exc())
965 log.warning(traceback.format_exc())
962 return False
966 return False
963 if self.required_perms.issubset(self._user_perms):
967 if self.required_perms.issubset(self._user_perms):
964 return True
968 return True
965 return False
969 return False
966
970
967
971
968 class HasRepoPermissionAnyApi(_BaseApiPerm):
972 class HasRepoPermissionAnyApi(_BaseApiPerm):
969 def __call__(self, user, repo_name, check_location=''):
973 def __call__(self, user, repo_name, check_location=''):
970 return super(HasRepoPermissionAnyApi, self)\
974 return super(HasRepoPermissionAnyApi, self)\
971 .__call__(check_location=check_location, user=user,
975 .__call__(check_location=check_location, user=user,
972 repo_name=repo_name)
976 repo_name=repo_name)
973
977
974 def check_permissions(self, perm_defs, repo_name):
978 def check_permissions(self, perm_defs, repo_name):
975
979
976 try:
980 try:
977 _user_perms = set(
981 _user_perms = set(
978 [perm_defs['repositories'][repo_name]]
982 [perm_defs['repositories'][repo_name]]
979 )
983 )
980 except KeyError:
984 except KeyError:
981 log.warning(traceback.format_exc())
985 log.warning(traceback.format_exc())
982 return False
986 return False
983 if self.required_perms.intersection(_user_perms):
987 if self.required_perms.intersection(_user_perms):
984 return True
988 return True
985 return False
989 return False
986
990
987
991
988 def check_ip_access(source_ip, allowed_ips=None):
992 def check_ip_access(source_ip, allowed_ips=None):
989 """
993 """
990 Checks if source_ip is a subnet of any of allowed_ips.
994 Checks if source_ip is a subnet of any of allowed_ips.
991
995
992 :param source_ip:
996 :param source_ip:
993 :param allowed_ips: list of allowed ips together with mask
997 :param allowed_ips: list of allowed ips together with mask
994 """
998 """
995 from rhodecode.lib import ipaddr
999 from rhodecode.lib import ipaddr
996 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1000 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
997 if isinstance(allowed_ips, (tuple, list, set)):
1001 if isinstance(allowed_ips, (tuple, list, set)):
998 for ip in allowed_ips:
1002 for ip in allowed_ips:
999 try:
1003 try:
1000 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1004 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1001 return True
1005 return True
1002 # for any case we cannot determine the IP, don't crash just
1006 # for any case we cannot determine the IP, don't crash just
1003 # skip it and log as error, we want to say forbidden still when
1007 # skip it and log as error, we want to say forbidden still when
1004 # sending bad IP
1008 # sending bad IP
1005 except Exception:
1009 except Exception:
1006 log.error(traceback.format_exc())
1010 log.error(traceback.format_exc())
1007 continue
1011 continue
1008 return False
1012 return False
@@ -1,1977 +1,1986 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import time
31 import time
32 from collections import defaultdict
32 from collections import defaultdict
33
33
34 from sqlalchemy import *
34 from sqlalchemy import *
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
40
40
41 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
42
42
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47
47
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 safe_unicode, remove_suffix, remove_prefix
49 safe_unicode, remove_suffix, remove_prefix
50 from rhodecode.lib.compat import json
50 from rhodecode.lib.compat import json
51 from rhodecode.lib.caching_query import FromCache
51 from rhodecode.lib.caching_query import FromCache
52
52
53 from rhodecode.model.meta import Base, Session
53 from rhodecode.model.meta import Base, Session
54
54
55 URL_SEP = '/'
55 URL_SEP = '/'
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58 #==============================================================================
58 #==============================================================================
59 # BASE CLASSES
59 # BASE CLASSES
60 #==============================================================================
60 #==============================================================================
61
61
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63
63
64
64
65 class BaseModel(object):
65 class BaseModel(object):
66 """
66 """
67 Base Model for all classess
67 Base Model for all classess
68 """
68 """
69
69
70 @classmethod
70 @classmethod
71 def _get_keys(cls):
71 def _get_keys(cls):
72 """return column names for this model """
72 """return column names for this model """
73 return class_mapper(cls).c.keys()
73 return class_mapper(cls).c.keys()
74
74
75 def get_dict(self):
75 def get_dict(self):
76 """
76 """
77 return dict with keys and values corresponding
77 return dict with keys and values corresponding
78 to this model data """
78 to this model data """
79
79
80 d = {}
80 d = {}
81 for k in self._get_keys():
81 for k in self._get_keys():
82 d[k] = getattr(self, k)
82 d[k] = getattr(self, k)
83
83
84 # also use __json__() if present to get additional fields
84 # also use __json__() if present to get additional fields
85 _json_attr = getattr(self, '__json__', None)
85 _json_attr = getattr(self, '__json__', None)
86 if _json_attr:
86 if _json_attr:
87 # update with attributes from __json__
87 # update with attributes from __json__
88 if callable(_json_attr):
88 if callable(_json_attr):
89 _json_attr = _json_attr()
89 _json_attr = _json_attr()
90 for k, val in _json_attr.iteritems():
90 for k, val in _json_attr.iteritems():
91 d[k] = val
91 d[k] = val
92 return d
92 return d
93
93
94 def get_appstruct(self):
94 def get_appstruct(self):
95 """return list with keys and values tupples corresponding
95 """return list with keys and values tupples corresponding
96 to this model data """
96 to this model data """
97
97
98 l = []
98 l = []
99 for k in self._get_keys():
99 for k in self._get_keys():
100 l.append((k, getattr(self, k),))
100 l.append((k, getattr(self, k),))
101 return l
101 return l
102
102
103 def populate_obj(self, populate_dict):
103 def populate_obj(self, populate_dict):
104 """populate model with data from given populate_dict"""
104 """populate model with data from given populate_dict"""
105
105
106 for k in self._get_keys():
106 for k in self._get_keys():
107 if k in populate_dict:
107 if k in populate_dict:
108 setattr(self, k, populate_dict[k])
108 setattr(self, k, populate_dict[k])
109
109
110 @classmethod
110 @classmethod
111 def query(cls):
111 def query(cls):
112 return Session().query(cls)
112 return Session().query(cls)
113
113
114 @classmethod
114 @classmethod
115 def get(cls, id_):
115 def get(cls, id_):
116 if id_:
116 if id_:
117 return cls.query().get(id_)
117 return cls.query().get(id_)
118
118
119 @classmethod
119 @classmethod
120 def get_or_404(cls, id_):
120 def get_or_404(cls, id_):
121 try:
121 try:
122 id_ = int(id_)
122 id_ = int(id_)
123 except (TypeError, ValueError):
123 except (TypeError, ValueError):
124 raise HTTPNotFound
124 raise HTTPNotFound
125
125
126 res = cls.query().get(id_)
126 res = cls.query().get(id_)
127 if not res:
127 if not res:
128 raise HTTPNotFound
128 raise HTTPNotFound
129 return res
129 return res
130
130
131 @classmethod
131 @classmethod
132 def getAll(cls):
132 def getAll(cls):
133 return cls.query().all()
133 return cls.query().all()
134
134
135 @classmethod
135 @classmethod
136 def delete(cls, id_):
136 def delete(cls, id_):
137 obj = cls.query().get(id_)
137 obj = cls.query().get(id_)
138 Session().delete(obj)
138 Session().delete(obj)
139
139
140 def __repr__(self):
140 def __repr__(self):
141 if hasattr(self, '__unicode__'):
141 if hasattr(self, '__unicode__'):
142 # python repr needs to return str
142 # python repr needs to return str
143 return safe_str(self.__unicode__())
143 return safe_str(self.__unicode__())
144 return '<DB:%s>' % (self.__class__.__name__)
144 return '<DB:%s>' % (self.__class__.__name__)
145
145
146
146
147 class RhodeCodeSetting(Base, BaseModel):
147 class RhodeCodeSetting(Base, BaseModel):
148 __tablename__ = 'rhodecode_settings'
148 __tablename__ = 'rhodecode_settings'
149 __table_args__ = (
149 __table_args__ = (
150 UniqueConstraint('app_settings_name'),
150 UniqueConstraint('app_settings_name'),
151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 'mysql_charset': 'utf8'}
152 'mysql_charset': 'utf8'}
153 )
153 )
154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157
157
158 def __init__(self, k='', v=''):
158 def __init__(self, k='', v=''):
159 self.app_settings_name = k
159 self.app_settings_name = k
160 self.app_settings_value = v
160 self.app_settings_value = v
161
161
162 @validates('_app_settings_value')
162 @validates('_app_settings_value')
163 def validate_settings_value(self, key, val):
163 def validate_settings_value(self, key, val):
164 assert type(val) == unicode
164 assert type(val) == unicode
165 return val
165 return val
166
166
167 @hybrid_property
167 @hybrid_property
168 def app_settings_value(self):
168 def app_settings_value(self):
169 v = self._app_settings_value
169 v = self._app_settings_value
170 if self.app_settings_name in ["ldap_active",
170 if self.app_settings_name in ["ldap_active",
171 "default_repo_enable_statistics",
171 "default_repo_enable_statistics",
172 "default_repo_enable_locking",
172 "default_repo_enable_locking",
173 "default_repo_private",
173 "default_repo_private",
174 "default_repo_enable_downloads"]:
174 "default_repo_enable_downloads"]:
175 v = str2bool(v)
175 v = str2bool(v)
176 return v
176 return v
177
177
178 @app_settings_value.setter
178 @app_settings_value.setter
179 def app_settings_value(self, val):
179 def app_settings_value(self, val):
180 """
180 """
181 Setter that will always make sure we use unicode in app_settings_value
181 Setter that will always make sure we use unicode in app_settings_value
182
182
183 :param val:
183 :param val:
184 """
184 """
185 self._app_settings_value = safe_unicode(val)
185 self._app_settings_value = safe_unicode(val)
186
186
187 def __unicode__(self):
187 def __unicode__(self):
188 return u"<%s('%s:%s')>" % (
188 return u"<%s('%s:%s')>" % (
189 self.__class__.__name__,
189 self.__class__.__name__,
190 self.app_settings_name, self.app_settings_value
190 self.app_settings_name, self.app_settings_value
191 )
191 )
192
192
193 @classmethod
193 @classmethod
194 def get_by_name(cls, key):
194 def get_by_name(cls, key):
195 return cls.query()\
195 return cls.query()\
196 .filter(cls.app_settings_name == key).scalar()
196 .filter(cls.app_settings_name == key).scalar()
197
197
198 @classmethod
198 @classmethod
199 def get_by_name_or_create(cls, key):
199 def get_by_name_or_create(cls, key):
200 res = cls.get_by_name(key)
200 res = cls.get_by_name(key)
201 if not res:
201 if not res:
202 res = cls(key)
202 res = cls(key)
203 return res
203 return res
204
204
205 @classmethod
205 @classmethod
206 def get_app_settings(cls, cache=False):
206 def get_app_settings(cls, cache=False):
207
207
208 ret = cls.query()
208 ret = cls.query()
209
209
210 if cache:
210 if cache:
211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
212
212
213 if not ret:
213 if not ret:
214 raise Exception('Could not get application settings !')
214 raise Exception('Could not get application settings !')
215 settings = {}
215 settings = {}
216 for each in ret:
216 for each in ret:
217 settings['rhodecode_' + each.app_settings_name] = \
217 settings['rhodecode_' + each.app_settings_name] = \
218 each.app_settings_value
218 each.app_settings_value
219
219
220 return settings
220 return settings
221
221
222 @classmethod
222 @classmethod
223 def get_ldap_settings(cls, cache=False):
223 def get_ldap_settings(cls, cache=False):
224 ret = cls.query()\
224 ret = cls.query()\
225 .filter(cls.app_settings_name.startswith('ldap_')).all()
225 .filter(cls.app_settings_name.startswith('ldap_')).all()
226 fd = {}
226 fd = {}
227 for row in ret:
227 for row in ret:
228 fd.update({row.app_settings_name: row.app_settings_value})
228 fd.update({row.app_settings_name: row.app_settings_value})
229
229
230 return fd
230 return fd
231
231
232 @classmethod
232 @classmethod
233 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
233 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
234 ret = cls.query()\
234 ret = cls.query()\
235 .filter(cls.app_settings_name.startswith('default_')).all()
235 .filter(cls.app_settings_name.startswith('default_')).all()
236 fd = {}
236 fd = {}
237 for row in ret:
237 for row in ret:
238 key = row.app_settings_name
238 key = row.app_settings_name
239 if strip_prefix:
239 if strip_prefix:
240 key = remove_prefix(key, prefix='default_')
240 key = remove_prefix(key, prefix='default_')
241 fd.update({key: row.app_settings_value})
241 fd.update({key: row.app_settings_value})
242
242
243 return fd
243 return fd
244
244
245
245
246 class RhodeCodeUi(Base, BaseModel):
246 class RhodeCodeUi(Base, BaseModel):
247 __tablename__ = 'rhodecode_ui'
247 __tablename__ = 'rhodecode_ui'
248 __table_args__ = (
248 __table_args__ = (
249 UniqueConstraint('ui_key'),
249 UniqueConstraint('ui_key'),
250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 'mysql_charset': 'utf8'}
251 'mysql_charset': 'utf8'}
252 )
252 )
253
253
254 HOOK_UPDATE = 'changegroup.update'
254 HOOK_UPDATE = 'changegroup.update'
255 HOOK_REPO_SIZE = 'changegroup.repo_size'
255 HOOK_REPO_SIZE = 'changegroup.repo_size'
256 HOOK_PUSH = 'changegroup.push_logger'
256 HOOK_PUSH = 'changegroup.push_logger'
257 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
257 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
258 HOOK_PULL = 'outgoing.pull_logger'
258 HOOK_PULL = 'outgoing.pull_logger'
259 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
259 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
260
260
261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
266
266
267 @classmethod
267 @classmethod
268 def get_by_key(cls, key):
268 def get_by_key(cls, key):
269 return cls.query().filter(cls.ui_key == key).scalar()
269 return cls.query().filter(cls.ui_key == key).scalar()
270
270
271 @classmethod
271 @classmethod
272 def get_builtin_hooks(cls):
272 def get_builtin_hooks(cls):
273 q = cls.query()
273 q = cls.query()
274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
276 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
276 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
277 return q.all()
277 return q.all()
278
278
279 @classmethod
279 @classmethod
280 def get_custom_hooks(cls):
280 def get_custom_hooks(cls):
281 q = cls.query()
281 q = cls.query()
282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
284 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
284 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
285 q = q.filter(cls.ui_section == 'hooks')
285 q = q.filter(cls.ui_section == 'hooks')
286 return q.all()
286 return q.all()
287
287
288 @classmethod
288 @classmethod
289 def get_repos_location(cls):
289 def get_repos_location(cls):
290 return cls.get_by_key('/').ui_value
290 return cls.get_by_key('/').ui_value
291
291
292 @classmethod
292 @classmethod
293 def create_or_update_hook(cls, key, val):
293 def create_or_update_hook(cls, key, val):
294 new_ui = cls.get_by_key(key) or cls()
294 new_ui = cls.get_by_key(key) or cls()
295 new_ui.ui_section = 'hooks'
295 new_ui.ui_section = 'hooks'
296 new_ui.ui_active = True
296 new_ui.ui_active = True
297 new_ui.ui_key = key
297 new_ui.ui_key = key
298 new_ui.ui_value = val
298 new_ui.ui_value = val
299
299
300 Session().add(new_ui)
300 Session().add(new_ui)
301
301
302 def __repr__(self):
302 def __repr__(self):
303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
304 self.ui_value)
304 self.ui_value)
305
305
306
306
307 class User(Base, BaseModel):
307 class User(Base, BaseModel):
308 __tablename__ = 'users'
308 __tablename__ = 'users'
309 __table_args__ = (
309 __table_args__ = (
310 UniqueConstraint('username'), UniqueConstraint('email'),
310 UniqueConstraint('username'), UniqueConstraint('email'),
311 Index('u_username_idx', 'username'),
311 Index('u_username_idx', 'username'),
312 Index('u_email_idx', 'email'),
312 Index('u_email_idx', 'email'),
313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
314 'mysql_charset': 'utf8'}
314 'mysql_charset': 'utf8'}
315 )
315 )
316 DEFAULT_USER = 'default'
316 DEFAULT_USER = 'default'
317 DEFAULT_PERMISSIONS = [
317 DEFAULT_PERMISSIONS = [
318 'hg.register.manual_activate', 'hg.create.repository',
318 'hg.register.manual_activate', 'hg.create.repository',
319 'hg.fork.repository', 'repository.read', 'group.read'
319 'hg.fork.repository', 'repository.read', 'group.read'
320 ]
320 ]
321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
324 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
333
333
334 user_log = relationship('UserLog')
334 user_log = relationship('UserLog')
335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
336
336
337 repositories = relationship('Repository')
337 repositories = relationship('Repository')
338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
340
340
341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
343
343
344 group_member = relationship('UsersGroupMember', cascade='all')
344 group_member = relationship('UsersGroupMember', cascade='all')
345
345
346 notifications = relationship('UserNotification', cascade='all')
346 notifications = relationship('UserNotification', cascade='all')
347 # notifications assigned to this user
347 # notifications assigned to this user
348 user_created_notifications = relationship('Notification', cascade='all')
348 user_created_notifications = relationship('Notification', cascade='all')
349 # comments created by this user
349 # comments created by this user
350 user_comments = relationship('ChangesetComment', cascade='all')
350 user_comments = relationship('ChangesetComment', cascade='all')
351 #extra emails for this user
351 #extra emails for this user
352 user_emails = relationship('UserEmailMap', cascade='all')
352 user_emails = relationship('UserEmailMap', cascade='all')
353
353
354 @hybrid_property
354 @hybrid_property
355 def email(self):
355 def email(self):
356 return self._email
356 return self._email
357
357
358 @email.setter
358 @email.setter
359 def email(self, val):
359 def email(self, val):
360 self._email = val.lower() if val else None
360 self._email = val.lower() if val else None
361
361
362 @property
362 @property
363 def firstname(self):
363 def firstname(self):
364 # alias for future
364 # alias for future
365 return self.name
365 return self.name
366
366
367 @property
367 @property
368 def emails(self):
368 def emails(self):
369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
370 return [self.email] + [x.email for x in other]
370 return [self.email] + [x.email for x in other]
371
371
372 @property
372 @property
373 def ip_addresses(self):
373 def ip_addresses(self):
374 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
374 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
375 return [x.ip_addr for x in ret]
375 return [x.ip_addr for x in ret]
376
376
377 @property
377 @property
378 def username_and_name(self):
378 def username_and_name(self):
379 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
379 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
380
380
381 @property
381 @property
382 def full_name(self):
382 def full_name(self):
383 return '%s %s' % (self.firstname, self.lastname)
383 return '%s %s' % (self.firstname, self.lastname)
384
384
385 @property
385 @property
386 def full_name_or_username(self):
386 def full_name_or_username(self):
387 return ('%s %s' % (self.firstname, self.lastname)
387 return ('%s %s' % (self.firstname, self.lastname)
388 if (self.firstname and self.lastname) else self.username)
388 if (self.firstname and self.lastname) else self.username)
389
389
390 @property
390 @property
391 def full_contact(self):
391 def full_contact(self):
392 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
392 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
393
393
394 @property
394 @property
395 def short_contact(self):
395 def short_contact(self):
396 return '%s %s' % (self.firstname, self.lastname)
396 return '%s %s' % (self.firstname, self.lastname)
397
397
398 @property
398 @property
399 def is_admin(self):
399 def is_admin(self):
400 return self.admin
400 return self.admin
401
401
402 @property
403 def AuthUser(self):
404 """
405 Returns instance of AuthUser for this user
406 """
407 from rhodecode.lib.auth import AuthUser
408 return AuthUser(user_id=self.user_id, api_key=self.api_key,
409 username=self.username)
410
402 def __unicode__(self):
411 def __unicode__(self):
403 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
412 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
404 self.user_id, self.username)
413 self.user_id, self.username)
405
414
406 @classmethod
415 @classmethod
407 def get_by_username(cls, username, case_insensitive=False, cache=False):
416 def get_by_username(cls, username, case_insensitive=False, cache=False):
408 if case_insensitive:
417 if case_insensitive:
409 q = cls.query().filter(cls.username.ilike(username))
418 q = cls.query().filter(cls.username.ilike(username))
410 else:
419 else:
411 q = cls.query().filter(cls.username == username)
420 q = cls.query().filter(cls.username == username)
412
421
413 if cache:
422 if cache:
414 q = q.options(FromCache(
423 q = q.options(FromCache(
415 "sql_cache_short",
424 "sql_cache_short",
416 "get_user_%s" % _hash_key(username)
425 "get_user_%s" % _hash_key(username)
417 )
426 )
418 )
427 )
419 return q.scalar()
428 return q.scalar()
420
429
421 @classmethod
430 @classmethod
422 def get_by_api_key(cls, api_key, cache=False):
431 def get_by_api_key(cls, api_key, cache=False):
423 q = cls.query().filter(cls.api_key == api_key)
432 q = cls.query().filter(cls.api_key == api_key)
424
433
425 if cache:
434 if cache:
426 q = q.options(FromCache("sql_cache_short",
435 q = q.options(FromCache("sql_cache_short",
427 "get_api_key_%s" % api_key))
436 "get_api_key_%s" % api_key))
428 return q.scalar()
437 return q.scalar()
429
438
430 @classmethod
439 @classmethod
431 def get_by_email(cls, email, case_insensitive=False, cache=False):
440 def get_by_email(cls, email, case_insensitive=False, cache=False):
432 if case_insensitive:
441 if case_insensitive:
433 q = cls.query().filter(cls.email.ilike(email))
442 q = cls.query().filter(cls.email.ilike(email))
434 else:
443 else:
435 q = cls.query().filter(cls.email == email)
444 q = cls.query().filter(cls.email == email)
436
445
437 if cache:
446 if cache:
438 q = q.options(FromCache("sql_cache_short",
447 q = q.options(FromCache("sql_cache_short",
439 "get_email_key_%s" % email))
448 "get_email_key_%s" % email))
440
449
441 ret = q.scalar()
450 ret = q.scalar()
442 if ret is None:
451 if ret is None:
443 q = UserEmailMap.query()
452 q = UserEmailMap.query()
444 # try fetching in alternate email map
453 # try fetching in alternate email map
445 if case_insensitive:
454 if case_insensitive:
446 q = q.filter(UserEmailMap.email.ilike(email))
455 q = q.filter(UserEmailMap.email.ilike(email))
447 else:
456 else:
448 q = q.filter(UserEmailMap.email == email)
457 q = q.filter(UserEmailMap.email == email)
449 q = q.options(joinedload(UserEmailMap.user))
458 q = q.options(joinedload(UserEmailMap.user))
450 if cache:
459 if cache:
451 q = q.options(FromCache("sql_cache_short",
460 q = q.options(FromCache("sql_cache_short",
452 "get_email_map_key_%s" % email))
461 "get_email_map_key_%s" % email))
453 ret = getattr(q.scalar(), 'user', None)
462 ret = getattr(q.scalar(), 'user', None)
454
463
455 return ret
464 return ret
456
465
457 @classmethod
466 @classmethod
458 def get_from_cs_author(cls, author):
467 def get_from_cs_author(cls, author):
459 """
468 """
460 Tries to get User objects out of commit author string
469 Tries to get User objects out of commit author string
461
470
462 :param author:
471 :param author:
463 """
472 """
464 from rhodecode.lib.helpers import email, author_name
473 from rhodecode.lib.helpers import email, author_name
465 # Valid email in the attribute passed, see if they're in the system
474 # Valid email in the attribute passed, see if they're in the system
466 _email = email(author)
475 _email = email(author)
467 if _email:
476 if _email:
468 user = cls.get_by_email(_email, case_insensitive=True)
477 user = cls.get_by_email(_email, case_insensitive=True)
469 if user:
478 if user:
470 return user
479 return user
471 # Maybe we can match by username?
480 # Maybe we can match by username?
472 _author = author_name(author)
481 _author = author_name(author)
473 user = cls.get_by_username(_author, case_insensitive=True)
482 user = cls.get_by_username(_author, case_insensitive=True)
474 if user:
483 if user:
475 return user
484 return user
476
485
477 def update_lastlogin(self):
486 def update_lastlogin(self):
478 """Update user lastlogin"""
487 """Update user lastlogin"""
479 self.last_login = datetime.datetime.now()
488 self.last_login = datetime.datetime.now()
480 Session().add(self)
489 Session().add(self)
481 log.debug('updated user %s lastlogin' % self.username)
490 log.debug('updated user %s lastlogin' % self.username)
482
491
483 def get_api_data(self):
492 def get_api_data(self):
484 """
493 """
485 Common function for generating user related data for API
494 Common function for generating user related data for API
486 """
495 """
487 user = self
496 user = self
488 data = dict(
497 data = dict(
489 user_id=user.user_id,
498 user_id=user.user_id,
490 username=user.username,
499 username=user.username,
491 firstname=user.name,
500 firstname=user.name,
492 lastname=user.lastname,
501 lastname=user.lastname,
493 email=user.email,
502 email=user.email,
494 emails=user.emails,
503 emails=user.emails,
495 api_key=user.api_key,
504 api_key=user.api_key,
496 active=user.active,
505 active=user.active,
497 admin=user.admin,
506 admin=user.admin,
498 ldap_dn=user.ldap_dn,
507 ldap_dn=user.ldap_dn,
499 last_login=user.last_login,
508 last_login=user.last_login,
500 ip_addresses=user.ip_addresses
509 ip_addresses=user.ip_addresses
501 )
510 )
502 return data
511 return data
503
512
504 def __json__(self):
513 def __json__(self):
505 data = dict(
514 data = dict(
506 full_name=self.full_name,
515 full_name=self.full_name,
507 full_name_or_username=self.full_name_or_username,
516 full_name_or_username=self.full_name_or_username,
508 short_contact=self.short_contact,
517 short_contact=self.short_contact,
509 full_contact=self.full_contact
518 full_contact=self.full_contact
510 )
519 )
511 data.update(self.get_api_data())
520 data.update(self.get_api_data())
512 return data
521 return data
513
522
514
523
515 class UserEmailMap(Base, BaseModel):
524 class UserEmailMap(Base, BaseModel):
516 __tablename__ = 'user_email_map'
525 __tablename__ = 'user_email_map'
517 __table_args__ = (
526 __table_args__ = (
518 Index('uem_email_idx', 'email'),
527 Index('uem_email_idx', 'email'),
519 UniqueConstraint('email'),
528 UniqueConstraint('email'),
520 {'extend_existing': True, 'mysql_engine': 'InnoDB',
529 {'extend_existing': True, 'mysql_engine': 'InnoDB',
521 'mysql_charset': 'utf8'}
530 'mysql_charset': 'utf8'}
522 )
531 )
523 __mapper_args__ = {}
532 __mapper_args__ = {}
524
533
525 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
534 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
526 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
535 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
527 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
536 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
528 user = relationship('User', lazy='joined')
537 user = relationship('User', lazy='joined')
529
538
530 @validates('_email')
539 @validates('_email')
531 def validate_email(self, key, email):
540 def validate_email(self, key, email):
532 # check if this email is not main one
541 # check if this email is not main one
533 main_email = Session().query(User).filter(User.email == email).scalar()
542 main_email = Session().query(User).filter(User.email == email).scalar()
534 if main_email is not None:
543 if main_email is not None:
535 raise AttributeError('email %s is present is user table' % email)
544 raise AttributeError('email %s is present is user table' % email)
536 return email
545 return email
537
546
538 @hybrid_property
547 @hybrid_property
539 def email(self):
548 def email(self):
540 return self._email
549 return self._email
541
550
542 @email.setter
551 @email.setter
543 def email(self, val):
552 def email(self, val):
544 self._email = val.lower() if val else None
553 self._email = val.lower() if val else None
545
554
546
555
547 class UserIpMap(Base, BaseModel):
556 class UserIpMap(Base, BaseModel):
548 __tablename__ = 'user_ip_map'
557 __tablename__ = 'user_ip_map'
549 __table_args__ = (
558 __table_args__ = (
550 UniqueConstraint('user_id', 'ip_addr'),
559 UniqueConstraint('user_id', 'ip_addr'),
551 {'extend_existing': True, 'mysql_engine': 'InnoDB',
560 {'extend_existing': True, 'mysql_engine': 'InnoDB',
552 'mysql_charset': 'utf8'}
561 'mysql_charset': 'utf8'}
553 )
562 )
554 __mapper_args__ = {}
563 __mapper_args__ = {}
555
564
556 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
565 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
557 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
566 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
558 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
567 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
559 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
568 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
560 user = relationship('User', lazy='joined')
569 user = relationship('User', lazy='joined')
561
570
562 @classmethod
571 @classmethod
563 def _get_ip_range(cls, ip_addr):
572 def _get_ip_range(cls, ip_addr):
564 from rhodecode.lib import ipaddr
573 from rhodecode.lib import ipaddr
565 net = ipaddr.IPNetwork(address=ip_addr)
574 net = ipaddr.IPNetwork(address=ip_addr)
566 return [str(net.network), str(net.broadcast)]
575 return [str(net.network), str(net.broadcast)]
567
576
568 def __json__(self):
577 def __json__(self):
569 return dict(
578 return dict(
570 ip_addr=self.ip_addr,
579 ip_addr=self.ip_addr,
571 ip_range=self._get_ip_range(self.ip_addr)
580 ip_range=self._get_ip_range(self.ip_addr)
572 )
581 )
573
582
574
583
575 class UserLog(Base, BaseModel):
584 class UserLog(Base, BaseModel):
576 __tablename__ = 'user_logs'
585 __tablename__ = 'user_logs'
577 __table_args__ = (
586 __table_args__ = (
578 {'extend_existing': True, 'mysql_engine': 'InnoDB',
587 {'extend_existing': True, 'mysql_engine': 'InnoDB',
579 'mysql_charset': 'utf8'},
588 'mysql_charset': 'utf8'},
580 )
589 )
581 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
590 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
582 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
591 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
583 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
592 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
584 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
593 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
585 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
594 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
586 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
595 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
587 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
588 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
597 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
589
598
590 @property
599 @property
591 def action_as_day(self):
600 def action_as_day(self):
592 return datetime.date(*self.action_date.timetuple()[:3])
601 return datetime.date(*self.action_date.timetuple()[:3])
593
602
594 user = relationship('User')
603 user = relationship('User')
595 repository = relationship('Repository', cascade='')
604 repository = relationship('Repository', cascade='')
596
605
597
606
598 class UsersGroup(Base, BaseModel):
607 class UsersGroup(Base, BaseModel):
599 __tablename__ = 'users_groups'
608 __tablename__ = 'users_groups'
600 __table_args__ = (
609 __table_args__ = (
601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
610 {'extend_existing': True, 'mysql_engine': 'InnoDB',
602 'mysql_charset': 'utf8'},
611 'mysql_charset': 'utf8'},
603 )
612 )
604
613
605 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
614 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
606 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
615 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
607 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
616 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
608 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
617 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
609
618
610 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
619 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
611 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
620 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
612 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
621 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
613
622
614 def __unicode__(self):
623 def __unicode__(self):
615 return u'<userGroup(%s)>' % (self.users_group_name)
624 return u'<userGroup(%s)>' % (self.users_group_name)
616
625
617 @classmethod
626 @classmethod
618 def get_by_group_name(cls, group_name, cache=False,
627 def get_by_group_name(cls, group_name, cache=False,
619 case_insensitive=False):
628 case_insensitive=False):
620 if case_insensitive:
629 if case_insensitive:
621 q = cls.query().filter(cls.users_group_name.ilike(group_name))
630 q = cls.query().filter(cls.users_group_name.ilike(group_name))
622 else:
631 else:
623 q = cls.query().filter(cls.users_group_name == group_name)
632 q = cls.query().filter(cls.users_group_name == group_name)
624 if cache:
633 if cache:
625 q = q.options(FromCache(
634 q = q.options(FromCache(
626 "sql_cache_short",
635 "sql_cache_short",
627 "get_user_%s" % _hash_key(group_name)
636 "get_user_%s" % _hash_key(group_name)
628 )
637 )
629 )
638 )
630 return q.scalar()
639 return q.scalar()
631
640
632 @classmethod
641 @classmethod
633 def get(cls, users_group_id, cache=False):
642 def get(cls, users_group_id, cache=False):
634 users_group = cls.query()
643 users_group = cls.query()
635 if cache:
644 if cache:
636 users_group = users_group.options(FromCache("sql_cache_short",
645 users_group = users_group.options(FromCache("sql_cache_short",
637 "get_users_group_%s" % users_group_id))
646 "get_users_group_%s" % users_group_id))
638 return users_group.get(users_group_id)
647 return users_group.get(users_group_id)
639
648
640 def get_api_data(self):
649 def get_api_data(self):
641 users_group = self
650 users_group = self
642
651
643 data = dict(
652 data = dict(
644 users_group_id=users_group.users_group_id,
653 users_group_id=users_group.users_group_id,
645 group_name=users_group.users_group_name,
654 group_name=users_group.users_group_name,
646 active=users_group.users_group_active,
655 active=users_group.users_group_active,
647 )
656 )
648
657
649 return data
658 return data
650
659
651
660
652 class UsersGroupMember(Base, BaseModel):
661 class UsersGroupMember(Base, BaseModel):
653 __tablename__ = 'users_groups_members'
662 __tablename__ = 'users_groups_members'
654 __table_args__ = (
663 __table_args__ = (
655 {'extend_existing': True, 'mysql_engine': 'InnoDB',
664 {'extend_existing': True, 'mysql_engine': 'InnoDB',
656 'mysql_charset': 'utf8'},
665 'mysql_charset': 'utf8'},
657 )
666 )
658
667
659 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
668 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
660 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
669 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
661 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
670 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
662
671
663 user = relationship('User', lazy='joined')
672 user = relationship('User', lazy='joined')
664 users_group = relationship('UsersGroup')
673 users_group = relationship('UsersGroup')
665
674
666 def __init__(self, gr_id='', u_id=''):
675 def __init__(self, gr_id='', u_id=''):
667 self.users_group_id = gr_id
676 self.users_group_id = gr_id
668 self.user_id = u_id
677 self.user_id = u_id
669
678
670
679
671 class Repository(Base, BaseModel):
680 class Repository(Base, BaseModel):
672 __tablename__ = 'repositories'
681 __tablename__ = 'repositories'
673 __table_args__ = (
682 __table_args__ = (
674 UniqueConstraint('repo_name'),
683 UniqueConstraint('repo_name'),
675 Index('r_repo_name_idx', 'repo_name'),
684 Index('r_repo_name_idx', 'repo_name'),
676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
677 'mysql_charset': 'utf8'},
686 'mysql_charset': 'utf8'},
678 )
687 )
679
688
680 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
689 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
681 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
690 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
682 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
691 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
683 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
692 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
684 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
693 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
685 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
694 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
686 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
695 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
687 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
696 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
688 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
697 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
689 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
698 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
690 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
699 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
691 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
700 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
692 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
701 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
693 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
702 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
694 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
703 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
695
704
696 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
705 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
697 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
706 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
698
707
699 user = relationship('User')
708 user = relationship('User')
700 fork = relationship('Repository', remote_side=repo_id)
709 fork = relationship('Repository', remote_side=repo_id)
701 group = relationship('RepoGroup')
710 group = relationship('RepoGroup')
702 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
711 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
703 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
712 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
704 stats = relationship('Statistics', cascade='all', uselist=False)
713 stats = relationship('Statistics', cascade='all', uselist=False)
705
714
706 followers = relationship('UserFollowing',
715 followers = relationship('UserFollowing',
707 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
716 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
708 cascade='all')
717 cascade='all')
709
718
710 logs = relationship('UserLog')
719 logs = relationship('UserLog')
711 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
720 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
712
721
713 pull_requests_org = relationship('PullRequest',
722 pull_requests_org = relationship('PullRequest',
714 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
723 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
715 cascade="all, delete, delete-orphan")
724 cascade="all, delete, delete-orphan")
716
725
717 pull_requests_other = relationship('PullRequest',
726 pull_requests_other = relationship('PullRequest',
718 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
727 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
719 cascade="all, delete, delete-orphan")
728 cascade="all, delete, delete-orphan")
720
729
721 def __unicode__(self):
730 def __unicode__(self):
722 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
731 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
723 self.repo_name)
732 self.repo_name)
724
733
725 @hybrid_property
734 @hybrid_property
726 def locked(self):
735 def locked(self):
727 # always should return [user_id, timelocked]
736 # always should return [user_id, timelocked]
728 if self._locked:
737 if self._locked:
729 _lock_info = self._locked.split(':')
738 _lock_info = self._locked.split(':')
730 return int(_lock_info[0]), _lock_info[1]
739 return int(_lock_info[0]), _lock_info[1]
731 return [None, None]
740 return [None, None]
732
741
733 @locked.setter
742 @locked.setter
734 def locked(self, val):
743 def locked(self, val):
735 if val and isinstance(val, (list, tuple)):
744 if val and isinstance(val, (list, tuple)):
736 self._locked = ':'.join(map(str, val))
745 self._locked = ':'.join(map(str, val))
737 else:
746 else:
738 self._locked = None
747 self._locked = None
739
748
740 @hybrid_property
749 @hybrid_property
741 def changeset_cache(self):
750 def changeset_cache(self):
742 from rhodecode.lib.vcs.backends.base import EmptyChangeset
751 from rhodecode.lib.vcs.backends.base import EmptyChangeset
743 dummy = EmptyChangeset().__json__()
752 dummy = EmptyChangeset().__json__()
744 if not self._changeset_cache:
753 if not self._changeset_cache:
745 return dummy
754 return dummy
746 try:
755 try:
747 return json.loads(self._changeset_cache)
756 return json.loads(self._changeset_cache)
748 except TypeError:
757 except TypeError:
749 return dummy
758 return dummy
750
759
751 @changeset_cache.setter
760 @changeset_cache.setter
752 def changeset_cache(self, val):
761 def changeset_cache(self, val):
753 try:
762 try:
754 self._changeset_cache = json.dumps(val)
763 self._changeset_cache = json.dumps(val)
755 except:
764 except:
756 log.error(traceback.format_exc())
765 log.error(traceback.format_exc())
757
766
758 @classmethod
767 @classmethod
759 def url_sep(cls):
768 def url_sep(cls):
760 return URL_SEP
769 return URL_SEP
761
770
762 @classmethod
771 @classmethod
763 def normalize_repo_name(cls, repo_name):
772 def normalize_repo_name(cls, repo_name):
764 """
773 """
765 Normalizes os specific repo_name to the format internally stored inside
774 Normalizes os specific repo_name to the format internally stored inside
766 dabatabase using URL_SEP
775 dabatabase using URL_SEP
767
776
768 :param cls:
777 :param cls:
769 :param repo_name:
778 :param repo_name:
770 """
779 """
771 return cls.url_sep().join(repo_name.split(os.sep))
780 return cls.url_sep().join(repo_name.split(os.sep))
772
781
773 @classmethod
782 @classmethod
774 def get_by_repo_name(cls, repo_name):
783 def get_by_repo_name(cls, repo_name):
775 q = Session().query(cls).filter(cls.repo_name == repo_name)
784 q = Session().query(cls).filter(cls.repo_name == repo_name)
776 q = q.options(joinedload(Repository.fork))\
785 q = q.options(joinedload(Repository.fork))\
777 .options(joinedload(Repository.user))\
786 .options(joinedload(Repository.user))\
778 .options(joinedload(Repository.group))
787 .options(joinedload(Repository.group))
779 return q.scalar()
788 return q.scalar()
780
789
781 @classmethod
790 @classmethod
782 def get_by_full_path(cls, repo_full_path):
791 def get_by_full_path(cls, repo_full_path):
783 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
792 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
784 repo_name = cls.normalize_repo_name(repo_name)
793 repo_name = cls.normalize_repo_name(repo_name)
785 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
794 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
786
795
787 @classmethod
796 @classmethod
788 def get_repo_forks(cls, repo_id):
797 def get_repo_forks(cls, repo_id):
789 return cls.query().filter(Repository.fork_id == repo_id)
798 return cls.query().filter(Repository.fork_id == repo_id)
790
799
791 @classmethod
800 @classmethod
792 def base_path(cls):
801 def base_path(cls):
793 """
802 """
794 Returns base path when all repos are stored
803 Returns base path when all repos are stored
795
804
796 :param cls:
805 :param cls:
797 """
806 """
798 q = Session().query(RhodeCodeUi)\
807 q = Session().query(RhodeCodeUi)\
799 .filter(RhodeCodeUi.ui_key == cls.url_sep())
808 .filter(RhodeCodeUi.ui_key == cls.url_sep())
800 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
809 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
801 return q.one().ui_value
810 return q.one().ui_value
802
811
803 @property
812 @property
804 def forks(self):
813 def forks(self):
805 """
814 """
806 Return forks of this repo
815 Return forks of this repo
807 """
816 """
808 return Repository.get_repo_forks(self.repo_id)
817 return Repository.get_repo_forks(self.repo_id)
809
818
810 @property
819 @property
811 def parent(self):
820 def parent(self):
812 """
821 """
813 Returns fork parent
822 Returns fork parent
814 """
823 """
815 return self.fork
824 return self.fork
816
825
817 @property
826 @property
818 def just_name(self):
827 def just_name(self):
819 return self.repo_name.split(Repository.url_sep())[-1]
828 return self.repo_name.split(Repository.url_sep())[-1]
820
829
821 @property
830 @property
822 def groups_with_parents(self):
831 def groups_with_parents(self):
823 groups = []
832 groups = []
824 if self.group is None:
833 if self.group is None:
825 return groups
834 return groups
826
835
827 cur_gr = self.group
836 cur_gr = self.group
828 groups.insert(0, cur_gr)
837 groups.insert(0, cur_gr)
829 while 1:
838 while 1:
830 gr = getattr(cur_gr, 'parent_group', None)
839 gr = getattr(cur_gr, 'parent_group', None)
831 cur_gr = cur_gr.parent_group
840 cur_gr = cur_gr.parent_group
832 if gr is None:
841 if gr is None:
833 break
842 break
834 groups.insert(0, gr)
843 groups.insert(0, gr)
835
844
836 return groups
845 return groups
837
846
838 @property
847 @property
839 def groups_and_repo(self):
848 def groups_and_repo(self):
840 return self.groups_with_parents, self.just_name
849 return self.groups_with_parents, self.just_name
841
850
842 @LazyProperty
851 @LazyProperty
843 def repo_path(self):
852 def repo_path(self):
844 """
853 """
845 Returns base full path for that repository means where it actually
854 Returns base full path for that repository means where it actually
846 exists on a filesystem
855 exists on a filesystem
847 """
856 """
848 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
857 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
849 Repository.url_sep())
858 Repository.url_sep())
850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
859 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
851 return q.one().ui_value
860 return q.one().ui_value
852
861
853 @property
862 @property
854 def repo_full_path(self):
863 def repo_full_path(self):
855 p = [self.repo_path]
864 p = [self.repo_path]
856 # we need to split the name by / since this is how we store the
865 # we need to split the name by / since this is how we store the
857 # names in the database, but that eventually needs to be converted
866 # names in the database, but that eventually needs to be converted
858 # into a valid system path
867 # into a valid system path
859 p += self.repo_name.split(Repository.url_sep())
868 p += self.repo_name.split(Repository.url_sep())
860 return os.path.join(*p)
869 return os.path.join(*p)
861
870
862 @property
871 @property
863 def cache_keys(self):
872 def cache_keys(self):
864 """
873 """
865 Returns associated cache keys for that repo
874 Returns associated cache keys for that repo
866 """
875 """
867 return CacheInvalidation.query()\
876 return CacheInvalidation.query()\
868 .filter(CacheInvalidation.cache_args == self.repo_name)\
877 .filter(CacheInvalidation.cache_args == self.repo_name)\
869 .order_by(CacheInvalidation.cache_key)\
878 .order_by(CacheInvalidation.cache_key)\
870 .all()
879 .all()
871
880
872 def get_new_name(self, repo_name):
881 def get_new_name(self, repo_name):
873 """
882 """
874 returns new full repository name based on assigned group and new new
883 returns new full repository name based on assigned group and new new
875
884
876 :param group_name:
885 :param group_name:
877 """
886 """
878 path_prefix = self.group.full_path_splitted if self.group else []
887 path_prefix = self.group.full_path_splitted if self.group else []
879 return Repository.url_sep().join(path_prefix + [repo_name])
888 return Repository.url_sep().join(path_prefix + [repo_name])
880
889
881 @property
890 @property
882 def _ui(self):
891 def _ui(self):
883 """
892 """
884 Creates an db based ui object for this repository
893 Creates an db based ui object for this repository
885 """
894 """
886 from rhodecode.lib.utils import make_ui
895 from rhodecode.lib.utils import make_ui
887 return make_ui('db', clear_session=False)
896 return make_ui('db', clear_session=False)
888
897
889 @classmethod
898 @classmethod
890 def inject_ui(cls, repo, extras={}):
899 def inject_ui(cls, repo, extras={}):
891 from rhodecode.lib.vcs.backends.hg import MercurialRepository
900 from rhodecode.lib.vcs.backends.hg import MercurialRepository
892 from rhodecode.lib.vcs.backends.git import GitRepository
901 from rhodecode.lib.vcs.backends.git import GitRepository
893 required = (MercurialRepository, GitRepository)
902 required = (MercurialRepository, GitRepository)
894 if not isinstance(repo, required):
903 if not isinstance(repo, required):
895 raise Exception('repo must be instance of %s' % required)
904 raise Exception('repo must be instance of %s' % required)
896
905
897 # inject ui extra param to log this action via push logger
906 # inject ui extra param to log this action via push logger
898 for k, v in extras.items():
907 for k, v in extras.items():
899 repo._repo.ui.setconfig('rhodecode_extras', k, v)
908 repo._repo.ui.setconfig('rhodecode_extras', k, v)
900
909
901 @classmethod
910 @classmethod
902 def is_valid(cls, repo_name):
911 def is_valid(cls, repo_name):
903 """
912 """
904 returns True if given repo name is a valid filesystem repository
913 returns True if given repo name is a valid filesystem repository
905
914
906 :param cls:
915 :param cls:
907 :param repo_name:
916 :param repo_name:
908 """
917 """
909 from rhodecode.lib.utils import is_valid_repo
918 from rhodecode.lib.utils import is_valid_repo
910
919
911 return is_valid_repo(repo_name, cls.base_path())
920 return is_valid_repo(repo_name, cls.base_path())
912
921
913 def get_api_data(self):
922 def get_api_data(self):
914 """
923 """
915 Common function for generating repo api data
924 Common function for generating repo api data
916
925
917 """
926 """
918 repo = self
927 repo = self
919 data = dict(
928 data = dict(
920 repo_id=repo.repo_id,
929 repo_id=repo.repo_id,
921 repo_name=repo.repo_name,
930 repo_name=repo.repo_name,
922 repo_type=repo.repo_type,
931 repo_type=repo.repo_type,
923 clone_uri=repo.clone_uri,
932 clone_uri=repo.clone_uri,
924 private=repo.private,
933 private=repo.private,
925 created_on=repo.created_on,
934 created_on=repo.created_on,
926 description=repo.description,
935 description=repo.description,
927 landing_rev=repo.landing_rev,
936 landing_rev=repo.landing_rev,
928 owner=repo.user.username,
937 owner=repo.user.username,
929 fork_of=repo.fork.repo_name if repo.fork else None,
938 fork_of=repo.fork.repo_name if repo.fork else None,
930 enable_statistics=repo.enable_statistics,
939 enable_statistics=repo.enable_statistics,
931 enable_locking=repo.enable_locking,
940 enable_locking=repo.enable_locking,
932 enable_downloads=repo.enable_downloads,
941 enable_downloads=repo.enable_downloads,
933 last_changeset=repo.changeset_cache
942 last_changeset=repo.changeset_cache
934 )
943 )
935
944
936 return data
945 return data
937
946
938 @classmethod
947 @classmethod
939 def lock(cls, repo, user_id):
948 def lock(cls, repo, user_id):
940 repo.locked = [user_id, time.time()]
949 repo.locked = [user_id, time.time()]
941 Session().add(repo)
950 Session().add(repo)
942 Session().commit()
951 Session().commit()
943
952
944 @classmethod
953 @classmethod
945 def unlock(cls, repo):
954 def unlock(cls, repo):
946 repo.locked = None
955 repo.locked = None
947 Session().add(repo)
956 Session().add(repo)
948 Session().commit()
957 Session().commit()
949
958
950 @property
959 @property
951 def last_db_change(self):
960 def last_db_change(self):
952 return self.updated_on
961 return self.updated_on
953
962
954 def clone_url(self, **override):
963 def clone_url(self, **override):
955 from pylons import url
964 from pylons import url
956 from urlparse import urlparse
965 from urlparse import urlparse
957 import urllib
966 import urllib
958 parsed_url = urlparse(url('home', qualified=True))
967 parsed_url = urlparse(url('home', qualified=True))
959 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
968 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
960 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
969 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
961 args = {
970 args = {
962 'user': '',
971 'user': '',
963 'pass': '',
972 'pass': '',
964 'scheme': parsed_url.scheme,
973 'scheme': parsed_url.scheme,
965 'netloc': parsed_url.netloc,
974 'netloc': parsed_url.netloc,
966 'prefix': decoded_path,
975 'prefix': decoded_path,
967 'path': self.repo_name
976 'path': self.repo_name
968 }
977 }
969
978
970 args.update(override)
979 args.update(override)
971 return default_clone_uri % args
980 return default_clone_uri % args
972
981
973 #==========================================================================
982 #==========================================================================
974 # SCM PROPERTIES
983 # SCM PROPERTIES
975 #==========================================================================
984 #==========================================================================
976
985
977 def get_changeset(self, rev=None):
986 def get_changeset(self, rev=None):
978 return get_changeset_safe(self.scm_instance, rev)
987 return get_changeset_safe(self.scm_instance, rev)
979
988
980 def get_landing_changeset(self):
989 def get_landing_changeset(self):
981 """
990 """
982 Returns landing changeset, or if that doesn't exist returns the tip
991 Returns landing changeset, or if that doesn't exist returns the tip
983 """
992 """
984 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
993 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
985 return cs
994 return cs
986
995
987 def update_changeset_cache(self, cs_cache=None):
996 def update_changeset_cache(self, cs_cache=None):
988 """
997 """
989 Update cache of last changeset for repository, keys should be::
998 Update cache of last changeset for repository, keys should be::
990
999
991 short_id
1000 short_id
992 raw_id
1001 raw_id
993 revision
1002 revision
994 message
1003 message
995 date
1004 date
996 author
1005 author
997
1006
998 :param cs_cache:
1007 :param cs_cache:
999 """
1008 """
1000 from rhodecode.lib.vcs.backends.base import BaseChangeset
1009 from rhodecode.lib.vcs.backends.base import BaseChangeset
1001 if cs_cache is None:
1010 if cs_cache is None:
1002 cs_cache = self.get_changeset()
1011 cs_cache = self.get_changeset()
1003 if isinstance(cs_cache, BaseChangeset):
1012 if isinstance(cs_cache, BaseChangeset):
1004 cs_cache = cs_cache.__json__()
1013 cs_cache = cs_cache.__json__()
1005
1014
1006 if (cs_cache != self.changeset_cache
1015 if (cs_cache != self.changeset_cache
1007 or not self.last_change
1016 or not self.last_change
1008 or not self.changeset_cache):
1017 or not self.changeset_cache):
1009 _default = datetime.datetime.fromtimestamp(0)
1018 _default = datetime.datetime.fromtimestamp(0)
1010 last_change = cs_cache.get('date') or self.last_change or _default
1019 last_change = cs_cache.get('date') or self.last_change or _default
1011 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1020 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1012 self.updated_on = last_change
1021 self.updated_on = last_change
1013 self.changeset_cache = cs_cache
1022 self.changeset_cache = cs_cache
1014 Session().add(self)
1023 Session().add(self)
1015 Session().commit()
1024 Session().commit()
1016 else:
1025 else:
1017 log.debug('Skipping repo:%s already with latest changes' % self)
1026 log.debug('Skipping repo:%s already with latest changes' % self)
1018
1027
1019 @property
1028 @property
1020 def tip(self):
1029 def tip(self):
1021 return self.get_changeset('tip')
1030 return self.get_changeset('tip')
1022
1031
1023 @property
1032 @property
1024 def author(self):
1033 def author(self):
1025 return self.tip.author
1034 return self.tip.author
1026
1035
1027 @property
1036 @property
1028 def last_change(self):
1037 def last_change(self):
1029 return self.scm_instance.last_change
1038 return self.scm_instance.last_change
1030
1039
1031 def get_comments(self, revisions=None):
1040 def get_comments(self, revisions=None):
1032 """
1041 """
1033 Returns comments for this repository grouped by revisions
1042 Returns comments for this repository grouped by revisions
1034
1043
1035 :param revisions: filter query by revisions only
1044 :param revisions: filter query by revisions only
1036 """
1045 """
1037 cmts = ChangesetComment.query()\
1046 cmts = ChangesetComment.query()\
1038 .filter(ChangesetComment.repo == self)
1047 .filter(ChangesetComment.repo == self)
1039 if revisions:
1048 if revisions:
1040 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1049 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1041 grouped = defaultdict(list)
1050 grouped = defaultdict(list)
1042 for cmt in cmts.all():
1051 for cmt in cmts.all():
1043 grouped[cmt.revision].append(cmt)
1052 grouped[cmt.revision].append(cmt)
1044 return grouped
1053 return grouped
1045
1054
1046 def statuses(self, revisions=None):
1055 def statuses(self, revisions=None):
1047 """
1056 """
1048 Returns statuses for this repository
1057 Returns statuses for this repository
1049
1058
1050 :param revisions: list of revisions to get statuses for
1059 :param revisions: list of revisions to get statuses for
1051 :type revisions: list
1060 :type revisions: list
1052 """
1061 """
1053
1062
1054 statuses = ChangesetStatus.query()\
1063 statuses = ChangesetStatus.query()\
1055 .filter(ChangesetStatus.repo == self)\
1064 .filter(ChangesetStatus.repo == self)\
1056 .filter(ChangesetStatus.version == 0)
1065 .filter(ChangesetStatus.version == 0)
1057 if revisions:
1066 if revisions:
1058 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1067 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1059 grouped = {}
1068 grouped = {}
1060
1069
1061 #maybe we have open new pullrequest without a status ?
1070 #maybe we have open new pullrequest without a status ?
1062 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1071 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1063 status_lbl = ChangesetStatus.get_status_lbl(stat)
1072 status_lbl = ChangesetStatus.get_status_lbl(stat)
1064 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1073 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1065 for rev in pr.revisions:
1074 for rev in pr.revisions:
1066 pr_id = pr.pull_request_id
1075 pr_id = pr.pull_request_id
1067 pr_repo = pr.other_repo.repo_name
1076 pr_repo = pr.other_repo.repo_name
1068 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1077 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1069
1078
1070 for stat in statuses.all():
1079 for stat in statuses.all():
1071 pr_id = pr_repo = None
1080 pr_id = pr_repo = None
1072 if stat.pull_request:
1081 if stat.pull_request:
1073 pr_id = stat.pull_request.pull_request_id
1082 pr_id = stat.pull_request.pull_request_id
1074 pr_repo = stat.pull_request.other_repo.repo_name
1083 pr_repo = stat.pull_request.other_repo.repo_name
1075 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1084 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1076 pr_id, pr_repo]
1085 pr_id, pr_repo]
1077 return grouped
1086 return grouped
1078
1087
1079 #==========================================================================
1088 #==========================================================================
1080 # SCM CACHE INSTANCE
1089 # SCM CACHE INSTANCE
1081 #==========================================================================
1090 #==========================================================================
1082
1091
1083 @property
1092 @property
1084 def invalidate(self):
1093 def invalidate(self):
1085 return CacheInvalidation.invalidate(self.repo_name)
1094 return CacheInvalidation.invalidate(self.repo_name)
1086
1095
1087 def set_invalidate(self):
1096 def set_invalidate(self):
1088 """
1097 """
1089 set a cache for invalidation for this instance
1098 set a cache for invalidation for this instance
1090 """
1099 """
1091 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1100 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1092
1101
1093 @LazyProperty
1102 @LazyProperty
1094 def scm_instance_no_cache(self):
1103 def scm_instance_no_cache(self):
1095 return self.__get_instance()
1104 return self.__get_instance()
1096
1105
1097 @LazyProperty
1106 @LazyProperty
1098 def scm_instance(self):
1107 def scm_instance(self):
1099 import rhodecode
1108 import rhodecode
1100 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1109 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1101 if full_cache:
1110 if full_cache:
1102 return self.scm_instance_cached()
1111 return self.scm_instance_cached()
1103 return self.__get_instance()
1112 return self.__get_instance()
1104
1113
1105 def scm_instance_cached(self, cache_map=None):
1114 def scm_instance_cached(self, cache_map=None):
1106 @cache_region('long_term')
1115 @cache_region('long_term')
1107 def _c(repo_name):
1116 def _c(repo_name):
1108 return self.__get_instance()
1117 return self.__get_instance()
1109 rn = self.repo_name
1118 rn = self.repo_name
1110 log.debug('Getting cached instance of repo')
1119 log.debug('Getting cached instance of repo')
1111
1120
1112 if cache_map:
1121 if cache_map:
1113 # get using prefilled cache_map
1122 # get using prefilled cache_map
1114 invalidate_repo = cache_map[self.repo_name]
1123 invalidate_repo = cache_map[self.repo_name]
1115 if invalidate_repo:
1124 if invalidate_repo:
1116 invalidate_repo = (None if invalidate_repo.cache_active
1125 invalidate_repo = (None if invalidate_repo.cache_active
1117 else invalidate_repo)
1126 else invalidate_repo)
1118 else:
1127 else:
1119 # get from invalidate
1128 # get from invalidate
1120 invalidate_repo = self.invalidate
1129 invalidate_repo = self.invalidate
1121
1130
1122 if invalidate_repo is not None:
1131 if invalidate_repo is not None:
1123 region_invalidate(_c, None, rn)
1132 region_invalidate(_c, None, rn)
1124 # update our cache
1133 # update our cache
1125 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1134 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1126 return _c(rn)
1135 return _c(rn)
1127
1136
1128 def __get_instance(self):
1137 def __get_instance(self):
1129 repo_full_path = self.repo_full_path
1138 repo_full_path = self.repo_full_path
1130 try:
1139 try:
1131 alias = get_scm(repo_full_path)[0]
1140 alias = get_scm(repo_full_path)[0]
1132 log.debug('Creating instance of %s repository' % alias)
1141 log.debug('Creating instance of %s repository' % alias)
1133 backend = get_backend(alias)
1142 backend = get_backend(alias)
1134 except VCSError:
1143 except VCSError:
1135 log.error(traceback.format_exc())
1144 log.error(traceback.format_exc())
1136 log.error('Perhaps this repository is in db and not in '
1145 log.error('Perhaps this repository is in db and not in '
1137 'filesystem run rescan repositories with '
1146 'filesystem run rescan repositories with '
1138 '"destroy old data " option from admin panel')
1147 '"destroy old data " option from admin panel')
1139 return
1148 return
1140
1149
1141 if alias == 'hg':
1150 if alias == 'hg':
1142
1151
1143 repo = backend(safe_str(repo_full_path), create=False,
1152 repo = backend(safe_str(repo_full_path), create=False,
1144 baseui=self._ui)
1153 baseui=self._ui)
1145 # skip hidden web repository
1154 # skip hidden web repository
1146 if repo._get_hidden():
1155 if repo._get_hidden():
1147 return
1156 return
1148 else:
1157 else:
1149 repo = backend(repo_full_path, create=False)
1158 repo = backend(repo_full_path, create=False)
1150
1159
1151 return repo
1160 return repo
1152
1161
1153
1162
1154 class RepoGroup(Base, BaseModel):
1163 class RepoGroup(Base, BaseModel):
1155 __tablename__ = 'groups'
1164 __tablename__ = 'groups'
1156 __table_args__ = (
1165 __table_args__ = (
1157 UniqueConstraint('group_name', 'group_parent_id'),
1166 UniqueConstraint('group_name', 'group_parent_id'),
1158 CheckConstraint('group_id != group_parent_id'),
1167 CheckConstraint('group_id != group_parent_id'),
1159 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1168 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1160 'mysql_charset': 'utf8'},
1169 'mysql_charset': 'utf8'},
1161 )
1170 )
1162 __mapper_args__ = {'order_by': 'group_name'}
1171 __mapper_args__ = {'order_by': 'group_name'}
1163
1172
1164 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1173 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1165 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1174 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1166 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1175 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1167 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1176 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1168 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1177 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1169
1178
1170 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1179 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1171 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1180 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1172
1181
1173 parent_group = relationship('RepoGroup', remote_side=group_id)
1182 parent_group = relationship('RepoGroup', remote_side=group_id)
1174
1183
1175 def __init__(self, group_name='', parent_group=None):
1184 def __init__(self, group_name='', parent_group=None):
1176 self.group_name = group_name
1185 self.group_name = group_name
1177 self.parent_group = parent_group
1186 self.parent_group = parent_group
1178
1187
1179 def __unicode__(self):
1188 def __unicode__(self):
1180 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1189 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1181 self.group_name)
1190 self.group_name)
1182
1191
1183 @classmethod
1192 @classmethod
1184 def groups_choices(cls, check_perms=False):
1193 def groups_choices(cls, check_perms=False):
1185 from webhelpers.html import literal as _literal
1194 from webhelpers.html import literal as _literal
1186 from rhodecode.model.scm import ScmModel
1195 from rhodecode.model.scm import ScmModel
1187 groups = cls.query().all()
1196 groups = cls.query().all()
1188 if check_perms:
1197 if check_perms:
1189 #filter group user have access to, it's done
1198 #filter group user have access to, it's done
1190 #magically inside ScmModel based on current user
1199 #magically inside ScmModel based on current user
1191 groups = ScmModel().get_repos_groups(groups)
1200 groups = ScmModel().get_repos_groups(groups)
1192 repo_groups = [('', '')]
1201 repo_groups = [('', '')]
1193 sep = ' &raquo; '
1202 sep = ' &raquo; '
1194 _name = lambda k: _literal(sep.join(k))
1203 _name = lambda k: _literal(sep.join(k))
1195
1204
1196 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1205 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1197 for x in groups])
1206 for x in groups])
1198
1207
1199 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1208 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1200 return repo_groups
1209 return repo_groups
1201
1210
1202 @classmethod
1211 @classmethod
1203 def url_sep(cls):
1212 def url_sep(cls):
1204 return URL_SEP
1213 return URL_SEP
1205
1214
1206 @classmethod
1215 @classmethod
1207 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1216 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1208 if case_insensitive:
1217 if case_insensitive:
1209 gr = cls.query()\
1218 gr = cls.query()\
1210 .filter(cls.group_name.ilike(group_name))
1219 .filter(cls.group_name.ilike(group_name))
1211 else:
1220 else:
1212 gr = cls.query()\
1221 gr = cls.query()\
1213 .filter(cls.group_name == group_name)
1222 .filter(cls.group_name == group_name)
1214 if cache:
1223 if cache:
1215 gr = gr.options(FromCache(
1224 gr = gr.options(FromCache(
1216 "sql_cache_short",
1225 "sql_cache_short",
1217 "get_group_%s" % _hash_key(group_name)
1226 "get_group_%s" % _hash_key(group_name)
1218 )
1227 )
1219 )
1228 )
1220 return gr.scalar()
1229 return gr.scalar()
1221
1230
1222 @property
1231 @property
1223 def parents(self):
1232 def parents(self):
1224 parents_recursion_limit = 5
1233 parents_recursion_limit = 5
1225 groups = []
1234 groups = []
1226 if self.parent_group is None:
1235 if self.parent_group is None:
1227 return groups
1236 return groups
1228 cur_gr = self.parent_group
1237 cur_gr = self.parent_group
1229 groups.insert(0, cur_gr)
1238 groups.insert(0, cur_gr)
1230 cnt = 0
1239 cnt = 0
1231 while 1:
1240 while 1:
1232 cnt += 1
1241 cnt += 1
1233 gr = getattr(cur_gr, 'parent_group', None)
1242 gr = getattr(cur_gr, 'parent_group', None)
1234 cur_gr = cur_gr.parent_group
1243 cur_gr = cur_gr.parent_group
1235 if gr is None:
1244 if gr is None:
1236 break
1245 break
1237 if cnt == parents_recursion_limit:
1246 if cnt == parents_recursion_limit:
1238 # this will prevent accidental infinit loops
1247 # this will prevent accidental infinit loops
1239 log.error('group nested more than %s' %
1248 log.error('group nested more than %s' %
1240 parents_recursion_limit)
1249 parents_recursion_limit)
1241 break
1250 break
1242
1251
1243 groups.insert(0, gr)
1252 groups.insert(0, gr)
1244 return groups
1253 return groups
1245
1254
1246 @property
1255 @property
1247 def children(self):
1256 def children(self):
1248 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1257 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1249
1258
1250 @property
1259 @property
1251 def name(self):
1260 def name(self):
1252 return self.group_name.split(RepoGroup.url_sep())[-1]
1261 return self.group_name.split(RepoGroup.url_sep())[-1]
1253
1262
1254 @property
1263 @property
1255 def full_path(self):
1264 def full_path(self):
1256 return self.group_name
1265 return self.group_name
1257
1266
1258 @property
1267 @property
1259 def full_path_splitted(self):
1268 def full_path_splitted(self):
1260 return self.group_name.split(RepoGroup.url_sep())
1269 return self.group_name.split(RepoGroup.url_sep())
1261
1270
1262 @property
1271 @property
1263 def repositories(self):
1272 def repositories(self):
1264 return Repository.query()\
1273 return Repository.query()\
1265 .filter(Repository.group == self)\
1274 .filter(Repository.group == self)\
1266 .order_by(Repository.repo_name)
1275 .order_by(Repository.repo_name)
1267
1276
1268 @property
1277 @property
1269 def repositories_recursive_count(self):
1278 def repositories_recursive_count(self):
1270 cnt = self.repositories.count()
1279 cnt = self.repositories.count()
1271
1280
1272 def children_count(group):
1281 def children_count(group):
1273 cnt = 0
1282 cnt = 0
1274 for child in group.children:
1283 for child in group.children:
1275 cnt += child.repositories.count()
1284 cnt += child.repositories.count()
1276 cnt += children_count(child)
1285 cnt += children_count(child)
1277 return cnt
1286 return cnt
1278
1287
1279 return cnt + children_count(self)
1288 return cnt + children_count(self)
1280
1289
1281 def recursive_groups_and_repos(self):
1290 def recursive_groups_and_repos(self):
1282 """
1291 """
1283 Recursive return all groups, with repositories in those groups
1292 Recursive return all groups, with repositories in those groups
1284 """
1293 """
1285 all_ = []
1294 all_ = []
1286
1295
1287 def _get_members(root_gr):
1296 def _get_members(root_gr):
1288 for r in root_gr.repositories:
1297 for r in root_gr.repositories:
1289 all_.append(r)
1298 all_.append(r)
1290 childs = root_gr.children.all()
1299 childs = root_gr.children.all()
1291 if childs:
1300 if childs:
1292 for gr in childs:
1301 for gr in childs:
1293 all_.append(gr)
1302 all_.append(gr)
1294 _get_members(gr)
1303 _get_members(gr)
1295
1304
1296 _get_members(self)
1305 _get_members(self)
1297 return [self] + all_
1306 return [self] + all_
1298
1307
1299 def get_new_name(self, group_name):
1308 def get_new_name(self, group_name):
1300 """
1309 """
1301 returns new full group name based on parent and new name
1310 returns new full group name based on parent and new name
1302
1311
1303 :param group_name:
1312 :param group_name:
1304 """
1313 """
1305 path_prefix = (self.parent_group.full_path_splitted if
1314 path_prefix = (self.parent_group.full_path_splitted if
1306 self.parent_group else [])
1315 self.parent_group else [])
1307 return RepoGroup.url_sep().join(path_prefix + [group_name])
1316 return RepoGroup.url_sep().join(path_prefix + [group_name])
1308
1317
1309
1318
1310 class Permission(Base, BaseModel):
1319 class Permission(Base, BaseModel):
1311 __tablename__ = 'permissions'
1320 __tablename__ = 'permissions'
1312 __table_args__ = (
1321 __table_args__ = (
1313 Index('p_perm_name_idx', 'permission_name'),
1322 Index('p_perm_name_idx', 'permission_name'),
1314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1323 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1315 'mysql_charset': 'utf8'},
1324 'mysql_charset': 'utf8'},
1316 )
1325 )
1317 PERMS = [
1326 PERMS = [
1318 ('repository.none', _('Repository no access')),
1327 ('repository.none', _('Repository no access')),
1319 ('repository.read', _('Repository read access')),
1328 ('repository.read', _('Repository read access')),
1320 ('repository.write', _('Repository write access')),
1329 ('repository.write', _('Repository write access')),
1321 ('repository.admin', _('Repository admin access')),
1330 ('repository.admin', _('Repository admin access')),
1322
1331
1323 ('group.none', _('Repositories Group no access')),
1332 ('group.none', _('Repositories Group no access')),
1324 ('group.read', _('Repositories Group read access')),
1333 ('group.read', _('Repositories Group read access')),
1325 ('group.write', _('Repositories Group write access')),
1334 ('group.write', _('Repositories Group write access')),
1326 ('group.admin', _('Repositories Group admin access')),
1335 ('group.admin', _('Repositories Group admin access')),
1327
1336
1328 ('hg.admin', _('RhodeCode Administrator')),
1337 ('hg.admin', _('RhodeCode Administrator')),
1329 ('hg.create.none', _('Repository creation disabled')),
1338 ('hg.create.none', _('Repository creation disabled')),
1330 ('hg.create.repository', _('Repository creation enabled')),
1339 ('hg.create.repository', _('Repository creation enabled')),
1331 ('hg.fork.none', _('Repository forking disabled')),
1340 ('hg.fork.none', _('Repository forking disabled')),
1332 ('hg.fork.repository', _('Repository forking enabled')),
1341 ('hg.fork.repository', _('Repository forking enabled')),
1333 ('hg.register.none', _('Register disabled')),
1342 ('hg.register.none', _('Register disabled')),
1334 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1343 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1335 'with manual activation')),
1344 'with manual activation')),
1336
1345
1337 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1346 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1338 'with auto activation')),
1347 'with auto activation')),
1339 ]
1348 ]
1340
1349
1341 # defines which permissions are more important higher the more important
1350 # defines which permissions are more important higher the more important
1342 PERM_WEIGHTS = {
1351 PERM_WEIGHTS = {
1343 'repository.none': 0,
1352 'repository.none': 0,
1344 'repository.read': 1,
1353 'repository.read': 1,
1345 'repository.write': 3,
1354 'repository.write': 3,
1346 'repository.admin': 4,
1355 'repository.admin': 4,
1347
1356
1348 'group.none': 0,
1357 'group.none': 0,
1349 'group.read': 1,
1358 'group.read': 1,
1350 'group.write': 3,
1359 'group.write': 3,
1351 'group.admin': 4,
1360 'group.admin': 4,
1352
1361
1353 'hg.fork.none': 0,
1362 'hg.fork.none': 0,
1354 'hg.fork.repository': 1,
1363 'hg.fork.repository': 1,
1355 'hg.create.none': 0,
1364 'hg.create.none': 0,
1356 'hg.create.repository':1
1365 'hg.create.repository':1
1357 }
1366 }
1358
1367
1359 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1368 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1360 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1369 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1361 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1370 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1362
1371
1363 def __unicode__(self):
1372 def __unicode__(self):
1364 return u"<%s('%s:%s')>" % (
1373 return u"<%s('%s:%s')>" % (
1365 self.__class__.__name__, self.permission_id, self.permission_name
1374 self.__class__.__name__, self.permission_id, self.permission_name
1366 )
1375 )
1367
1376
1368 @classmethod
1377 @classmethod
1369 def get_by_key(cls, key):
1378 def get_by_key(cls, key):
1370 return cls.query().filter(cls.permission_name == key).scalar()
1379 return cls.query().filter(cls.permission_name == key).scalar()
1371
1380
1372 @classmethod
1381 @classmethod
1373 def get_default_perms(cls, default_user_id):
1382 def get_default_perms(cls, default_user_id):
1374 q = Session().query(UserRepoToPerm, Repository, cls)\
1383 q = Session().query(UserRepoToPerm, Repository, cls)\
1375 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1384 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1376 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1385 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1377 .filter(UserRepoToPerm.user_id == default_user_id)
1386 .filter(UserRepoToPerm.user_id == default_user_id)
1378
1387
1379 return q.all()
1388 return q.all()
1380
1389
1381 @classmethod
1390 @classmethod
1382 def get_default_group_perms(cls, default_user_id):
1391 def get_default_group_perms(cls, default_user_id):
1383 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1392 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1384 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1393 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1385 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1394 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1386 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1395 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1387
1396
1388 return q.all()
1397 return q.all()
1389
1398
1390
1399
1391 class UserRepoToPerm(Base, BaseModel):
1400 class UserRepoToPerm(Base, BaseModel):
1392 __tablename__ = 'repo_to_perm'
1401 __tablename__ = 'repo_to_perm'
1393 __table_args__ = (
1402 __table_args__ = (
1394 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1403 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1395 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1404 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1396 'mysql_charset': 'utf8'}
1405 'mysql_charset': 'utf8'}
1397 )
1406 )
1398 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1407 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1399 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1408 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1400 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1409 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1401 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1410 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1402
1411
1403 user = relationship('User')
1412 user = relationship('User')
1404 repository = relationship('Repository')
1413 repository = relationship('Repository')
1405 permission = relationship('Permission')
1414 permission = relationship('Permission')
1406
1415
1407 @classmethod
1416 @classmethod
1408 def create(cls, user, repository, permission):
1417 def create(cls, user, repository, permission):
1409 n = cls()
1418 n = cls()
1410 n.user = user
1419 n.user = user
1411 n.repository = repository
1420 n.repository = repository
1412 n.permission = permission
1421 n.permission = permission
1413 Session().add(n)
1422 Session().add(n)
1414 return n
1423 return n
1415
1424
1416 def __unicode__(self):
1425 def __unicode__(self):
1417 return u'<user:%s => %s >' % (self.user, self.repository)
1426 return u'<user:%s => %s >' % (self.user, self.repository)
1418
1427
1419
1428
1420 class UserToPerm(Base, BaseModel):
1429 class UserToPerm(Base, BaseModel):
1421 __tablename__ = 'user_to_perm'
1430 __tablename__ = 'user_to_perm'
1422 __table_args__ = (
1431 __table_args__ = (
1423 UniqueConstraint('user_id', 'permission_id'),
1432 UniqueConstraint('user_id', 'permission_id'),
1424 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1433 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1425 'mysql_charset': 'utf8'}
1434 'mysql_charset': 'utf8'}
1426 )
1435 )
1427 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1436 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1428 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1437 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1429 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1438 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1430
1439
1431 user = relationship('User')
1440 user = relationship('User')
1432 permission = relationship('Permission', lazy='joined')
1441 permission = relationship('Permission', lazy='joined')
1433
1442
1434
1443
1435 class UsersGroupRepoToPerm(Base, BaseModel):
1444 class UsersGroupRepoToPerm(Base, BaseModel):
1436 __tablename__ = 'users_group_repo_to_perm'
1445 __tablename__ = 'users_group_repo_to_perm'
1437 __table_args__ = (
1446 __table_args__ = (
1438 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1447 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1439 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1448 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1440 'mysql_charset': 'utf8'}
1449 'mysql_charset': 'utf8'}
1441 )
1450 )
1442 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1451 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1443 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1452 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1444 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1453 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1445 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1454 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1446
1455
1447 users_group = relationship('UsersGroup')
1456 users_group = relationship('UsersGroup')
1448 permission = relationship('Permission')
1457 permission = relationship('Permission')
1449 repository = relationship('Repository')
1458 repository = relationship('Repository')
1450
1459
1451 @classmethod
1460 @classmethod
1452 def create(cls, users_group, repository, permission):
1461 def create(cls, users_group, repository, permission):
1453 n = cls()
1462 n = cls()
1454 n.users_group = users_group
1463 n.users_group = users_group
1455 n.repository = repository
1464 n.repository = repository
1456 n.permission = permission
1465 n.permission = permission
1457 Session().add(n)
1466 Session().add(n)
1458 return n
1467 return n
1459
1468
1460 def __unicode__(self):
1469 def __unicode__(self):
1461 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1470 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1462
1471
1463
1472
1464 class UsersGroupToPerm(Base, BaseModel):
1473 class UsersGroupToPerm(Base, BaseModel):
1465 __tablename__ = 'users_group_to_perm'
1474 __tablename__ = 'users_group_to_perm'
1466 __table_args__ = (
1475 __table_args__ = (
1467 UniqueConstraint('users_group_id', 'permission_id',),
1476 UniqueConstraint('users_group_id', 'permission_id',),
1468 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1477 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1469 'mysql_charset': 'utf8'}
1478 'mysql_charset': 'utf8'}
1470 )
1479 )
1471 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1480 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1472 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1481 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1473 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1482 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1474
1483
1475 users_group = relationship('UsersGroup')
1484 users_group = relationship('UsersGroup')
1476 permission = relationship('Permission')
1485 permission = relationship('Permission')
1477
1486
1478
1487
1479 class UserRepoGroupToPerm(Base, BaseModel):
1488 class UserRepoGroupToPerm(Base, BaseModel):
1480 __tablename__ = 'user_repo_group_to_perm'
1489 __tablename__ = 'user_repo_group_to_perm'
1481 __table_args__ = (
1490 __table_args__ = (
1482 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1491 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1483 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1484 'mysql_charset': 'utf8'}
1493 'mysql_charset': 'utf8'}
1485 )
1494 )
1486
1495
1487 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1496 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1488 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1497 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1489 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1498 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1490 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1499 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1491
1500
1492 user = relationship('User')
1501 user = relationship('User')
1493 group = relationship('RepoGroup')
1502 group = relationship('RepoGroup')
1494 permission = relationship('Permission')
1503 permission = relationship('Permission')
1495
1504
1496
1505
1497 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1506 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1498 __tablename__ = 'users_group_repo_group_to_perm'
1507 __tablename__ = 'users_group_repo_group_to_perm'
1499 __table_args__ = (
1508 __table_args__ = (
1500 UniqueConstraint('users_group_id', 'group_id'),
1509 UniqueConstraint('users_group_id', 'group_id'),
1501 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1510 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1502 'mysql_charset': 'utf8'}
1511 'mysql_charset': 'utf8'}
1503 )
1512 )
1504
1513
1505 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1514 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1506 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1515 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1507 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1516 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1508 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1517 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1509
1518
1510 users_group = relationship('UsersGroup')
1519 users_group = relationship('UsersGroup')
1511 permission = relationship('Permission')
1520 permission = relationship('Permission')
1512 group = relationship('RepoGroup')
1521 group = relationship('RepoGroup')
1513
1522
1514
1523
1515 class Statistics(Base, BaseModel):
1524 class Statistics(Base, BaseModel):
1516 __tablename__ = 'statistics'
1525 __tablename__ = 'statistics'
1517 __table_args__ = (
1526 __table_args__ = (
1518 UniqueConstraint('repository_id'),
1527 UniqueConstraint('repository_id'),
1519 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1528 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1520 'mysql_charset': 'utf8'}
1529 'mysql_charset': 'utf8'}
1521 )
1530 )
1522 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1531 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1523 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1532 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1524 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1533 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1525 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1534 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1526 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1535 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1527 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1536 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1528
1537
1529 repository = relationship('Repository', single_parent=True)
1538 repository = relationship('Repository', single_parent=True)
1530
1539
1531
1540
1532 class UserFollowing(Base, BaseModel):
1541 class UserFollowing(Base, BaseModel):
1533 __tablename__ = 'user_followings'
1542 __tablename__ = 'user_followings'
1534 __table_args__ = (
1543 __table_args__ = (
1535 UniqueConstraint('user_id', 'follows_repository_id'),
1544 UniqueConstraint('user_id', 'follows_repository_id'),
1536 UniqueConstraint('user_id', 'follows_user_id'),
1545 UniqueConstraint('user_id', 'follows_user_id'),
1537 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1538 'mysql_charset': 'utf8'}
1547 'mysql_charset': 'utf8'}
1539 )
1548 )
1540
1549
1541 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1550 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1542 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1551 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1543 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1552 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1544 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1553 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1545 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1554 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1546
1555
1547 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1556 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1548
1557
1549 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1558 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1550 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1559 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1551
1560
1552 @classmethod
1561 @classmethod
1553 def get_repo_followers(cls, repo_id):
1562 def get_repo_followers(cls, repo_id):
1554 return cls.query().filter(cls.follows_repo_id == repo_id)
1563 return cls.query().filter(cls.follows_repo_id == repo_id)
1555
1564
1556
1565
1557 class CacheInvalidation(Base, BaseModel):
1566 class CacheInvalidation(Base, BaseModel):
1558 __tablename__ = 'cache_invalidation'
1567 __tablename__ = 'cache_invalidation'
1559 __table_args__ = (
1568 __table_args__ = (
1560 UniqueConstraint('cache_key'),
1569 UniqueConstraint('cache_key'),
1561 Index('key_idx', 'cache_key'),
1570 Index('key_idx', 'cache_key'),
1562 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1571 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1563 'mysql_charset': 'utf8'},
1572 'mysql_charset': 'utf8'},
1564 )
1573 )
1565 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1574 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1566 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1575 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1567 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1576 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1568 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1577 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1569
1578
1570 def __init__(self, cache_key, cache_args=''):
1579 def __init__(self, cache_key, cache_args=''):
1571 self.cache_key = cache_key
1580 self.cache_key = cache_key
1572 self.cache_args = cache_args
1581 self.cache_args = cache_args
1573 self.cache_active = False
1582 self.cache_active = False
1574
1583
1575 def __unicode__(self):
1584 def __unicode__(self):
1576 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1585 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1577 self.cache_id, self.cache_key)
1586 self.cache_id, self.cache_key)
1578
1587
1579 @property
1588 @property
1580 def prefix(self):
1589 def prefix(self):
1581 _split = self.cache_key.split(self.cache_args, 1)
1590 _split = self.cache_key.split(self.cache_args, 1)
1582 if _split and len(_split) == 2:
1591 if _split and len(_split) == 2:
1583 return _split[0]
1592 return _split[0]
1584 return ''
1593 return ''
1585
1594
1586 @classmethod
1595 @classmethod
1587 def clear_cache(cls):
1596 def clear_cache(cls):
1588 cls.query().delete()
1597 cls.query().delete()
1589
1598
1590 @classmethod
1599 @classmethod
1591 def _get_key(cls, key):
1600 def _get_key(cls, key):
1592 """
1601 """
1593 Wrapper for generating a key, together with a prefix
1602 Wrapper for generating a key, together with a prefix
1594
1603
1595 :param key:
1604 :param key:
1596 """
1605 """
1597 import rhodecode
1606 import rhodecode
1598 prefix = ''
1607 prefix = ''
1599 org_key = key
1608 org_key = key
1600 iid = rhodecode.CONFIG.get('instance_id')
1609 iid = rhodecode.CONFIG.get('instance_id')
1601 if iid:
1610 if iid:
1602 prefix = iid
1611 prefix = iid
1603
1612
1604 return "%s%s" % (prefix, key), prefix, org_key
1613 return "%s%s" % (prefix, key), prefix, org_key
1605
1614
1606 @classmethod
1615 @classmethod
1607 def get_by_key(cls, key):
1616 def get_by_key(cls, key):
1608 return cls.query().filter(cls.cache_key == key).scalar()
1617 return cls.query().filter(cls.cache_key == key).scalar()
1609
1618
1610 @classmethod
1619 @classmethod
1611 def get_by_repo_name(cls, repo_name):
1620 def get_by_repo_name(cls, repo_name):
1612 return cls.query().filter(cls.cache_args == repo_name).all()
1621 return cls.query().filter(cls.cache_args == repo_name).all()
1613
1622
1614 @classmethod
1623 @classmethod
1615 def _get_or_create_key(cls, key, repo_name, commit=True):
1624 def _get_or_create_key(cls, key, repo_name, commit=True):
1616 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1625 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1617 if not inv_obj:
1626 if not inv_obj:
1618 try:
1627 try:
1619 inv_obj = CacheInvalidation(key, repo_name)
1628 inv_obj = CacheInvalidation(key, repo_name)
1620 Session().add(inv_obj)
1629 Session().add(inv_obj)
1621 if commit:
1630 if commit:
1622 Session().commit()
1631 Session().commit()
1623 except Exception:
1632 except Exception:
1624 log.error(traceback.format_exc())
1633 log.error(traceback.format_exc())
1625 Session().rollback()
1634 Session().rollback()
1626 return inv_obj
1635 return inv_obj
1627
1636
1628 @classmethod
1637 @classmethod
1629 def invalidate(cls, key):
1638 def invalidate(cls, key):
1630 """
1639 """
1631 Returns Invalidation object if this given key should be invalidated
1640 Returns Invalidation object if this given key should be invalidated
1632 None otherwise. `cache_active = False` means that this cache
1641 None otherwise. `cache_active = False` means that this cache
1633 state is not valid and needs to be invalidated
1642 state is not valid and needs to be invalidated
1634
1643
1635 :param key:
1644 :param key:
1636 """
1645 """
1637 repo_name = key
1646 repo_name = key
1638 repo_name = remove_suffix(repo_name, '_README')
1647 repo_name = remove_suffix(repo_name, '_README')
1639 repo_name = remove_suffix(repo_name, '_RSS')
1648 repo_name = remove_suffix(repo_name, '_RSS')
1640 repo_name = remove_suffix(repo_name, '_ATOM')
1649 repo_name = remove_suffix(repo_name, '_ATOM')
1641
1650
1642 # adds instance prefix
1651 # adds instance prefix
1643 key, _prefix, _org_key = cls._get_key(key)
1652 key, _prefix, _org_key = cls._get_key(key)
1644 inv = cls._get_or_create_key(key, repo_name)
1653 inv = cls._get_or_create_key(key, repo_name)
1645
1654
1646 if inv and inv.cache_active is False:
1655 if inv and inv.cache_active is False:
1647 return inv
1656 return inv
1648
1657
1649 @classmethod
1658 @classmethod
1650 def set_invalidate(cls, key=None, repo_name=None):
1659 def set_invalidate(cls, key=None, repo_name=None):
1651 """
1660 """
1652 Mark this Cache key for invalidation, either by key or whole
1661 Mark this Cache key for invalidation, either by key or whole
1653 cache sets based on repo_name
1662 cache sets based on repo_name
1654
1663
1655 :param key:
1664 :param key:
1656 """
1665 """
1657 if key:
1666 if key:
1658 key, _prefix, _org_key = cls._get_key(key)
1667 key, _prefix, _org_key = cls._get_key(key)
1659 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1668 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1660 elif repo_name:
1669 elif repo_name:
1661 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1670 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1662
1671
1663 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1672 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1664 % (len(inv_objs), key, repo_name))
1673 % (len(inv_objs), key, repo_name))
1665 try:
1674 try:
1666 for inv_obj in inv_objs:
1675 for inv_obj in inv_objs:
1667 inv_obj.cache_active = False
1676 inv_obj.cache_active = False
1668 Session().add(inv_obj)
1677 Session().add(inv_obj)
1669 Session().commit()
1678 Session().commit()
1670 except Exception:
1679 except Exception:
1671 log.error(traceback.format_exc())
1680 log.error(traceback.format_exc())
1672 Session().rollback()
1681 Session().rollback()
1673
1682
1674 @classmethod
1683 @classmethod
1675 def set_valid(cls, key):
1684 def set_valid(cls, key):
1676 """
1685 """
1677 Mark this cache key as active and currently cached
1686 Mark this cache key as active and currently cached
1678
1687
1679 :param key:
1688 :param key:
1680 """
1689 """
1681 inv_obj = cls.get_by_key(key)
1690 inv_obj = cls.get_by_key(key)
1682 inv_obj.cache_active = True
1691 inv_obj.cache_active = True
1683 Session().add(inv_obj)
1692 Session().add(inv_obj)
1684 Session().commit()
1693 Session().commit()
1685
1694
1686 @classmethod
1695 @classmethod
1687 def get_cache_map(cls):
1696 def get_cache_map(cls):
1688
1697
1689 class cachemapdict(dict):
1698 class cachemapdict(dict):
1690
1699
1691 def __init__(self, *args, **kwargs):
1700 def __init__(self, *args, **kwargs):
1692 fixkey = kwargs.get('fixkey')
1701 fixkey = kwargs.get('fixkey')
1693 if fixkey:
1702 if fixkey:
1694 del kwargs['fixkey']
1703 del kwargs['fixkey']
1695 self.fixkey = fixkey
1704 self.fixkey = fixkey
1696 super(cachemapdict, self).__init__(*args, **kwargs)
1705 super(cachemapdict, self).__init__(*args, **kwargs)
1697
1706
1698 def __getattr__(self, name):
1707 def __getattr__(self, name):
1699 key = name
1708 key = name
1700 if self.fixkey:
1709 if self.fixkey:
1701 key, _prefix, _org_key = cls._get_key(key)
1710 key, _prefix, _org_key = cls._get_key(key)
1702 if key in self.__dict__:
1711 if key in self.__dict__:
1703 return self.__dict__[key]
1712 return self.__dict__[key]
1704 else:
1713 else:
1705 return self[key]
1714 return self[key]
1706
1715
1707 def __getitem__(self, key):
1716 def __getitem__(self, key):
1708 if self.fixkey:
1717 if self.fixkey:
1709 key, _prefix, _org_key = cls._get_key(key)
1718 key, _prefix, _org_key = cls._get_key(key)
1710 try:
1719 try:
1711 return super(cachemapdict, self).__getitem__(key)
1720 return super(cachemapdict, self).__getitem__(key)
1712 except KeyError:
1721 except KeyError:
1713 return
1722 return
1714
1723
1715 cache_map = cachemapdict(fixkey=True)
1724 cache_map = cachemapdict(fixkey=True)
1716 for obj in cls.query().all():
1725 for obj in cls.query().all():
1717 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1726 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1718 return cache_map
1727 return cache_map
1719
1728
1720
1729
1721 class ChangesetComment(Base, BaseModel):
1730 class ChangesetComment(Base, BaseModel):
1722 __tablename__ = 'changeset_comments'
1731 __tablename__ = 'changeset_comments'
1723 __table_args__ = (
1732 __table_args__ = (
1724 Index('cc_revision_idx', 'revision'),
1733 Index('cc_revision_idx', 'revision'),
1725 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1734 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1726 'mysql_charset': 'utf8'},
1735 'mysql_charset': 'utf8'},
1727 )
1736 )
1728 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1737 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1729 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1738 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1730 revision = Column('revision', String(40), nullable=True)
1739 revision = Column('revision', String(40), nullable=True)
1731 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1740 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1732 line_no = Column('line_no', Unicode(10), nullable=True)
1741 line_no = Column('line_no', Unicode(10), nullable=True)
1733 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1742 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1734 f_path = Column('f_path', Unicode(1000), nullable=True)
1743 f_path = Column('f_path', Unicode(1000), nullable=True)
1735 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1744 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1736 text = Column('text', UnicodeText(25000), nullable=False)
1745 text = Column('text', UnicodeText(25000), nullable=False)
1737 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1746 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1738 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1747 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1739
1748
1740 author = relationship('User', lazy='joined')
1749 author = relationship('User', lazy='joined')
1741 repo = relationship('Repository')
1750 repo = relationship('Repository')
1742 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1751 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1743 pull_request = relationship('PullRequest', lazy='joined')
1752 pull_request = relationship('PullRequest', lazy='joined')
1744
1753
1745 @classmethod
1754 @classmethod
1746 def get_users(cls, revision=None, pull_request_id=None):
1755 def get_users(cls, revision=None, pull_request_id=None):
1747 """
1756 """
1748 Returns user associated with this ChangesetComment. ie those
1757 Returns user associated with this ChangesetComment. ie those
1749 who actually commented
1758 who actually commented
1750
1759
1751 :param cls:
1760 :param cls:
1752 :param revision:
1761 :param revision:
1753 """
1762 """
1754 q = Session().query(User)\
1763 q = Session().query(User)\
1755 .join(ChangesetComment.author)
1764 .join(ChangesetComment.author)
1756 if revision:
1765 if revision:
1757 q = q.filter(cls.revision == revision)
1766 q = q.filter(cls.revision == revision)
1758 elif pull_request_id:
1767 elif pull_request_id:
1759 q = q.filter(cls.pull_request_id == pull_request_id)
1768 q = q.filter(cls.pull_request_id == pull_request_id)
1760 return q.all()
1769 return q.all()
1761
1770
1762
1771
1763 class ChangesetStatus(Base, BaseModel):
1772 class ChangesetStatus(Base, BaseModel):
1764 __tablename__ = 'changeset_statuses'
1773 __tablename__ = 'changeset_statuses'
1765 __table_args__ = (
1774 __table_args__ = (
1766 Index('cs_revision_idx', 'revision'),
1775 Index('cs_revision_idx', 'revision'),
1767 Index('cs_version_idx', 'version'),
1776 Index('cs_version_idx', 'version'),
1768 UniqueConstraint('repo_id', 'revision', 'version'),
1777 UniqueConstraint('repo_id', 'revision', 'version'),
1769 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1778 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1770 'mysql_charset': 'utf8'}
1779 'mysql_charset': 'utf8'}
1771 )
1780 )
1772 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1781 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1773 STATUS_APPROVED = 'approved'
1782 STATUS_APPROVED = 'approved'
1774 STATUS_REJECTED = 'rejected'
1783 STATUS_REJECTED = 'rejected'
1775 STATUS_UNDER_REVIEW = 'under_review'
1784 STATUS_UNDER_REVIEW = 'under_review'
1776
1785
1777 STATUSES = [
1786 STATUSES = [
1778 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1787 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1779 (STATUS_APPROVED, _("Approved")),
1788 (STATUS_APPROVED, _("Approved")),
1780 (STATUS_REJECTED, _("Rejected")),
1789 (STATUS_REJECTED, _("Rejected")),
1781 (STATUS_UNDER_REVIEW, _("Under Review")),
1790 (STATUS_UNDER_REVIEW, _("Under Review")),
1782 ]
1791 ]
1783
1792
1784 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1793 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1785 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1794 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1786 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1795 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1787 revision = Column('revision', String(40), nullable=False)
1796 revision = Column('revision', String(40), nullable=False)
1788 status = Column('status', String(128), nullable=False, default=DEFAULT)
1797 status = Column('status', String(128), nullable=False, default=DEFAULT)
1789 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1798 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1790 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1799 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1791 version = Column('version', Integer(), nullable=False, default=0)
1800 version = Column('version', Integer(), nullable=False, default=0)
1792 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1801 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1793
1802
1794 author = relationship('User', lazy='joined')
1803 author = relationship('User', lazy='joined')
1795 repo = relationship('Repository')
1804 repo = relationship('Repository')
1796 comment = relationship('ChangesetComment', lazy='joined')
1805 comment = relationship('ChangesetComment', lazy='joined')
1797 pull_request = relationship('PullRequest', lazy='joined')
1806 pull_request = relationship('PullRequest', lazy='joined')
1798
1807
1799 def __unicode__(self):
1808 def __unicode__(self):
1800 return u"<%s('%s:%s')>" % (
1809 return u"<%s('%s:%s')>" % (
1801 self.__class__.__name__,
1810 self.__class__.__name__,
1802 self.status, self.author
1811 self.status, self.author
1803 )
1812 )
1804
1813
1805 @classmethod
1814 @classmethod
1806 def get_status_lbl(cls, value):
1815 def get_status_lbl(cls, value):
1807 return dict(cls.STATUSES).get(value)
1816 return dict(cls.STATUSES).get(value)
1808
1817
1809 @property
1818 @property
1810 def status_lbl(self):
1819 def status_lbl(self):
1811 return ChangesetStatus.get_status_lbl(self.status)
1820 return ChangesetStatus.get_status_lbl(self.status)
1812
1821
1813
1822
1814 class PullRequest(Base, BaseModel):
1823 class PullRequest(Base, BaseModel):
1815 __tablename__ = 'pull_requests'
1824 __tablename__ = 'pull_requests'
1816 __table_args__ = (
1825 __table_args__ = (
1817 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1826 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1818 'mysql_charset': 'utf8'},
1827 'mysql_charset': 'utf8'},
1819 )
1828 )
1820
1829
1821 STATUS_NEW = u'new'
1830 STATUS_NEW = u'new'
1822 STATUS_OPEN = u'open'
1831 STATUS_OPEN = u'open'
1823 STATUS_CLOSED = u'closed'
1832 STATUS_CLOSED = u'closed'
1824
1833
1825 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1834 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1826 title = Column('title', Unicode(256), nullable=True)
1835 title = Column('title', Unicode(256), nullable=True)
1827 description = Column('description', UnicodeText(10240), nullable=True)
1836 description = Column('description', UnicodeText(10240), nullable=True)
1828 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1837 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1829 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1838 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1830 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1839 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1831 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1840 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1832 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1841 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1833 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1842 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1834 org_ref = Column('org_ref', Unicode(256), nullable=False)
1843 org_ref = Column('org_ref', Unicode(256), nullable=False)
1835 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1844 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1836 other_ref = Column('other_ref', Unicode(256), nullable=False)
1845 other_ref = Column('other_ref', Unicode(256), nullable=False)
1837
1846
1838 @hybrid_property
1847 @hybrid_property
1839 def revisions(self):
1848 def revisions(self):
1840 return self._revisions.split(':')
1849 return self._revisions.split(':')
1841
1850
1842 @revisions.setter
1851 @revisions.setter
1843 def revisions(self, val):
1852 def revisions(self, val):
1844 self._revisions = ':'.join(val)
1853 self._revisions = ':'.join(val)
1845
1854
1846 @property
1855 @property
1847 def org_ref_parts(self):
1856 def org_ref_parts(self):
1848 return self.org_ref.split(':')
1857 return self.org_ref.split(':')
1849
1858
1850 @property
1859 @property
1851 def other_ref_parts(self):
1860 def other_ref_parts(self):
1852 return self.other_ref.split(':')
1861 return self.other_ref.split(':')
1853
1862
1854 author = relationship('User', lazy='joined')
1863 author = relationship('User', lazy='joined')
1855 reviewers = relationship('PullRequestReviewers',
1864 reviewers = relationship('PullRequestReviewers',
1856 cascade="all, delete, delete-orphan")
1865 cascade="all, delete, delete-orphan")
1857 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1866 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1858 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1867 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1859 statuses = relationship('ChangesetStatus')
1868 statuses = relationship('ChangesetStatus')
1860 comments = relationship('ChangesetComment',
1869 comments = relationship('ChangesetComment',
1861 cascade="all, delete, delete-orphan")
1870 cascade="all, delete, delete-orphan")
1862
1871
1863 def is_closed(self):
1872 def is_closed(self):
1864 return self.status == self.STATUS_CLOSED
1873 return self.status == self.STATUS_CLOSED
1865
1874
1866 def __json__(self):
1875 def __json__(self):
1867 return dict(
1876 return dict(
1868 revisions=self.revisions
1877 revisions=self.revisions
1869 )
1878 )
1870
1879
1871
1880
1872 class PullRequestReviewers(Base, BaseModel):
1881 class PullRequestReviewers(Base, BaseModel):
1873 __tablename__ = 'pull_request_reviewers'
1882 __tablename__ = 'pull_request_reviewers'
1874 __table_args__ = (
1883 __table_args__ = (
1875 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1884 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1876 'mysql_charset': 'utf8'},
1885 'mysql_charset': 'utf8'},
1877 )
1886 )
1878
1887
1879 def __init__(self, user=None, pull_request=None):
1888 def __init__(self, user=None, pull_request=None):
1880 self.user = user
1889 self.user = user
1881 self.pull_request = pull_request
1890 self.pull_request = pull_request
1882
1891
1883 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1892 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1884 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1893 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1885 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1894 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1886
1895
1887 user = relationship('User')
1896 user = relationship('User')
1888 pull_request = relationship('PullRequest')
1897 pull_request = relationship('PullRequest')
1889
1898
1890
1899
1891 class Notification(Base, BaseModel):
1900 class Notification(Base, BaseModel):
1892 __tablename__ = 'notifications'
1901 __tablename__ = 'notifications'
1893 __table_args__ = (
1902 __table_args__ = (
1894 Index('notification_type_idx', 'type'),
1903 Index('notification_type_idx', 'type'),
1895 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1904 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1896 'mysql_charset': 'utf8'},
1905 'mysql_charset': 'utf8'},
1897 )
1906 )
1898
1907
1899 TYPE_CHANGESET_COMMENT = u'cs_comment'
1908 TYPE_CHANGESET_COMMENT = u'cs_comment'
1900 TYPE_MESSAGE = u'message'
1909 TYPE_MESSAGE = u'message'
1901 TYPE_MENTION = u'mention'
1910 TYPE_MENTION = u'mention'
1902 TYPE_REGISTRATION = u'registration'
1911 TYPE_REGISTRATION = u'registration'
1903 TYPE_PULL_REQUEST = u'pull_request'
1912 TYPE_PULL_REQUEST = u'pull_request'
1904 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1913 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1905
1914
1906 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1915 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1907 subject = Column('subject', Unicode(512), nullable=True)
1916 subject = Column('subject', Unicode(512), nullable=True)
1908 body = Column('body', UnicodeText(50000), nullable=True)
1917 body = Column('body', UnicodeText(50000), nullable=True)
1909 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1918 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1910 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1919 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1911 type_ = Column('type', Unicode(256))
1920 type_ = Column('type', Unicode(256))
1912
1921
1913 created_by_user = relationship('User')
1922 created_by_user = relationship('User')
1914 notifications_to_users = relationship('UserNotification', lazy='joined',
1923 notifications_to_users = relationship('UserNotification', lazy='joined',
1915 cascade="all, delete, delete-orphan")
1924 cascade="all, delete, delete-orphan")
1916
1925
1917 @property
1926 @property
1918 def recipients(self):
1927 def recipients(self):
1919 return [x.user for x in UserNotification.query()\
1928 return [x.user for x in UserNotification.query()\
1920 .filter(UserNotification.notification == self)\
1929 .filter(UserNotification.notification == self)\
1921 .order_by(UserNotification.user_id.asc()).all()]
1930 .order_by(UserNotification.user_id.asc()).all()]
1922
1931
1923 @classmethod
1932 @classmethod
1924 def create(cls, created_by, subject, body, recipients, type_=None):
1933 def create(cls, created_by, subject, body, recipients, type_=None):
1925 if type_ is None:
1934 if type_ is None:
1926 type_ = Notification.TYPE_MESSAGE
1935 type_ = Notification.TYPE_MESSAGE
1927
1936
1928 notification = cls()
1937 notification = cls()
1929 notification.created_by_user = created_by
1938 notification.created_by_user = created_by
1930 notification.subject = subject
1939 notification.subject = subject
1931 notification.body = body
1940 notification.body = body
1932 notification.type_ = type_
1941 notification.type_ = type_
1933 notification.created_on = datetime.datetime.now()
1942 notification.created_on = datetime.datetime.now()
1934
1943
1935 for u in recipients:
1944 for u in recipients:
1936 assoc = UserNotification()
1945 assoc = UserNotification()
1937 assoc.notification = notification
1946 assoc.notification = notification
1938 u.notifications.append(assoc)
1947 u.notifications.append(assoc)
1939 Session().add(notification)
1948 Session().add(notification)
1940 return notification
1949 return notification
1941
1950
1942 @property
1951 @property
1943 def description(self):
1952 def description(self):
1944 from rhodecode.model.notification import NotificationModel
1953 from rhodecode.model.notification import NotificationModel
1945 return NotificationModel().make_description(self)
1954 return NotificationModel().make_description(self)
1946
1955
1947
1956
1948 class UserNotification(Base, BaseModel):
1957 class UserNotification(Base, BaseModel):
1949 __tablename__ = 'user_to_notification'
1958 __tablename__ = 'user_to_notification'
1950 __table_args__ = (
1959 __table_args__ = (
1951 UniqueConstraint('user_id', 'notification_id'),
1960 UniqueConstraint('user_id', 'notification_id'),
1952 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1961 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1953 'mysql_charset': 'utf8'}
1962 'mysql_charset': 'utf8'}
1954 )
1963 )
1955 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1964 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1956 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1965 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1957 read = Column('read', Boolean, default=False)
1966 read = Column('read', Boolean, default=False)
1958 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1967 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1959
1968
1960 user = relationship('User', lazy="joined")
1969 user = relationship('User', lazy="joined")
1961 notification = relationship('Notification', lazy="joined",
1970 notification = relationship('Notification', lazy="joined",
1962 order_by=lambda: Notification.created_on.desc(),)
1971 order_by=lambda: Notification.created_on.desc(),)
1963
1972
1964 def mark_as_read(self):
1973 def mark_as_read(self):
1965 self.read = True
1974 self.read = True
1966 Session().add(self)
1975 Session().add(self)
1967
1976
1968
1977
1969 class DbMigrateVersion(Base, BaseModel):
1978 class DbMigrateVersion(Base, BaseModel):
1970 __tablename__ = 'db_migrate_version'
1979 __tablename__ = 'db_migrate_version'
1971 __table_args__ = (
1980 __table_args__ = (
1972 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1981 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1973 'mysql_charset': 'utf8'},
1982 'mysql_charset': 'utf8'},
1974 )
1983 )
1975 repository_id = Column('repository_id', String(250), primary_key=True)
1984 repository_id = Column('repository_id', String(250), primary_key=True)
1976 repository_path = Column('repository_path', Text)
1985 repository_path = Column('repository_path', Text)
1977 version = Column('version', Integer)
1986 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now