##// END OF EJS Templates
fixes #762, LDAP and container created users are now activated based on...
marcink -
r3370:fdb0f59b beta
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,1009 +1,1013 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 try:
666 try:
663 user_perms = set([self.user_perms['repositories'][repo_name]])
667 user_perms = set([self.user_perms['repositories'][repo_name]])
664 except KeyError:
668 except KeyError:
665 return False
669 return False
666
670
667 if self.required_perms.intersection(user_perms):
671 if self.required_perms.intersection(user_perms):
668 return True
672 return True
669 return False
673 return False
670
674
671
675
672 class HasReposGroupPermissionAllDecorator(PermsDecorator):
676 class HasReposGroupPermissionAllDecorator(PermsDecorator):
673 """
677 """
674 Checks for access permission for all given predicates for specific
678 Checks for access permission for all given predicates for specific
675 repository. All of them have to be meet in order to fulfill the request
679 repository. All of them have to be meet in order to fulfill the request
676 """
680 """
677
681
678 def check_permissions(self):
682 def check_permissions(self):
679 group_name = get_repos_group_slug(request)
683 group_name = get_repos_group_slug(request)
680 try:
684 try:
681 user_perms = set([self.user_perms['repositories_groups'][group_name]])
685 user_perms = set([self.user_perms['repositories_groups'][group_name]])
682 except KeyError:
686 except KeyError:
683 return False
687 return False
684
688
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 try:
702 try:
699 user_perms = set([self.user_perms['repositories_groups'][group_name]])
703 user_perms = set([self.user_perms['repositories_groups'][group_name]])
700 except KeyError:
704 except KeyError:
701 return False
705 return False
702
706
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 #TODO: put user as attribute here
730 #TODO: put user as attribute here
727 user = request.user
731 user = request.user
728 cls_name = self.__class__.__name__
732 cls_name = self.__class__.__name__
729 check_scope = {
733 check_scope = {
730 'HasPermissionAll': '',
734 'HasPermissionAll': '',
731 'HasPermissionAny': '',
735 'HasPermissionAny': '',
732 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
736 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
733 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
737 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
734 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
738 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
735 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
739 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
736 }.get(cls_name, '?')
740 }.get(cls_name, '?')
737 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
741 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
738 self.required_perms, user, check_scope,
742 self.required_perms, user, check_scope,
739 check_location or 'unspecified location')
743 check_location or 'unspecified location')
740 if not user:
744 if not user:
741 log.debug('Empty request user')
745 log.debug('Empty request user')
742 return False
746 return False
743 self.user_perms = user.permissions
747 self.user_perms = user.permissions
744 if self.check_permissions():
748 if self.check_permissions():
745 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
749 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
746 check_location or 'unspecified location')
750 check_location or 'unspecified location')
747 return True
751 return True
748
752
749 else:
753 else:
750 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
754 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
751 check_location or 'unspecified location')
755 check_location or 'unspecified location')
752 return False
756 return False
753
757
754 def check_permissions(self):
758 def check_permissions(self):
755 """Dummy function for overriding"""
759 """Dummy function for overriding"""
756 raise Exception('You have to write this function in child class')
760 raise Exception('You have to write this function in child class')
757
761
758
762
759 class HasPermissionAll(PermsFunction):
763 class HasPermissionAll(PermsFunction):
760 def check_permissions(self):
764 def check_permissions(self):
761 if self.required_perms.issubset(self.user_perms.get('global')):
765 if self.required_perms.issubset(self.user_perms.get('global')):
762 return True
766 return True
763 return False
767 return False
764
768
765
769
766 class HasPermissionAny(PermsFunction):
770 class HasPermissionAny(PermsFunction):
767 def check_permissions(self):
771 def check_permissions(self):
768 if self.required_perms.intersection(self.user_perms.get('global')):
772 if self.required_perms.intersection(self.user_perms.get('global')):
769 return True
773 return True
770 return False
774 return False
771
775
772
776
773 class HasRepoPermissionAll(PermsFunction):
777 class HasRepoPermissionAll(PermsFunction):
774 def __call__(self, repo_name=None, check_location=''):
778 def __call__(self, repo_name=None, check_location=''):
775 self.repo_name = repo_name
779 self.repo_name = repo_name
776 return super(HasRepoPermissionAll, self).__call__(check_location)
780 return super(HasRepoPermissionAll, self).__call__(check_location)
777
781
778 def check_permissions(self):
782 def check_permissions(self):
779 if not self.repo_name:
783 if not self.repo_name:
780 self.repo_name = get_repo_slug(request)
784 self.repo_name = get_repo_slug(request)
781
785
782 try:
786 try:
783 self._user_perms = set(
787 self._user_perms = set(
784 [self.user_perms['repositories'][self.repo_name]]
788 [self.user_perms['repositories'][self.repo_name]]
785 )
789 )
786 except KeyError:
790 except KeyError:
787 return False
791 return False
788 if self.required_perms.issubset(self._user_perms):
792 if self.required_perms.issubset(self._user_perms):
789 return True
793 return True
790 return False
794 return False
791
795
792
796
793 class HasRepoPermissionAny(PermsFunction):
797 class HasRepoPermissionAny(PermsFunction):
794 def __call__(self, repo_name=None, check_location=''):
798 def __call__(self, repo_name=None, check_location=''):
795 self.repo_name = repo_name
799 self.repo_name = repo_name
796 return super(HasRepoPermissionAny, self).__call__(check_location)
800 return super(HasRepoPermissionAny, self).__call__(check_location)
797
801
798 def check_permissions(self):
802 def check_permissions(self):
799 if not self.repo_name:
803 if not self.repo_name:
800 self.repo_name = get_repo_slug(request)
804 self.repo_name = get_repo_slug(request)
801
805
802 try:
806 try:
803 self._user_perms = set(
807 self._user_perms = set(
804 [self.user_perms['repositories'][self.repo_name]]
808 [self.user_perms['repositories'][self.repo_name]]
805 )
809 )
806 except KeyError:
810 except KeyError:
807 return False
811 return False
808 if self.required_perms.intersection(self._user_perms):
812 if self.required_perms.intersection(self._user_perms):
809 return True
813 return True
810 return False
814 return False
811
815
812
816
813 class HasReposGroupPermissionAny(PermsFunction):
817 class HasReposGroupPermissionAny(PermsFunction):
814 def __call__(self, group_name=None, check_location=''):
818 def __call__(self, group_name=None, check_location=''):
815 self.group_name = group_name
819 self.group_name = group_name
816 return super(HasReposGroupPermissionAny, self).__call__(check_location)
820 return super(HasReposGroupPermissionAny, self).__call__(check_location)
817
821
818 def check_permissions(self):
822 def check_permissions(self):
819 try:
823 try:
820 self._user_perms = set(
824 self._user_perms = set(
821 [self.user_perms['repositories_groups'][self.group_name]]
825 [self.user_perms['repositories_groups'][self.group_name]]
822 )
826 )
823 except KeyError:
827 except KeyError:
824 return False
828 return False
825 if self.required_perms.intersection(self._user_perms):
829 if self.required_perms.intersection(self._user_perms):
826 return True
830 return True
827 return False
831 return False
828
832
829
833
830 class HasReposGroupPermissionAll(PermsFunction):
834 class HasReposGroupPermissionAll(PermsFunction):
831 def __call__(self, group_name=None, check_location=''):
835 def __call__(self, group_name=None, check_location=''):
832 self.group_name = group_name
836 self.group_name = group_name
833 return super(HasReposGroupPermissionAll, self).__call__(check_location)
837 return super(HasReposGroupPermissionAll, self).__call__(check_location)
834
838
835 def check_permissions(self):
839 def check_permissions(self):
836 try:
840 try:
837 self._user_perms = set(
841 self._user_perms = set(
838 [self.user_perms['repositories_groups'][self.group_name]]
842 [self.user_perms['repositories_groups'][self.group_name]]
839 )
843 )
840 except KeyError:
844 except KeyError:
841 return False
845 return False
842 if self.required_perms.issubset(self._user_perms):
846 if self.required_perms.issubset(self._user_perms):
843 return True
847 return True
844 return False
848 return False
845
849
846
850
847 #==============================================================================
851 #==============================================================================
848 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
852 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
849 #==============================================================================
853 #==============================================================================
850 class HasPermissionAnyMiddleware(object):
854 class HasPermissionAnyMiddleware(object):
851 def __init__(self, *perms):
855 def __init__(self, *perms):
852 self.required_perms = set(perms)
856 self.required_perms = set(perms)
853
857
854 def __call__(self, user, repo_name):
858 def __call__(self, user, repo_name):
855 # repo_name MUST be unicode, since we handle keys in permission
859 # repo_name MUST be unicode, since we handle keys in permission
856 # dict by unicode
860 # dict by unicode
857 repo_name = safe_unicode(repo_name)
861 repo_name = safe_unicode(repo_name)
858 usr = AuthUser(user.user_id)
862 usr = AuthUser(user.user_id)
859 try:
863 try:
860 self.user_perms = set([usr.permissions['repositories'][repo_name]])
864 self.user_perms = set([usr.permissions['repositories'][repo_name]])
861 except Exception:
865 except Exception:
862 log.error('Exception while accessing permissions %s' %
866 log.error('Exception while accessing permissions %s' %
863 traceback.format_exc())
867 traceback.format_exc())
864 self.user_perms = set()
868 self.user_perms = set()
865 self.username = user.username
869 self.username = user.username
866 self.repo_name = repo_name
870 self.repo_name = repo_name
867 return self.check_permissions()
871 return self.check_permissions()
868
872
869 def check_permissions(self):
873 def check_permissions(self):
870 log.debug('checking VCS protocol '
874 log.debug('checking VCS protocol '
871 'permissions %s for user:%s repository:%s', self.user_perms,
875 'permissions %s for user:%s repository:%s', self.user_perms,
872 self.username, self.repo_name)
876 self.username, self.repo_name)
873 if self.required_perms.intersection(self.user_perms):
877 if self.required_perms.intersection(self.user_perms):
874 log.debug('permission granted for user:%s on repo:%s' % (
878 log.debug('permission granted for user:%s on repo:%s' % (
875 self.username, self.repo_name
879 self.username, self.repo_name
876 )
880 )
877 )
881 )
878 return True
882 return True
879 log.debug('permission denied for user:%s on repo:%s' % (
883 log.debug('permission denied for user:%s on repo:%s' % (
880 self.username, self.repo_name
884 self.username, self.repo_name
881 )
885 )
882 )
886 )
883 return False
887 return False
884
888
885
889
886 #==============================================================================
890 #==============================================================================
887 # SPECIAL VERSION TO HANDLE API AUTH
891 # SPECIAL VERSION TO HANDLE API AUTH
888 #==============================================================================
892 #==============================================================================
889 class _BaseApiPerm(object):
893 class _BaseApiPerm(object):
890 def __init__(self, *perms):
894 def __init__(self, *perms):
891 self.required_perms = set(perms)
895 self.required_perms = set(perms)
892
896
893 def __call__(self, check_location='unspecified', user=None, repo_name=None):
897 def __call__(self, check_location='unspecified', user=None, repo_name=None):
894 cls_name = self.__class__.__name__
898 cls_name = self.__class__.__name__
895 check_scope = 'user:%s, repo:%s' % (user, repo_name)
899 check_scope = 'user:%s, repo:%s' % (user, repo_name)
896 log.debug('checking cls:%s %s %s @ %s', cls_name,
900 log.debug('checking cls:%s %s %s @ %s', cls_name,
897 self.required_perms, check_scope, check_location)
901 self.required_perms, check_scope, check_location)
898 if not user:
902 if not user:
899 log.debug('Empty User passed into arguments')
903 log.debug('Empty User passed into arguments')
900 return False
904 return False
901
905
902 ## process user
906 ## process user
903 if not isinstance(user, AuthUser):
907 if not isinstance(user, AuthUser):
904 user = AuthUser(user.user_id)
908 user = AuthUser(user.user_id)
905
909
906 if self.check_permissions(user.permissions, repo_name):
910 if self.check_permissions(user.permissions, repo_name):
907 log.debug('Permission to %s granted for user: %s @ %s', repo_name,
911 log.debug('Permission to %s granted for user: %s @ %s', repo_name,
908 user, check_location)
912 user, check_location)
909 return True
913 return True
910
914
911 else:
915 else:
912 log.debug('Permission to %s denied for user: %s @ %s', repo_name,
916 log.debug('Permission to %s denied for user: %s @ %s', repo_name,
913 user, check_location)
917 user, check_location)
914 return False
918 return False
915
919
916 def check_permissions(self, perm_defs, repo_name):
920 def check_permissions(self, perm_defs, repo_name):
917 """
921 """
918 implement in child class should return True if permissions are ok,
922 implement in child class should return True if permissions are ok,
919 False otherwise
923 False otherwise
920
924
921 :param perm_defs: dict with permission definitions
925 :param perm_defs: dict with permission definitions
922 :param repo_name: repo name
926 :param repo_name: repo name
923 """
927 """
924 raise NotImplementedError()
928 raise NotImplementedError()
925
929
926
930
927 class HasPermissionAllApi(_BaseApiPerm):
931 class HasPermissionAllApi(_BaseApiPerm):
928 def __call__(self, user, check_location=''):
932 def __call__(self, user, check_location=''):
929 return super(HasPermissionAllApi, self)\
933 return super(HasPermissionAllApi, self)\
930 .__call__(check_location=check_location, user=user)
934 .__call__(check_location=check_location, user=user)
931
935
932 def check_permissions(self, perm_defs, repo):
936 def check_permissions(self, perm_defs, repo):
933 if self.required_perms.issubset(perm_defs.get('global')):
937 if self.required_perms.issubset(perm_defs.get('global')):
934 return True
938 return True
935 return False
939 return False
936
940
937
941
938 class HasPermissionAnyApi(_BaseApiPerm):
942 class HasPermissionAnyApi(_BaseApiPerm):
939 def __call__(self, user, check_location=''):
943 def __call__(self, user, check_location=''):
940 return super(HasPermissionAnyApi, self)\
944 return super(HasPermissionAnyApi, self)\
941 .__call__(check_location=check_location, user=user)
945 .__call__(check_location=check_location, user=user)
942
946
943 def check_permissions(self, perm_defs, repo):
947 def check_permissions(self, perm_defs, repo):
944 if self.required_perms.intersection(perm_defs.get('global')):
948 if self.required_perms.intersection(perm_defs.get('global')):
945 return True
949 return True
946 return False
950 return False
947
951
948
952
949 class HasRepoPermissionAllApi(_BaseApiPerm):
953 class HasRepoPermissionAllApi(_BaseApiPerm):
950 def __call__(self, user, repo_name, check_location=''):
954 def __call__(self, user, repo_name, check_location=''):
951 return super(HasRepoPermissionAllApi, self)\
955 return super(HasRepoPermissionAllApi, self)\
952 .__call__(check_location=check_location, user=user,
956 .__call__(check_location=check_location, user=user,
953 repo_name=repo_name)
957 repo_name=repo_name)
954
958
955 def check_permissions(self, perm_defs, repo_name):
959 def check_permissions(self, perm_defs, repo_name):
956
960
957 try:
961 try:
958 self._user_perms = set(
962 self._user_perms = set(
959 [perm_defs['repositories'][repo_name]]
963 [perm_defs['repositories'][repo_name]]
960 )
964 )
961 except KeyError:
965 except KeyError:
962 log.warning(traceback.format_exc())
966 log.warning(traceback.format_exc())
963 return False
967 return False
964 if self.required_perms.issubset(self._user_perms):
968 if self.required_perms.issubset(self._user_perms):
965 return True
969 return True
966 return False
970 return False
967
971
968
972
969 class HasRepoPermissionAnyApi(_BaseApiPerm):
973 class HasRepoPermissionAnyApi(_BaseApiPerm):
970 def __call__(self, user, repo_name, check_location=''):
974 def __call__(self, user, repo_name, check_location=''):
971 return super(HasRepoPermissionAnyApi, self)\
975 return super(HasRepoPermissionAnyApi, self)\
972 .__call__(check_location=check_location, user=user,
976 .__call__(check_location=check_location, user=user,
973 repo_name=repo_name)
977 repo_name=repo_name)
974
978
975 def check_permissions(self, perm_defs, repo_name):
979 def check_permissions(self, perm_defs, repo_name):
976
980
977 try:
981 try:
978 _user_perms = set(
982 _user_perms = set(
979 [perm_defs['repositories'][repo_name]]
983 [perm_defs['repositories'][repo_name]]
980 )
984 )
981 except KeyError:
985 except KeyError:
982 log.warning(traceback.format_exc())
986 log.warning(traceback.format_exc())
983 return False
987 return False
984 if self.required_perms.intersection(_user_perms):
988 if self.required_perms.intersection(_user_perms):
985 return True
989 return True
986 return False
990 return False
987
991
988
992
989 def check_ip_access(source_ip, allowed_ips=None):
993 def check_ip_access(source_ip, allowed_ips=None):
990 """
994 """
991 Checks if source_ip is a subnet of any of allowed_ips.
995 Checks if source_ip is a subnet of any of allowed_ips.
992
996
993 :param source_ip:
997 :param source_ip:
994 :param allowed_ips: list of allowed ips together with mask
998 :param allowed_ips: list of allowed ips together with mask
995 """
999 """
996 from rhodecode.lib import ipaddr
1000 from rhodecode.lib import ipaddr
997 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1001 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
998 if isinstance(allowed_ips, (tuple, list, set)):
1002 if isinstance(allowed_ips, (tuple, list, set)):
999 for ip in allowed_ips:
1003 for ip in allowed_ips:
1000 try:
1004 try:
1001 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1005 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1002 return True
1006 return True
1003 # for any case we cannot determine the IP, don't crash just
1007 # for any case we cannot determine the IP, don't crash just
1004 # skip it and log as error, we want to say forbidden still when
1008 # skip it and log as error, we want to say forbidden still when
1005 # sending bad IP
1009 # sending bad IP
1006 except Exception:
1010 except Exception:
1007 log.error(traceback.format_exc())
1011 log.error(traceback.format_exc())
1008 continue
1012 continue
1009 return False
1013 return False
@@ -1,2033 +1,2042 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 RepositoryField(Base, BaseModel):
680 class RepositoryField(Base, BaseModel):
672 __tablename__ = 'repositories_fields'
681 __tablename__ = 'repositories_fields'
673 __table_args__ = (
682 __table_args__ = (
674 UniqueConstraint('repository_id', 'field_key'), # no-multi field
683 UniqueConstraint('repository_id', 'field_key'), # no-multi field
675 {'extend_existing': True, 'mysql_engine': 'InnoDB',
684 {'extend_existing': True, 'mysql_engine': 'InnoDB',
676 'mysql_charset': 'utf8'},
685 'mysql_charset': 'utf8'},
677 )
686 )
678 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
687 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
679
688
680 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
689 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
681 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
690 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
682 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
691 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
683 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
692 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
684 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
693 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
685 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
694 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
686 field_type = Column("field_type", String(256), nullable=False, unique=None)
695 field_type = Column("field_type", String(256), nullable=False, unique=None)
687 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
696 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
688
697
689 repository = relationship('Repository')
698 repository = relationship('Repository')
690
699
691 @property
700 @property
692 def field_key_prefixed(self):
701 def field_key_prefixed(self):
693 return 'ex_%s' % self.field_key
702 return 'ex_%s' % self.field_key
694
703
695 @classmethod
704 @classmethod
696 def un_prefix_key(cls, key):
705 def un_prefix_key(cls, key):
697 if key.startswith(cls.PREFIX):
706 if key.startswith(cls.PREFIX):
698 return key[len(cls.PREFIX):]
707 return key[len(cls.PREFIX):]
699 return key
708 return key
700
709
701 @classmethod
710 @classmethod
702 def get_by_key_name(cls, key, repo):
711 def get_by_key_name(cls, key, repo):
703 row = cls.query()\
712 row = cls.query()\
704 .filter(cls.repository == repo)\
713 .filter(cls.repository == repo)\
705 .filter(cls.field_key == key).scalar()
714 .filter(cls.field_key == key).scalar()
706 return row
715 return row
707
716
708
717
709 class Repository(Base, BaseModel):
718 class Repository(Base, BaseModel):
710 __tablename__ = 'repositories'
719 __tablename__ = 'repositories'
711 __table_args__ = (
720 __table_args__ = (
712 UniqueConstraint('repo_name'),
721 UniqueConstraint('repo_name'),
713 Index('r_repo_name_idx', 'repo_name'),
722 Index('r_repo_name_idx', 'repo_name'),
714 {'extend_existing': True, 'mysql_engine': 'InnoDB',
723 {'extend_existing': True, 'mysql_engine': 'InnoDB',
715 'mysql_charset': 'utf8'},
724 'mysql_charset': 'utf8'},
716 )
725 )
717
726
718 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
727 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
719 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
728 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
720 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
729 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
721 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
730 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
722 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
731 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
723 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
732 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
724 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
733 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
725 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
734 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
726 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
735 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
727 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
736 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
728 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
737 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
729 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
738 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
730 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
739 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
731 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
740 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
732 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
741 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
733
742
734 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
743 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
735 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
744 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
736
745
737 user = relationship('User')
746 user = relationship('User')
738 fork = relationship('Repository', remote_side=repo_id)
747 fork = relationship('Repository', remote_side=repo_id)
739 group = relationship('RepoGroup')
748 group = relationship('RepoGroup')
740 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
749 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
741 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
750 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
742 stats = relationship('Statistics', cascade='all', uselist=False)
751 stats = relationship('Statistics', cascade='all', uselist=False)
743
752
744 followers = relationship('UserFollowing',
753 followers = relationship('UserFollowing',
745 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
754 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
746 cascade='all')
755 cascade='all')
747 extra_fields = relationship('RepositoryField',
756 extra_fields = relationship('RepositoryField',
748 cascade="all, delete, delete-orphan")
757 cascade="all, delete, delete-orphan")
749
758
750 logs = relationship('UserLog')
759 logs = relationship('UserLog')
751 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
760 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
752
761
753 pull_requests_org = relationship('PullRequest',
762 pull_requests_org = relationship('PullRequest',
754 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
763 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
755 cascade="all, delete, delete-orphan")
764 cascade="all, delete, delete-orphan")
756
765
757 pull_requests_other = relationship('PullRequest',
766 pull_requests_other = relationship('PullRequest',
758 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
767 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
759 cascade="all, delete, delete-orphan")
768 cascade="all, delete, delete-orphan")
760
769
761 def __unicode__(self):
770 def __unicode__(self):
762 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
771 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
763 self.repo_name)
772 self.repo_name)
764
773
765 @hybrid_property
774 @hybrid_property
766 def locked(self):
775 def locked(self):
767 # always should return [user_id, timelocked]
776 # always should return [user_id, timelocked]
768 if self._locked:
777 if self._locked:
769 _lock_info = self._locked.split(':')
778 _lock_info = self._locked.split(':')
770 return int(_lock_info[0]), _lock_info[1]
779 return int(_lock_info[0]), _lock_info[1]
771 return [None, None]
780 return [None, None]
772
781
773 @locked.setter
782 @locked.setter
774 def locked(self, val):
783 def locked(self, val):
775 if val and isinstance(val, (list, tuple)):
784 if val and isinstance(val, (list, tuple)):
776 self._locked = ':'.join(map(str, val))
785 self._locked = ':'.join(map(str, val))
777 else:
786 else:
778 self._locked = None
787 self._locked = None
779
788
780 @hybrid_property
789 @hybrid_property
781 def changeset_cache(self):
790 def changeset_cache(self):
782 from rhodecode.lib.vcs.backends.base import EmptyChangeset
791 from rhodecode.lib.vcs.backends.base import EmptyChangeset
783 dummy = EmptyChangeset().__json__()
792 dummy = EmptyChangeset().__json__()
784 if not self._changeset_cache:
793 if not self._changeset_cache:
785 return dummy
794 return dummy
786 try:
795 try:
787 return json.loads(self._changeset_cache)
796 return json.loads(self._changeset_cache)
788 except TypeError:
797 except TypeError:
789 return dummy
798 return dummy
790
799
791 @changeset_cache.setter
800 @changeset_cache.setter
792 def changeset_cache(self, val):
801 def changeset_cache(self, val):
793 try:
802 try:
794 self._changeset_cache = json.dumps(val)
803 self._changeset_cache = json.dumps(val)
795 except:
804 except:
796 log.error(traceback.format_exc())
805 log.error(traceback.format_exc())
797
806
798 @classmethod
807 @classmethod
799 def url_sep(cls):
808 def url_sep(cls):
800 return URL_SEP
809 return URL_SEP
801
810
802 @classmethod
811 @classmethod
803 def normalize_repo_name(cls, repo_name):
812 def normalize_repo_name(cls, repo_name):
804 """
813 """
805 Normalizes os specific repo_name to the format internally stored inside
814 Normalizes os specific repo_name to the format internally stored inside
806 dabatabase using URL_SEP
815 dabatabase using URL_SEP
807
816
808 :param cls:
817 :param cls:
809 :param repo_name:
818 :param repo_name:
810 """
819 """
811 return cls.url_sep().join(repo_name.split(os.sep))
820 return cls.url_sep().join(repo_name.split(os.sep))
812
821
813 @classmethod
822 @classmethod
814 def get_by_repo_name(cls, repo_name):
823 def get_by_repo_name(cls, repo_name):
815 q = Session().query(cls).filter(cls.repo_name == repo_name)
824 q = Session().query(cls).filter(cls.repo_name == repo_name)
816 q = q.options(joinedload(Repository.fork))\
825 q = q.options(joinedload(Repository.fork))\
817 .options(joinedload(Repository.user))\
826 .options(joinedload(Repository.user))\
818 .options(joinedload(Repository.group))
827 .options(joinedload(Repository.group))
819 return q.scalar()
828 return q.scalar()
820
829
821 @classmethod
830 @classmethod
822 def get_by_full_path(cls, repo_full_path):
831 def get_by_full_path(cls, repo_full_path):
823 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
832 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
824 repo_name = cls.normalize_repo_name(repo_name)
833 repo_name = cls.normalize_repo_name(repo_name)
825 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
834 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
826
835
827 @classmethod
836 @classmethod
828 def get_repo_forks(cls, repo_id):
837 def get_repo_forks(cls, repo_id):
829 return cls.query().filter(Repository.fork_id == repo_id)
838 return cls.query().filter(Repository.fork_id == repo_id)
830
839
831 @classmethod
840 @classmethod
832 def base_path(cls):
841 def base_path(cls):
833 """
842 """
834 Returns base path when all repos are stored
843 Returns base path when all repos are stored
835
844
836 :param cls:
845 :param cls:
837 """
846 """
838 q = Session().query(RhodeCodeUi)\
847 q = Session().query(RhodeCodeUi)\
839 .filter(RhodeCodeUi.ui_key == cls.url_sep())
848 .filter(RhodeCodeUi.ui_key == cls.url_sep())
840 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
849 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
841 return q.one().ui_value
850 return q.one().ui_value
842
851
843 @property
852 @property
844 def forks(self):
853 def forks(self):
845 """
854 """
846 Return forks of this repo
855 Return forks of this repo
847 """
856 """
848 return Repository.get_repo_forks(self.repo_id)
857 return Repository.get_repo_forks(self.repo_id)
849
858
850 @property
859 @property
851 def parent(self):
860 def parent(self):
852 """
861 """
853 Returns fork parent
862 Returns fork parent
854 """
863 """
855 return self.fork
864 return self.fork
856
865
857 @property
866 @property
858 def just_name(self):
867 def just_name(self):
859 return self.repo_name.split(Repository.url_sep())[-1]
868 return self.repo_name.split(Repository.url_sep())[-1]
860
869
861 @property
870 @property
862 def groups_with_parents(self):
871 def groups_with_parents(self):
863 groups = []
872 groups = []
864 if self.group is None:
873 if self.group is None:
865 return groups
874 return groups
866
875
867 cur_gr = self.group
876 cur_gr = self.group
868 groups.insert(0, cur_gr)
877 groups.insert(0, cur_gr)
869 while 1:
878 while 1:
870 gr = getattr(cur_gr, 'parent_group', None)
879 gr = getattr(cur_gr, 'parent_group', None)
871 cur_gr = cur_gr.parent_group
880 cur_gr = cur_gr.parent_group
872 if gr is None:
881 if gr is None:
873 break
882 break
874 groups.insert(0, gr)
883 groups.insert(0, gr)
875
884
876 return groups
885 return groups
877
886
878 @property
887 @property
879 def groups_and_repo(self):
888 def groups_and_repo(self):
880 return self.groups_with_parents, self.just_name
889 return self.groups_with_parents, self.just_name
881
890
882 @LazyProperty
891 @LazyProperty
883 def repo_path(self):
892 def repo_path(self):
884 """
893 """
885 Returns base full path for that repository means where it actually
894 Returns base full path for that repository means where it actually
886 exists on a filesystem
895 exists on a filesystem
887 """
896 """
888 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
897 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
889 Repository.url_sep())
898 Repository.url_sep())
890 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
899 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
891 return q.one().ui_value
900 return q.one().ui_value
892
901
893 @property
902 @property
894 def repo_full_path(self):
903 def repo_full_path(self):
895 p = [self.repo_path]
904 p = [self.repo_path]
896 # we need to split the name by / since this is how we store the
905 # we need to split the name by / since this is how we store the
897 # names in the database, but that eventually needs to be converted
906 # names in the database, but that eventually needs to be converted
898 # into a valid system path
907 # into a valid system path
899 p += self.repo_name.split(Repository.url_sep())
908 p += self.repo_name.split(Repository.url_sep())
900 return os.path.join(*p)
909 return os.path.join(*p)
901
910
902 @property
911 @property
903 def cache_keys(self):
912 def cache_keys(self):
904 """
913 """
905 Returns associated cache keys for that repo
914 Returns associated cache keys for that repo
906 """
915 """
907 return CacheInvalidation.query()\
916 return CacheInvalidation.query()\
908 .filter(CacheInvalidation.cache_args == self.repo_name)\
917 .filter(CacheInvalidation.cache_args == self.repo_name)\
909 .order_by(CacheInvalidation.cache_key)\
918 .order_by(CacheInvalidation.cache_key)\
910 .all()
919 .all()
911
920
912 def get_new_name(self, repo_name):
921 def get_new_name(self, repo_name):
913 """
922 """
914 returns new full repository name based on assigned group and new new
923 returns new full repository name based on assigned group and new new
915
924
916 :param group_name:
925 :param group_name:
917 """
926 """
918 path_prefix = self.group.full_path_splitted if self.group else []
927 path_prefix = self.group.full_path_splitted if self.group else []
919 return Repository.url_sep().join(path_prefix + [repo_name])
928 return Repository.url_sep().join(path_prefix + [repo_name])
920
929
921 @property
930 @property
922 def _ui(self):
931 def _ui(self):
923 """
932 """
924 Creates an db based ui object for this repository
933 Creates an db based ui object for this repository
925 """
934 """
926 from rhodecode.lib.utils import make_ui
935 from rhodecode.lib.utils import make_ui
927 return make_ui('db', clear_session=False)
936 return make_ui('db', clear_session=False)
928
937
929 @classmethod
938 @classmethod
930 def inject_ui(cls, repo, extras={}):
939 def inject_ui(cls, repo, extras={}):
931 from rhodecode.lib.vcs.backends.hg import MercurialRepository
940 from rhodecode.lib.vcs.backends.hg import MercurialRepository
932 from rhodecode.lib.vcs.backends.git import GitRepository
941 from rhodecode.lib.vcs.backends.git import GitRepository
933 required = (MercurialRepository, GitRepository)
942 required = (MercurialRepository, GitRepository)
934 if not isinstance(repo, required):
943 if not isinstance(repo, required):
935 raise Exception('repo must be instance of %s' % required)
944 raise Exception('repo must be instance of %s' % required)
936
945
937 # inject ui extra param to log this action via push logger
946 # inject ui extra param to log this action via push logger
938 for k, v in extras.items():
947 for k, v in extras.items():
939 repo._repo.ui.setconfig('rhodecode_extras', k, v)
948 repo._repo.ui.setconfig('rhodecode_extras', k, v)
940
949
941 @classmethod
950 @classmethod
942 def is_valid(cls, repo_name):
951 def is_valid(cls, repo_name):
943 """
952 """
944 returns True if given repo name is a valid filesystem repository
953 returns True if given repo name is a valid filesystem repository
945
954
946 :param cls:
955 :param cls:
947 :param repo_name:
956 :param repo_name:
948 """
957 """
949 from rhodecode.lib.utils import is_valid_repo
958 from rhodecode.lib.utils import is_valid_repo
950
959
951 return is_valid_repo(repo_name, cls.base_path())
960 return is_valid_repo(repo_name, cls.base_path())
952
961
953 def get_api_data(self):
962 def get_api_data(self):
954 """
963 """
955 Common function for generating repo api data
964 Common function for generating repo api data
956
965
957 """
966 """
958 repo = self
967 repo = self
959 data = dict(
968 data = dict(
960 repo_id=repo.repo_id,
969 repo_id=repo.repo_id,
961 repo_name=repo.repo_name,
970 repo_name=repo.repo_name,
962 repo_type=repo.repo_type,
971 repo_type=repo.repo_type,
963 clone_uri=repo.clone_uri,
972 clone_uri=repo.clone_uri,
964 private=repo.private,
973 private=repo.private,
965 created_on=repo.created_on,
974 created_on=repo.created_on,
966 description=repo.description,
975 description=repo.description,
967 landing_rev=repo.landing_rev,
976 landing_rev=repo.landing_rev,
968 owner=repo.user.username,
977 owner=repo.user.username,
969 fork_of=repo.fork.repo_name if repo.fork else None,
978 fork_of=repo.fork.repo_name if repo.fork else None,
970 enable_statistics=repo.enable_statistics,
979 enable_statistics=repo.enable_statistics,
971 enable_locking=repo.enable_locking,
980 enable_locking=repo.enable_locking,
972 enable_downloads=repo.enable_downloads,
981 enable_downloads=repo.enable_downloads,
973 last_changeset=repo.changeset_cache
982 last_changeset=repo.changeset_cache
974 )
983 )
975 rc_config = RhodeCodeSetting.get_app_settings()
984 rc_config = RhodeCodeSetting.get_app_settings()
976 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
985 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
977 if repository_fields:
986 if repository_fields:
978 for f in self.extra_fields:
987 for f in self.extra_fields:
979 data[f.field_key_prefixed] = f.field_value
988 data[f.field_key_prefixed] = f.field_value
980
989
981 return data
990 return data
982
991
983 @classmethod
992 @classmethod
984 def lock(cls, repo, user_id):
993 def lock(cls, repo, user_id):
985 repo.locked = [user_id, time.time()]
994 repo.locked = [user_id, time.time()]
986 Session().add(repo)
995 Session().add(repo)
987 Session().commit()
996 Session().commit()
988
997
989 @classmethod
998 @classmethod
990 def unlock(cls, repo):
999 def unlock(cls, repo):
991 repo.locked = None
1000 repo.locked = None
992 Session().add(repo)
1001 Session().add(repo)
993 Session().commit()
1002 Session().commit()
994
1003
995 @property
1004 @property
996 def last_db_change(self):
1005 def last_db_change(self):
997 return self.updated_on
1006 return self.updated_on
998
1007
999 def clone_url(self, **override):
1008 def clone_url(self, **override):
1000 from pylons import url
1009 from pylons import url
1001 from urlparse import urlparse
1010 from urlparse import urlparse
1002 import urllib
1011 import urllib
1003 parsed_url = urlparse(url('home', qualified=True))
1012 parsed_url = urlparse(url('home', qualified=True))
1004 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1013 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1005 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1014 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1006 args = {
1015 args = {
1007 'user': '',
1016 'user': '',
1008 'pass': '',
1017 'pass': '',
1009 'scheme': parsed_url.scheme,
1018 'scheme': parsed_url.scheme,
1010 'netloc': parsed_url.netloc,
1019 'netloc': parsed_url.netloc,
1011 'prefix': decoded_path,
1020 'prefix': decoded_path,
1012 'path': self.repo_name
1021 'path': self.repo_name
1013 }
1022 }
1014
1023
1015 args.update(override)
1024 args.update(override)
1016 return default_clone_uri % args
1025 return default_clone_uri % args
1017
1026
1018 #==========================================================================
1027 #==========================================================================
1019 # SCM PROPERTIES
1028 # SCM PROPERTIES
1020 #==========================================================================
1029 #==========================================================================
1021
1030
1022 def get_changeset(self, rev=None):
1031 def get_changeset(self, rev=None):
1023 return get_changeset_safe(self.scm_instance, rev)
1032 return get_changeset_safe(self.scm_instance, rev)
1024
1033
1025 def get_landing_changeset(self):
1034 def get_landing_changeset(self):
1026 """
1035 """
1027 Returns landing changeset, or if that doesn't exist returns the tip
1036 Returns landing changeset, or if that doesn't exist returns the tip
1028 """
1037 """
1029 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1038 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1030 return cs
1039 return cs
1031
1040
1032 def update_changeset_cache(self, cs_cache=None):
1041 def update_changeset_cache(self, cs_cache=None):
1033 """
1042 """
1034 Update cache of last changeset for repository, keys should be::
1043 Update cache of last changeset for repository, keys should be::
1035
1044
1036 short_id
1045 short_id
1037 raw_id
1046 raw_id
1038 revision
1047 revision
1039 message
1048 message
1040 date
1049 date
1041 author
1050 author
1042
1051
1043 :param cs_cache:
1052 :param cs_cache:
1044 """
1053 """
1045 from rhodecode.lib.vcs.backends.base import BaseChangeset
1054 from rhodecode.lib.vcs.backends.base import BaseChangeset
1046 if cs_cache is None:
1055 if cs_cache is None:
1047 cs_cache = self.get_changeset()
1056 cs_cache = self.get_changeset()
1048 if isinstance(cs_cache, BaseChangeset):
1057 if isinstance(cs_cache, BaseChangeset):
1049 cs_cache = cs_cache.__json__()
1058 cs_cache = cs_cache.__json__()
1050
1059
1051 if (cs_cache != self.changeset_cache
1060 if (cs_cache != self.changeset_cache
1052 or not self.last_change
1061 or not self.last_change
1053 or not self.changeset_cache):
1062 or not self.changeset_cache):
1054 _default = datetime.datetime.fromtimestamp(0)
1063 _default = datetime.datetime.fromtimestamp(0)
1055 last_change = cs_cache.get('date') or self.last_change or _default
1064 last_change = cs_cache.get('date') or self.last_change or _default
1056 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1065 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1057 self.updated_on = last_change
1066 self.updated_on = last_change
1058 self.changeset_cache = cs_cache
1067 self.changeset_cache = cs_cache
1059 Session().add(self)
1068 Session().add(self)
1060 Session().commit()
1069 Session().commit()
1061 else:
1070 else:
1062 log.debug('Skipping repo:%s already with latest changes' % self)
1071 log.debug('Skipping repo:%s already with latest changes' % self)
1063
1072
1064 @property
1073 @property
1065 def tip(self):
1074 def tip(self):
1066 return self.get_changeset('tip')
1075 return self.get_changeset('tip')
1067
1076
1068 @property
1077 @property
1069 def author(self):
1078 def author(self):
1070 return self.tip.author
1079 return self.tip.author
1071
1080
1072 @property
1081 @property
1073 def last_change(self):
1082 def last_change(self):
1074 return self.scm_instance.last_change
1083 return self.scm_instance.last_change
1075
1084
1076 def get_comments(self, revisions=None):
1085 def get_comments(self, revisions=None):
1077 """
1086 """
1078 Returns comments for this repository grouped by revisions
1087 Returns comments for this repository grouped by revisions
1079
1088
1080 :param revisions: filter query by revisions only
1089 :param revisions: filter query by revisions only
1081 """
1090 """
1082 cmts = ChangesetComment.query()\
1091 cmts = ChangesetComment.query()\
1083 .filter(ChangesetComment.repo == self)
1092 .filter(ChangesetComment.repo == self)
1084 if revisions:
1093 if revisions:
1085 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1094 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1086 grouped = defaultdict(list)
1095 grouped = defaultdict(list)
1087 for cmt in cmts.all():
1096 for cmt in cmts.all():
1088 grouped[cmt.revision].append(cmt)
1097 grouped[cmt.revision].append(cmt)
1089 return grouped
1098 return grouped
1090
1099
1091 def statuses(self, revisions=None):
1100 def statuses(self, revisions=None):
1092 """
1101 """
1093 Returns statuses for this repository
1102 Returns statuses for this repository
1094
1103
1095 :param revisions: list of revisions to get statuses for
1104 :param revisions: list of revisions to get statuses for
1096 :type revisions: list
1105 :type revisions: list
1097 """
1106 """
1098
1107
1099 statuses = ChangesetStatus.query()\
1108 statuses = ChangesetStatus.query()\
1100 .filter(ChangesetStatus.repo == self)\
1109 .filter(ChangesetStatus.repo == self)\
1101 .filter(ChangesetStatus.version == 0)
1110 .filter(ChangesetStatus.version == 0)
1102 if revisions:
1111 if revisions:
1103 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1112 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1104 grouped = {}
1113 grouped = {}
1105
1114
1106 #maybe we have open new pullrequest without a status ?
1115 #maybe we have open new pullrequest without a status ?
1107 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1116 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1108 status_lbl = ChangesetStatus.get_status_lbl(stat)
1117 status_lbl = ChangesetStatus.get_status_lbl(stat)
1109 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1118 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1110 for rev in pr.revisions:
1119 for rev in pr.revisions:
1111 pr_id = pr.pull_request_id
1120 pr_id = pr.pull_request_id
1112 pr_repo = pr.other_repo.repo_name
1121 pr_repo = pr.other_repo.repo_name
1113 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1122 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1114
1123
1115 for stat in statuses.all():
1124 for stat in statuses.all():
1116 pr_id = pr_repo = None
1125 pr_id = pr_repo = None
1117 if stat.pull_request:
1126 if stat.pull_request:
1118 pr_id = stat.pull_request.pull_request_id
1127 pr_id = stat.pull_request.pull_request_id
1119 pr_repo = stat.pull_request.other_repo.repo_name
1128 pr_repo = stat.pull_request.other_repo.repo_name
1120 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1129 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1121 pr_id, pr_repo]
1130 pr_id, pr_repo]
1122 return grouped
1131 return grouped
1123
1132
1124 def _repo_size(self):
1133 def _repo_size(self):
1125 from rhodecode.lib import helpers as h
1134 from rhodecode.lib import helpers as h
1126 log.debug('calculating repository size...')
1135 log.debug('calculating repository size...')
1127 return h.format_byte_size(self.scm_instance.size)
1136 return h.format_byte_size(self.scm_instance.size)
1128
1137
1129 #==========================================================================
1138 #==========================================================================
1130 # SCM CACHE INSTANCE
1139 # SCM CACHE INSTANCE
1131 #==========================================================================
1140 #==========================================================================
1132
1141
1133 @property
1142 @property
1134 def invalidate(self):
1143 def invalidate(self):
1135 return CacheInvalidation.invalidate(self.repo_name)
1144 return CacheInvalidation.invalidate(self.repo_name)
1136
1145
1137 def set_invalidate(self):
1146 def set_invalidate(self):
1138 """
1147 """
1139 set a cache for invalidation for this instance
1148 set a cache for invalidation for this instance
1140 """
1149 """
1141 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1150 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1142
1151
1143 @LazyProperty
1152 @LazyProperty
1144 def scm_instance_no_cache(self):
1153 def scm_instance_no_cache(self):
1145 return self.__get_instance()
1154 return self.__get_instance()
1146
1155
1147 @LazyProperty
1156 @LazyProperty
1148 def scm_instance(self):
1157 def scm_instance(self):
1149 import rhodecode
1158 import rhodecode
1150 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1159 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1151 if full_cache:
1160 if full_cache:
1152 return self.scm_instance_cached()
1161 return self.scm_instance_cached()
1153 return self.__get_instance()
1162 return self.__get_instance()
1154
1163
1155 def scm_instance_cached(self, cache_map=None):
1164 def scm_instance_cached(self, cache_map=None):
1156 @cache_region('long_term')
1165 @cache_region('long_term')
1157 def _c(repo_name):
1166 def _c(repo_name):
1158 return self.__get_instance()
1167 return self.__get_instance()
1159 rn = self.repo_name
1168 rn = self.repo_name
1160 log.debug('Getting cached instance of repo')
1169 log.debug('Getting cached instance of repo')
1161
1170
1162 if cache_map:
1171 if cache_map:
1163 # get using prefilled cache_map
1172 # get using prefilled cache_map
1164 invalidate_repo = cache_map[self.repo_name]
1173 invalidate_repo = cache_map[self.repo_name]
1165 if invalidate_repo:
1174 if invalidate_repo:
1166 invalidate_repo = (None if invalidate_repo.cache_active
1175 invalidate_repo = (None if invalidate_repo.cache_active
1167 else invalidate_repo)
1176 else invalidate_repo)
1168 else:
1177 else:
1169 # get from invalidate
1178 # get from invalidate
1170 invalidate_repo = self.invalidate
1179 invalidate_repo = self.invalidate
1171
1180
1172 if invalidate_repo is not None:
1181 if invalidate_repo is not None:
1173 region_invalidate(_c, None, rn)
1182 region_invalidate(_c, None, rn)
1174 # update our cache
1183 # update our cache
1175 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1184 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1176 return _c(rn)
1185 return _c(rn)
1177
1186
1178 def __get_instance(self):
1187 def __get_instance(self):
1179 repo_full_path = self.repo_full_path
1188 repo_full_path = self.repo_full_path
1180 try:
1189 try:
1181 alias = get_scm(repo_full_path)[0]
1190 alias = get_scm(repo_full_path)[0]
1182 log.debug('Creating instance of %s repository' % alias)
1191 log.debug('Creating instance of %s repository' % alias)
1183 backend = get_backend(alias)
1192 backend = get_backend(alias)
1184 except VCSError:
1193 except VCSError:
1185 log.error(traceback.format_exc())
1194 log.error(traceback.format_exc())
1186 log.error('Perhaps this repository is in db and not in '
1195 log.error('Perhaps this repository is in db and not in '
1187 'filesystem run rescan repositories with '
1196 'filesystem run rescan repositories with '
1188 '"destroy old data " option from admin panel')
1197 '"destroy old data " option from admin panel')
1189 return
1198 return
1190
1199
1191 if alias == 'hg':
1200 if alias == 'hg':
1192
1201
1193 repo = backend(safe_str(repo_full_path), create=False,
1202 repo = backend(safe_str(repo_full_path), create=False,
1194 baseui=self._ui)
1203 baseui=self._ui)
1195 # skip hidden web repository
1204 # skip hidden web repository
1196 if repo._get_hidden():
1205 if repo._get_hidden():
1197 return
1206 return
1198 else:
1207 else:
1199 repo = backend(repo_full_path, create=False)
1208 repo = backend(repo_full_path, create=False)
1200
1209
1201 return repo
1210 return repo
1202
1211
1203
1212
1204 class RepoGroup(Base, BaseModel):
1213 class RepoGroup(Base, BaseModel):
1205 __tablename__ = 'groups'
1214 __tablename__ = 'groups'
1206 __table_args__ = (
1215 __table_args__ = (
1207 UniqueConstraint('group_name', 'group_parent_id'),
1216 UniqueConstraint('group_name', 'group_parent_id'),
1208 CheckConstraint('group_id != group_parent_id'),
1217 CheckConstraint('group_id != group_parent_id'),
1209 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1218 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1210 'mysql_charset': 'utf8'},
1219 'mysql_charset': 'utf8'},
1211 )
1220 )
1212 __mapper_args__ = {'order_by': 'group_name'}
1221 __mapper_args__ = {'order_by': 'group_name'}
1213
1222
1214 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1223 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1215 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1224 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1216 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1225 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1217 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1226 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1218 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1227 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1219
1228
1220 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1229 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1221 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1230 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1222
1231
1223 parent_group = relationship('RepoGroup', remote_side=group_id)
1232 parent_group = relationship('RepoGroup', remote_side=group_id)
1224
1233
1225 def __init__(self, group_name='', parent_group=None):
1234 def __init__(self, group_name='', parent_group=None):
1226 self.group_name = group_name
1235 self.group_name = group_name
1227 self.parent_group = parent_group
1236 self.parent_group = parent_group
1228
1237
1229 def __unicode__(self):
1238 def __unicode__(self):
1230 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1239 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1231 self.group_name)
1240 self.group_name)
1232
1241
1233 @classmethod
1242 @classmethod
1234 def groups_choices(cls, groups=None, show_empty_group=True):
1243 def groups_choices(cls, groups=None, show_empty_group=True):
1235 from webhelpers.html import literal as _literal
1244 from webhelpers.html import literal as _literal
1236 if not groups:
1245 if not groups:
1237 groups = cls.query().all()
1246 groups = cls.query().all()
1238
1247
1239 repo_groups = []
1248 repo_groups = []
1240 if show_empty_group:
1249 if show_empty_group:
1241 repo_groups = [('-1', '-- no parent --')]
1250 repo_groups = [('-1', '-- no parent --')]
1242 sep = ' &raquo; '
1251 sep = ' &raquo; '
1243 _name = lambda k: _literal(sep.join(k))
1252 _name = lambda k: _literal(sep.join(k))
1244
1253
1245 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1254 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1246 for x in groups])
1255 for x in groups])
1247
1256
1248 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1257 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1249 return repo_groups
1258 return repo_groups
1250
1259
1251 @classmethod
1260 @classmethod
1252 def url_sep(cls):
1261 def url_sep(cls):
1253 return URL_SEP
1262 return URL_SEP
1254
1263
1255 @classmethod
1264 @classmethod
1256 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1265 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1257 if case_insensitive:
1266 if case_insensitive:
1258 gr = cls.query()\
1267 gr = cls.query()\
1259 .filter(cls.group_name.ilike(group_name))
1268 .filter(cls.group_name.ilike(group_name))
1260 else:
1269 else:
1261 gr = cls.query()\
1270 gr = cls.query()\
1262 .filter(cls.group_name == group_name)
1271 .filter(cls.group_name == group_name)
1263 if cache:
1272 if cache:
1264 gr = gr.options(FromCache(
1273 gr = gr.options(FromCache(
1265 "sql_cache_short",
1274 "sql_cache_short",
1266 "get_group_%s" % _hash_key(group_name)
1275 "get_group_%s" % _hash_key(group_name)
1267 )
1276 )
1268 )
1277 )
1269 return gr.scalar()
1278 return gr.scalar()
1270
1279
1271 @property
1280 @property
1272 def parents(self):
1281 def parents(self):
1273 parents_recursion_limit = 5
1282 parents_recursion_limit = 5
1274 groups = []
1283 groups = []
1275 if self.parent_group is None:
1284 if self.parent_group is None:
1276 return groups
1285 return groups
1277 cur_gr = self.parent_group
1286 cur_gr = self.parent_group
1278 groups.insert(0, cur_gr)
1287 groups.insert(0, cur_gr)
1279 cnt = 0
1288 cnt = 0
1280 while 1:
1289 while 1:
1281 cnt += 1
1290 cnt += 1
1282 gr = getattr(cur_gr, 'parent_group', None)
1291 gr = getattr(cur_gr, 'parent_group', None)
1283 cur_gr = cur_gr.parent_group
1292 cur_gr = cur_gr.parent_group
1284 if gr is None:
1293 if gr is None:
1285 break
1294 break
1286 if cnt == parents_recursion_limit:
1295 if cnt == parents_recursion_limit:
1287 # this will prevent accidental infinit loops
1296 # this will prevent accidental infinit loops
1288 log.error('group nested more than %s' %
1297 log.error('group nested more than %s' %
1289 parents_recursion_limit)
1298 parents_recursion_limit)
1290 break
1299 break
1291
1300
1292 groups.insert(0, gr)
1301 groups.insert(0, gr)
1293 return groups
1302 return groups
1294
1303
1295 @property
1304 @property
1296 def children(self):
1305 def children(self):
1297 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1306 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1298
1307
1299 @property
1308 @property
1300 def name(self):
1309 def name(self):
1301 return self.group_name.split(RepoGroup.url_sep())[-1]
1310 return self.group_name.split(RepoGroup.url_sep())[-1]
1302
1311
1303 @property
1312 @property
1304 def full_path(self):
1313 def full_path(self):
1305 return self.group_name
1314 return self.group_name
1306
1315
1307 @property
1316 @property
1308 def full_path_splitted(self):
1317 def full_path_splitted(self):
1309 return self.group_name.split(RepoGroup.url_sep())
1318 return self.group_name.split(RepoGroup.url_sep())
1310
1319
1311 @property
1320 @property
1312 def repositories(self):
1321 def repositories(self):
1313 return Repository.query()\
1322 return Repository.query()\
1314 .filter(Repository.group == self)\
1323 .filter(Repository.group == self)\
1315 .order_by(Repository.repo_name)
1324 .order_by(Repository.repo_name)
1316
1325
1317 @property
1326 @property
1318 def repositories_recursive_count(self):
1327 def repositories_recursive_count(self):
1319 cnt = self.repositories.count()
1328 cnt = self.repositories.count()
1320
1329
1321 def children_count(group):
1330 def children_count(group):
1322 cnt = 0
1331 cnt = 0
1323 for child in group.children:
1332 for child in group.children:
1324 cnt += child.repositories.count()
1333 cnt += child.repositories.count()
1325 cnt += children_count(child)
1334 cnt += children_count(child)
1326 return cnt
1335 return cnt
1327
1336
1328 return cnt + children_count(self)
1337 return cnt + children_count(self)
1329
1338
1330 def recursive_groups_and_repos(self):
1339 def recursive_groups_and_repos(self):
1331 """
1340 """
1332 Recursive return all groups, with repositories in those groups
1341 Recursive return all groups, with repositories in those groups
1333 """
1342 """
1334 all_ = []
1343 all_ = []
1335
1344
1336 def _get_members(root_gr):
1345 def _get_members(root_gr):
1337 for r in root_gr.repositories:
1346 for r in root_gr.repositories:
1338 all_.append(r)
1347 all_.append(r)
1339 childs = root_gr.children.all()
1348 childs = root_gr.children.all()
1340 if childs:
1349 if childs:
1341 for gr in childs:
1350 for gr in childs:
1342 all_.append(gr)
1351 all_.append(gr)
1343 _get_members(gr)
1352 _get_members(gr)
1344
1353
1345 _get_members(self)
1354 _get_members(self)
1346 return [self] + all_
1355 return [self] + all_
1347
1356
1348 def get_new_name(self, group_name):
1357 def get_new_name(self, group_name):
1349 """
1358 """
1350 returns new full group name based on parent and new name
1359 returns new full group name based on parent and new name
1351
1360
1352 :param group_name:
1361 :param group_name:
1353 """
1362 """
1354 path_prefix = (self.parent_group.full_path_splitted if
1363 path_prefix = (self.parent_group.full_path_splitted if
1355 self.parent_group else [])
1364 self.parent_group else [])
1356 return RepoGroup.url_sep().join(path_prefix + [group_name])
1365 return RepoGroup.url_sep().join(path_prefix + [group_name])
1357
1366
1358
1367
1359 class Permission(Base, BaseModel):
1368 class Permission(Base, BaseModel):
1360 __tablename__ = 'permissions'
1369 __tablename__ = 'permissions'
1361 __table_args__ = (
1370 __table_args__ = (
1362 Index('p_perm_name_idx', 'permission_name'),
1371 Index('p_perm_name_idx', 'permission_name'),
1363 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1372 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1364 'mysql_charset': 'utf8'},
1373 'mysql_charset': 'utf8'},
1365 )
1374 )
1366 PERMS = [
1375 PERMS = [
1367 ('repository.none', _('Repository no access')),
1376 ('repository.none', _('Repository no access')),
1368 ('repository.read', _('Repository read access')),
1377 ('repository.read', _('Repository read access')),
1369 ('repository.write', _('Repository write access')),
1378 ('repository.write', _('Repository write access')),
1370 ('repository.admin', _('Repository admin access')),
1379 ('repository.admin', _('Repository admin access')),
1371
1380
1372 ('group.none', _('Repositories Group no access')),
1381 ('group.none', _('Repositories Group no access')),
1373 ('group.read', _('Repositories Group read access')),
1382 ('group.read', _('Repositories Group read access')),
1374 ('group.write', _('Repositories Group write access')),
1383 ('group.write', _('Repositories Group write access')),
1375 ('group.admin', _('Repositories Group admin access')),
1384 ('group.admin', _('Repositories Group admin access')),
1376
1385
1377 ('hg.admin', _('RhodeCode Administrator')),
1386 ('hg.admin', _('RhodeCode Administrator')),
1378 ('hg.create.none', _('Repository creation disabled')),
1387 ('hg.create.none', _('Repository creation disabled')),
1379 ('hg.create.repository', _('Repository creation enabled')),
1388 ('hg.create.repository', _('Repository creation enabled')),
1380 ('hg.fork.none', _('Repository forking disabled')),
1389 ('hg.fork.none', _('Repository forking disabled')),
1381 ('hg.fork.repository', _('Repository forking enabled')),
1390 ('hg.fork.repository', _('Repository forking enabled')),
1382 ('hg.register.none', _('Register disabled')),
1391 ('hg.register.none', _('Register disabled')),
1383 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1392 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1384 'with manual activation')),
1393 'with manual activation')),
1385
1394
1386 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1395 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1387 'with auto activation')),
1396 'with auto activation')),
1388 ]
1397 ]
1389
1398
1390 # defines which permissions are more important higher the more important
1399 # defines which permissions are more important higher the more important
1391 PERM_WEIGHTS = {
1400 PERM_WEIGHTS = {
1392 'repository.none': 0,
1401 'repository.none': 0,
1393 'repository.read': 1,
1402 'repository.read': 1,
1394 'repository.write': 3,
1403 'repository.write': 3,
1395 'repository.admin': 4,
1404 'repository.admin': 4,
1396
1405
1397 'group.none': 0,
1406 'group.none': 0,
1398 'group.read': 1,
1407 'group.read': 1,
1399 'group.write': 3,
1408 'group.write': 3,
1400 'group.admin': 4,
1409 'group.admin': 4,
1401
1410
1402 'hg.fork.none': 0,
1411 'hg.fork.none': 0,
1403 'hg.fork.repository': 1,
1412 'hg.fork.repository': 1,
1404 'hg.create.none': 0,
1413 'hg.create.none': 0,
1405 'hg.create.repository':1
1414 'hg.create.repository':1
1406 }
1415 }
1407
1416
1408 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1417 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1409 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1418 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1410 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1419 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1411
1420
1412 def __unicode__(self):
1421 def __unicode__(self):
1413 return u"<%s('%s:%s')>" % (
1422 return u"<%s('%s:%s')>" % (
1414 self.__class__.__name__, self.permission_id, self.permission_name
1423 self.__class__.__name__, self.permission_id, self.permission_name
1415 )
1424 )
1416
1425
1417 @classmethod
1426 @classmethod
1418 def get_by_key(cls, key):
1427 def get_by_key(cls, key):
1419 return cls.query().filter(cls.permission_name == key).scalar()
1428 return cls.query().filter(cls.permission_name == key).scalar()
1420
1429
1421 @classmethod
1430 @classmethod
1422 def get_default_perms(cls, default_user_id):
1431 def get_default_perms(cls, default_user_id):
1423 q = Session().query(UserRepoToPerm, Repository, cls)\
1432 q = Session().query(UserRepoToPerm, Repository, cls)\
1424 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1433 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1425 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1434 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1426 .filter(UserRepoToPerm.user_id == default_user_id)
1435 .filter(UserRepoToPerm.user_id == default_user_id)
1427
1436
1428 return q.all()
1437 return q.all()
1429
1438
1430 @classmethod
1439 @classmethod
1431 def get_default_group_perms(cls, default_user_id):
1440 def get_default_group_perms(cls, default_user_id):
1432 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1441 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1433 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1442 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1434 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1443 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1435 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1444 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1436
1445
1437 return q.all()
1446 return q.all()
1438
1447
1439
1448
1440 class UserRepoToPerm(Base, BaseModel):
1449 class UserRepoToPerm(Base, BaseModel):
1441 __tablename__ = 'repo_to_perm'
1450 __tablename__ = 'repo_to_perm'
1442 __table_args__ = (
1451 __table_args__ = (
1443 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1452 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1444 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1453 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1445 'mysql_charset': 'utf8'}
1454 'mysql_charset': 'utf8'}
1446 )
1455 )
1447 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1456 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1448 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1457 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1449 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1458 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1450 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1459 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1451
1460
1452 user = relationship('User')
1461 user = relationship('User')
1453 repository = relationship('Repository')
1462 repository = relationship('Repository')
1454 permission = relationship('Permission')
1463 permission = relationship('Permission')
1455
1464
1456 @classmethod
1465 @classmethod
1457 def create(cls, user, repository, permission):
1466 def create(cls, user, repository, permission):
1458 n = cls()
1467 n = cls()
1459 n.user = user
1468 n.user = user
1460 n.repository = repository
1469 n.repository = repository
1461 n.permission = permission
1470 n.permission = permission
1462 Session().add(n)
1471 Session().add(n)
1463 return n
1472 return n
1464
1473
1465 def __unicode__(self):
1474 def __unicode__(self):
1466 return u'<user:%s => %s >' % (self.user, self.repository)
1475 return u'<user:%s => %s >' % (self.user, self.repository)
1467
1476
1468
1477
1469 class UserToPerm(Base, BaseModel):
1478 class UserToPerm(Base, BaseModel):
1470 __tablename__ = 'user_to_perm'
1479 __tablename__ = 'user_to_perm'
1471 __table_args__ = (
1480 __table_args__ = (
1472 UniqueConstraint('user_id', 'permission_id'),
1481 UniqueConstraint('user_id', 'permission_id'),
1473 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1482 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1474 'mysql_charset': 'utf8'}
1483 'mysql_charset': 'utf8'}
1475 )
1484 )
1476 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1485 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1477 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1486 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1478 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1487 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1479
1488
1480 user = relationship('User')
1489 user = relationship('User')
1481 permission = relationship('Permission', lazy='joined')
1490 permission = relationship('Permission', lazy='joined')
1482
1491
1483
1492
1484 class UsersGroupRepoToPerm(Base, BaseModel):
1493 class UsersGroupRepoToPerm(Base, BaseModel):
1485 __tablename__ = 'users_group_repo_to_perm'
1494 __tablename__ = 'users_group_repo_to_perm'
1486 __table_args__ = (
1495 __table_args__ = (
1487 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1496 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1488 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1497 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1489 'mysql_charset': 'utf8'}
1498 'mysql_charset': 'utf8'}
1490 )
1499 )
1491 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1500 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1492 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1501 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1493 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1502 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1494 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1503 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1495
1504
1496 users_group = relationship('UsersGroup')
1505 users_group = relationship('UsersGroup')
1497 permission = relationship('Permission')
1506 permission = relationship('Permission')
1498 repository = relationship('Repository')
1507 repository = relationship('Repository')
1499
1508
1500 @classmethod
1509 @classmethod
1501 def create(cls, users_group, repository, permission):
1510 def create(cls, users_group, repository, permission):
1502 n = cls()
1511 n = cls()
1503 n.users_group = users_group
1512 n.users_group = users_group
1504 n.repository = repository
1513 n.repository = repository
1505 n.permission = permission
1514 n.permission = permission
1506 Session().add(n)
1515 Session().add(n)
1507 return n
1516 return n
1508
1517
1509 def __unicode__(self):
1518 def __unicode__(self):
1510 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1519 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1511
1520
1512
1521
1513 class UsersGroupToPerm(Base, BaseModel):
1522 class UsersGroupToPerm(Base, BaseModel):
1514 __tablename__ = 'users_group_to_perm'
1523 __tablename__ = 'users_group_to_perm'
1515 __table_args__ = (
1524 __table_args__ = (
1516 UniqueConstraint('users_group_id', 'permission_id',),
1525 UniqueConstraint('users_group_id', 'permission_id',),
1517 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1526 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1518 'mysql_charset': 'utf8'}
1527 'mysql_charset': 'utf8'}
1519 )
1528 )
1520 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1529 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1521 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1530 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1522 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1531 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1523
1532
1524 users_group = relationship('UsersGroup')
1533 users_group = relationship('UsersGroup')
1525 permission = relationship('Permission')
1534 permission = relationship('Permission')
1526
1535
1527
1536
1528 class UserRepoGroupToPerm(Base, BaseModel):
1537 class UserRepoGroupToPerm(Base, BaseModel):
1529 __tablename__ = 'user_repo_group_to_perm'
1538 __tablename__ = 'user_repo_group_to_perm'
1530 __table_args__ = (
1539 __table_args__ = (
1531 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1540 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1532 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1541 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1533 'mysql_charset': 'utf8'}
1542 'mysql_charset': 'utf8'}
1534 )
1543 )
1535
1544
1536 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1545 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1537 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1546 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1538 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1547 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1539 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1548 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1540
1549
1541 user = relationship('User')
1550 user = relationship('User')
1542 group = relationship('RepoGroup')
1551 group = relationship('RepoGroup')
1543 permission = relationship('Permission')
1552 permission = relationship('Permission')
1544
1553
1545
1554
1546 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1555 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1547 __tablename__ = 'users_group_repo_group_to_perm'
1556 __tablename__ = 'users_group_repo_group_to_perm'
1548 __table_args__ = (
1557 __table_args__ = (
1549 UniqueConstraint('users_group_id', 'group_id'),
1558 UniqueConstraint('users_group_id', 'group_id'),
1550 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1559 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1551 'mysql_charset': 'utf8'}
1560 'mysql_charset': 'utf8'}
1552 )
1561 )
1553
1562
1554 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)
1563 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)
1555 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1564 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1556 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1565 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1557 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1566 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1558
1567
1559 users_group = relationship('UsersGroup')
1568 users_group = relationship('UsersGroup')
1560 permission = relationship('Permission')
1569 permission = relationship('Permission')
1561 group = relationship('RepoGroup')
1570 group = relationship('RepoGroup')
1562
1571
1563
1572
1564 class Statistics(Base, BaseModel):
1573 class Statistics(Base, BaseModel):
1565 __tablename__ = 'statistics'
1574 __tablename__ = 'statistics'
1566 __table_args__ = (
1575 __table_args__ = (
1567 UniqueConstraint('repository_id'),
1576 UniqueConstraint('repository_id'),
1568 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1569 'mysql_charset': 'utf8'}
1578 'mysql_charset': 'utf8'}
1570 )
1579 )
1571 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1580 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1572 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1581 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1573 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1582 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1574 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1583 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1575 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1584 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1576 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1585 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1577
1586
1578 repository = relationship('Repository', single_parent=True)
1587 repository = relationship('Repository', single_parent=True)
1579
1588
1580
1589
1581 class UserFollowing(Base, BaseModel):
1590 class UserFollowing(Base, BaseModel):
1582 __tablename__ = 'user_followings'
1591 __tablename__ = 'user_followings'
1583 __table_args__ = (
1592 __table_args__ = (
1584 UniqueConstraint('user_id', 'follows_repository_id'),
1593 UniqueConstraint('user_id', 'follows_repository_id'),
1585 UniqueConstraint('user_id', 'follows_user_id'),
1594 UniqueConstraint('user_id', 'follows_user_id'),
1586 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1595 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1587 'mysql_charset': 'utf8'}
1596 'mysql_charset': 'utf8'}
1588 )
1597 )
1589
1598
1590 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1599 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1591 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1600 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1592 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1601 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1593 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1602 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1594 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1603 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1595
1604
1596 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1605 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1597
1606
1598 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1607 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1599 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1608 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1600
1609
1601 @classmethod
1610 @classmethod
1602 def get_repo_followers(cls, repo_id):
1611 def get_repo_followers(cls, repo_id):
1603 return cls.query().filter(cls.follows_repo_id == repo_id)
1612 return cls.query().filter(cls.follows_repo_id == repo_id)
1604
1613
1605
1614
1606 class CacheInvalidation(Base, BaseModel):
1615 class CacheInvalidation(Base, BaseModel):
1607 __tablename__ = 'cache_invalidation'
1616 __tablename__ = 'cache_invalidation'
1608 __table_args__ = (
1617 __table_args__ = (
1609 UniqueConstraint('cache_key'),
1618 UniqueConstraint('cache_key'),
1610 Index('key_idx', 'cache_key'),
1619 Index('key_idx', 'cache_key'),
1611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1620 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1612 'mysql_charset': 'utf8'},
1621 'mysql_charset': 'utf8'},
1613 )
1622 )
1614 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1623 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1615 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1624 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1616 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1625 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1617 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1626 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1618
1627
1619 def __init__(self, cache_key, cache_args=''):
1628 def __init__(self, cache_key, cache_args=''):
1620 self.cache_key = cache_key
1629 self.cache_key = cache_key
1621 self.cache_args = cache_args
1630 self.cache_args = cache_args
1622 self.cache_active = False
1631 self.cache_active = False
1623
1632
1624 def __unicode__(self):
1633 def __unicode__(self):
1625 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1634 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1626 self.cache_id, self.cache_key)
1635 self.cache_id, self.cache_key)
1627
1636
1628 @property
1637 @property
1629 def prefix(self):
1638 def prefix(self):
1630 _split = self.cache_key.split(self.cache_args, 1)
1639 _split = self.cache_key.split(self.cache_args, 1)
1631 if _split and len(_split) == 2:
1640 if _split and len(_split) == 2:
1632 return _split[0]
1641 return _split[0]
1633 return ''
1642 return ''
1634
1643
1635 @classmethod
1644 @classmethod
1636 def clear_cache(cls):
1645 def clear_cache(cls):
1637 cls.query().delete()
1646 cls.query().delete()
1638
1647
1639 @classmethod
1648 @classmethod
1640 def _get_key(cls, key):
1649 def _get_key(cls, key):
1641 """
1650 """
1642 Wrapper for generating a key, together with a prefix
1651 Wrapper for generating a key, together with a prefix
1643
1652
1644 :param key:
1653 :param key:
1645 """
1654 """
1646 import rhodecode
1655 import rhodecode
1647 prefix = ''
1656 prefix = ''
1648 org_key = key
1657 org_key = key
1649 iid = rhodecode.CONFIG.get('instance_id')
1658 iid = rhodecode.CONFIG.get('instance_id')
1650 if iid:
1659 if iid:
1651 prefix = iid
1660 prefix = iid
1652
1661
1653 return "%s%s" % (prefix, key), prefix, org_key
1662 return "%s%s" % (prefix, key), prefix, org_key
1654
1663
1655 @classmethod
1664 @classmethod
1656 def get_by_key(cls, key):
1665 def get_by_key(cls, key):
1657 return cls.query().filter(cls.cache_key == key).scalar()
1666 return cls.query().filter(cls.cache_key == key).scalar()
1658
1667
1659 @classmethod
1668 @classmethod
1660 def get_by_repo_name(cls, repo_name):
1669 def get_by_repo_name(cls, repo_name):
1661 return cls.query().filter(cls.cache_args == repo_name).all()
1670 return cls.query().filter(cls.cache_args == repo_name).all()
1662
1671
1663 @classmethod
1672 @classmethod
1664 def _get_or_create_key(cls, key, repo_name, commit=True):
1673 def _get_or_create_key(cls, key, repo_name, commit=True):
1665 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1674 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1666 if not inv_obj:
1675 if not inv_obj:
1667 try:
1676 try:
1668 inv_obj = CacheInvalidation(key, repo_name)
1677 inv_obj = CacheInvalidation(key, repo_name)
1669 Session().add(inv_obj)
1678 Session().add(inv_obj)
1670 if commit:
1679 if commit:
1671 Session().commit()
1680 Session().commit()
1672 except Exception:
1681 except Exception:
1673 log.error(traceback.format_exc())
1682 log.error(traceback.format_exc())
1674 Session().rollback()
1683 Session().rollback()
1675 return inv_obj
1684 return inv_obj
1676
1685
1677 @classmethod
1686 @classmethod
1678 def invalidate(cls, key):
1687 def invalidate(cls, key):
1679 """
1688 """
1680 Returns Invalidation object if this given key should be invalidated
1689 Returns Invalidation object if this given key should be invalidated
1681 None otherwise. `cache_active = False` means that this cache
1690 None otherwise. `cache_active = False` means that this cache
1682 state is not valid and needs to be invalidated
1691 state is not valid and needs to be invalidated
1683
1692
1684 :param key:
1693 :param key:
1685 """
1694 """
1686 repo_name = key
1695 repo_name = key
1687 repo_name = remove_suffix(repo_name, '_README')
1696 repo_name = remove_suffix(repo_name, '_README')
1688 repo_name = remove_suffix(repo_name, '_RSS')
1697 repo_name = remove_suffix(repo_name, '_RSS')
1689 repo_name = remove_suffix(repo_name, '_ATOM')
1698 repo_name = remove_suffix(repo_name, '_ATOM')
1690
1699
1691 # adds instance prefix
1700 # adds instance prefix
1692 key, _prefix, _org_key = cls._get_key(key)
1701 key, _prefix, _org_key = cls._get_key(key)
1693 inv = cls._get_or_create_key(key, repo_name)
1702 inv = cls._get_or_create_key(key, repo_name)
1694
1703
1695 if inv and inv.cache_active is False:
1704 if inv and inv.cache_active is False:
1696 return inv
1705 return inv
1697
1706
1698 @classmethod
1707 @classmethod
1699 def set_invalidate(cls, key=None, repo_name=None):
1708 def set_invalidate(cls, key=None, repo_name=None):
1700 """
1709 """
1701 Mark this Cache key for invalidation, either by key or whole
1710 Mark this Cache key for invalidation, either by key or whole
1702 cache sets based on repo_name
1711 cache sets based on repo_name
1703
1712
1704 :param key:
1713 :param key:
1705 """
1714 """
1706 invalidated_keys = []
1715 invalidated_keys = []
1707 if key:
1716 if key:
1708 key, _prefix, _org_key = cls._get_key(key)
1717 key, _prefix, _org_key = cls._get_key(key)
1709 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1718 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1710 elif repo_name:
1719 elif repo_name:
1711 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1720 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1712
1721
1713 try:
1722 try:
1714 for inv_obj in inv_objs:
1723 for inv_obj in inv_objs:
1715 inv_obj.cache_active = False
1724 inv_obj.cache_active = False
1716 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1725 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1717 % (inv_obj, key, repo_name))
1726 % (inv_obj, key, repo_name))
1718 invalidated_keys.append(inv_obj.cache_key)
1727 invalidated_keys.append(inv_obj.cache_key)
1719 Session().add(inv_obj)
1728 Session().add(inv_obj)
1720 Session().commit()
1729 Session().commit()
1721 except Exception:
1730 except Exception:
1722 log.error(traceback.format_exc())
1731 log.error(traceback.format_exc())
1723 Session().rollback()
1732 Session().rollback()
1724 return invalidated_keys
1733 return invalidated_keys
1725
1734
1726 @classmethod
1735 @classmethod
1727 def set_valid(cls, key):
1736 def set_valid(cls, key):
1728 """
1737 """
1729 Mark this cache key as active and currently cached
1738 Mark this cache key as active and currently cached
1730
1739
1731 :param key:
1740 :param key:
1732 """
1741 """
1733 inv_obj = cls.get_by_key(key)
1742 inv_obj = cls.get_by_key(key)
1734 inv_obj.cache_active = True
1743 inv_obj.cache_active = True
1735 Session().add(inv_obj)
1744 Session().add(inv_obj)
1736 Session().commit()
1745 Session().commit()
1737
1746
1738 @classmethod
1747 @classmethod
1739 def get_cache_map(cls):
1748 def get_cache_map(cls):
1740
1749
1741 class cachemapdict(dict):
1750 class cachemapdict(dict):
1742
1751
1743 def __init__(self, *args, **kwargs):
1752 def __init__(self, *args, **kwargs):
1744 fixkey = kwargs.get('fixkey')
1753 fixkey = kwargs.get('fixkey')
1745 if fixkey:
1754 if fixkey:
1746 del kwargs['fixkey']
1755 del kwargs['fixkey']
1747 self.fixkey = fixkey
1756 self.fixkey = fixkey
1748 super(cachemapdict, self).__init__(*args, **kwargs)
1757 super(cachemapdict, self).__init__(*args, **kwargs)
1749
1758
1750 def __getattr__(self, name):
1759 def __getattr__(self, name):
1751 key = name
1760 key = name
1752 if self.fixkey:
1761 if self.fixkey:
1753 key, _prefix, _org_key = cls._get_key(key)
1762 key, _prefix, _org_key = cls._get_key(key)
1754 if key in self.__dict__:
1763 if key in self.__dict__:
1755 return self.__dict__[key]
1764 return self.__dict__[key]
1756 else:
1765 else:
1757 return self[key]
1766 return self[key]
1758
1767
1759 def __getitem__(self, key):
1768 def __getitem__(self, key):
1760 if self.fixkey:
1769 if self.fixkey:
1761 key, _prefix, _org_key = cls._get_key(key)
1770 key, _prefix, _org_key = cls._get_key(key)
1762 try:
1771 try:
1763 return super(cachemapdict, self).__getitem__(key)
1772 return super(cachemapdict, self).__getitem__(key)
1764 except KeyError:
1773 except KeyError:
1765 return
1774 return
1766
1775
1767 cache_map = cachemapdict(fixkey=True)
1776 cache_map = cachemapdict(fixkey=True)
1768 for obj in cls.query().all():
1777 for obj in cls.query().all():
1769 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1778 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1770 return cache_map
1779 return cache_map
1771
1780
1772
1781
1773 class ChangesetComment(Base, BaseModel):
1782 class ChangesetComment(Base, BaseModel):
1774 __tablename__ = 'changeset_comments'
1783 __tablename__ = 'changeset_comments'
1775 __table_args__ = (
1784 __table_args__ = (
1776 Index('cc_revision_idx', 'revision'),
1785 Index('cc_revision_idx', 'revision'),
1777 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1786 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1778 'mysql_charset': 'utf8'},
1787 'mysql_charset': 'utf8'},
1779 )
1788 )
1780 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1789 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1781 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1790 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1782 revision = Column('revision', String(40), nullable=True)
1791 revision = Column('revision', String(40), nullable=True)
1783 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1792 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1784 line_no = Column('line_no', Unicode(10), nullable=True)
1793 line_no = Column('line_no', Unicode(10), nullable=True)
1785 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1794 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1786 f_path = Column('f_path', Unicode(1000), nullable=True)
1795 f_path = Column('f_path', Unicode(1000), nullable=True)
1787 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1796 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1788 text = Column('text', UnicodeText(25000), nullable=False)
1797 text = Column('text', UnicodeText(25000), nullable=False)
1789 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1798 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1790 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1799 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1791
1800
1792 author = relationship('User', lazy='joined')
1801 author = relationship('User', lazy='joined')
1793 repo = relationship('Repository')
1802 repo = relationship('Repository')
1794 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1803 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1795 pull_request = relationship('PullRequest', lazy='joined')
1804 pull_request = relationship('PullRequest', lazy='joined')
1796
1805
1797 @classmethod
1806 @classmethod
1798 def get_users(cls, revision=None, pull_request_id=None):
1807 def get_users(cls, revision=None, pull_request_id=None):
1799 """
1808 """
1800 Returns user associated with this ChangesetComment. ie those
1809 Returns user associated with this ChangesetComment. ie those
1801 who actually commented
1810 who actually commented
1802
1811
1803 :param cls:
1812 :param cls:
1804 :param revision:
1813 :param revision:
1805 """
1814 """
1806 q = Session().query(User)\
1815 q = Session().query(User)\
1807 .join(ChangesetComment.author)
1816 .join(ChangesetComment.author)
1808 if revision:
1817 if revision:
1809 q = q.filter(cls.revision == revision)
1818 q = q.filter(cls.revision == revision)
1810 elif pull_request_id:
1819 elif pull_request_id:
1811 q = q.filter(cls.pull_request_id == pull_request_id)
1820 q = q.filter(cls.pull_request_id == pull_request_id)
1812 return q.all()
1821 return q.all()
1813
1822
1814
1823
1815 class ChangesetStatus(Base, BaseModel):
1824 class ChangesetStatus(Base, BaseModel):
1816 __tablename__ = 'changeset_statuses'
1825 __tablename__ = 'changeset_statuses'
1817 __table_args__ = (
1826 __table_args__ = (
1818 Index('cs_revision_idx', 'revision'),
1827 Index('cs_revision_idx', 'revision'),
1819 Index('cs_version_idx', 'version'),
1828 Index('cs_version_idx', 'version'),
1820 UniqueConstraint('repo_id', 'revision', 'version'),
1829 UniqueConstraint('repo_id', 'revision', 'version'),
1821 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1830 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1822 'mysql_charset': 'utf8'}
1831 'mysql_charset': 'utf8'}
1823 )
1832 )
1824 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1833 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1825 STATUS_APPROVED = 'approved'
1834 STATUS_APPROVED = 'approved'
1826 STATUS_REJECTED = 'rejected'
1835 STATUS_REJECTED = 'rejected'
1827 STATUS_UNDER_REVIEW = 'under_review'
1836 STATUS_UNDER_REVIEW = 'under_review'
1828
1837
1829 STATUSES = [
1838 STATUSES = [
1830 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1839 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1831 (STATUS_APPROVED, _("Approved")),
1840 (STATUS_APPROVED, _("Approved")),
1832 (STATUS_REJECTED, _("Rejected")),
1841 (STATUS_REJECTED, _("Rejected")),
1833 (STATUS_UNDER_REVIEW, _("Under Review")),
1842 (STATUS_UNDER_REVIEW, _("Under Review")),
1834 ]
1843 ]
1835
1844
1836 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1845 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1837 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1846 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1838 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1847 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1839 revision = Column('revision', String(40), nullable=False)
1848 revision = Column('revision', String(40), nullable=False)
1840 status = Column('status', String(128), nullable=False, default=DEFAULT)
1849 status = Column('status', String(128), nullable=False, default=DEFAULT)
1841 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1850 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1842 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1851 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1843 version = Column('version', Integer(), nullable=False, default=0)
1852 version = Column('version', Integer(), nullable=False, default=0)
1844 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1853 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1845
1854
1846 author = relationship('User', lazy='joined')
1855 author = relationship('User', lazy='joined')
1847 repo = relationship('Repository')
1856 repo = relationship('Repository')
1848 comment = relationship('ChangesetComment', lazy='joined')
1857 comment = relationship('ChangesetComment', lazy='joined')
1849 pull_request = relationship('PullRequest', lazy='joined')
1858 pull_request = relationship('PullRequest', lazy='joined')
1850
1859
1851 def __unicode__(self):
1860 def __unicode__(self):
1852 return u"<%s('%s:%s')>" % (
1861 return u"<%s('%s:%s')>" % (
1853 self.__class__.__name__,
1862 self.__class__.__name__,
1854 self.status, self.author
1863 self.status, self.author
1855 )
1864 )
1856
1865
1857 @classmethod
1866 @classmethod
1858 def get_status_lbl(cls, value):
1867 def get_status_lbl(cls, value):
1859 return dict(cls.STATUSES).get(value)
1868 return dict(cls.STATUSES).get(value)
1860
1869
1861 @property
1870 @property
1862 def status_lbl(self):
1871 def status_lbl(self):
1863 return ChangesetStatus.get_status_lbl(self.status)
1872 return ChangesetStatus.get_status_lbl(self.status)
1864
1873
1865
1874
1866 class PullRequest(Base, BaseModel):
1875 class PullRequest(Base, BaseModel):
1867 __tablename__ = 'pull_requests'
1876 __tablename__ = 'pull_requests'
1868 __table_args__ = (
1877 __table_args__ = (
1869 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1878 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1870 'mysql_charset': 'utf8'},
1879 'mysql_charset': 'utf8'},
1871 )
1880 )
1872
1881
1873 STATUS_NEW = u'new'
1882 STATUS_NEW = u'new'
1874 STATUS_OPEN = u'open'
1883 STATUS_OPEN = u'open'
1875 STATUS_CLOSED = u'closed'
1884 STATUS_CLOSED = u'closed'
1876
1885
1877 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1886 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1878 title = Column('title', Unicode(256), nullable=True)
1887 title = Column('title', Unicode(256), nullable=True)
1879 description = Column('description', UnicodeText(10240), nullable=True)
1888 description = Column('description', UnicodeText(10240), nullable=True)
1880 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1889 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1881 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1890 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1882 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1891 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1883 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1892 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1884 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1893 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1885 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1894 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1886 org_ref = Column('org_ref', Unicode(256), nullable=False)
1895 org_ref = Column('org_ref', Unicode(256), nullable=False)
1887 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1896 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1888 other_ref = Column('other_ref', Unicode(256), nullable=False)
1897 other_ref = Column('other_ref', Unicode(256), nullable=False)
1889
1898
1890 @hybrid_property
1899 @hybrid_property
1891 def revisions(self):
1900 def revisions(self):
1892 return self._revisions.split(':')
1901 return self._revisions.split(':')
1893
1902
1894 @revisions.setter
1903 @revisions.setter
1895 def revisions(self, val):
1904 def revisions(self, val):
1896 self._revisions = ':'.join(val)
1905 self._revisions = ':'.join(val)
1897
1906
1898 @property
1907 @property
1899 def org_ref_parts(self):
1908 def org_ref_parts(self):
1900 return self.org_ref.split(':')
1909 return self.org_ref.split(':')
1901
1910
1902 @property
1911 @property
1903 def other_ref_parts(self):
1912 def other_ref_parts(self):
1904 return self.other_ref.split(':')
1913 return self.other_ref.split(':')
1905
1914
1906 author = relationship('User', lazy='joined')
1915 author = relationship('User', lazy='joined')
1907 reviewers = relationship('PullRequestReviewers',
1916 reviewers = relationship('PullRequestReviewers',
1908 cascade="all, delete, delete-orphan")
1917 cascade="all, delete, delete-orphan")
1909 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1918 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1910 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1919 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1911 statuses = relationship('ChangesetStatus')
1920 statuses = relationship('ChangesetStatus')
1912 comments = relationship('ChangesetComment',
1921 comments = relationship('ChangesetComment',
1913 cascade="all, delete, delete-orphan")
1922 cascade="all, delete, delete-orphan")
1914
1923
1915 def is_closed(self):
1924 def is_closed(self):
1916 return self.status == self.STATUS_CLOSED
1925 return self.status == self.STATUS_CLOSED
1917
1926
1918 @property
1927 @property
1919 def last_review_status(self):
1928 def last_review_status(self):
1920 return self.statuses[-1].status if self.statuses else ''
1929 return self.statuses[-1].status if self.statuses else ''
1921
1930
1922 def __json__(self):
1931 def __json__(self):
1923 return dict(
1932 return dict(
1924 revisions=self.revisions
1933 revisions=self.revisions
1925 )
1934 )
1926
1935
1927
1936
1928 class PullRequestReviewers(Base, BaseModel):
1937 class PullRequestReviewers(Base, BaseModel):
1929 __tablename__ = 'pull_request_reviewers'
1938 __tablename__ = 'pull_request_reviewers'
1930 __table_args__ = (
1939 __table_args__ = (
1931 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1940 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1932 'mysql_charset': 'utf8'},
1941 'mysql_charset': 'utf8'},
1933 )
1942 )
1934
1943
1935 def __init__(self, user=None, pull_request=None):
1944 def __init__(self, user=None, pull_request=None):
1936 self.user = user
1945 self.user = user
1937 self.pull_request = pull_request
1946 self.pull_request = pull_request
1938
1947
1939 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1948 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1940 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1949 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1941 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1950 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1942
1951
1943 user = relationship('User')
1952 user = relationship('User')
1944 pull_request = relationship('PullRequest')
1953 pull_request = relationship('PullRequest')
1945
1954
1946
1955
1947 class Notification(Base, BaseModel):
1956 class Notification(Base, BaseModel):
1948 __tablename__ = 'notifications'
1957 __tablename__ = 'notifications'
1949 __table_args__ = (
1958 __table_args__ = (
1950 Index('notification_type_idx', 'type'),
1959 Index('notification_type_idx', 'type'),
1951 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1960 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1952 'mysql_charset': 'utf8'},
1961 'mysql_charset': 'utf8'},
1953 )
1962 )
1954
1963
1955 TYPE_CHANGESET_COMMENT = u'cs_comment'
1964 TYPE_CHANGESET_COMMENT = u'cs_comment'
1956 TYPE_MESSAGE = u'message'
1965 TYPE_MESSAGE = u'message'
1957 TYPE_MENTION = u'mention'
1966 TYPE_MENTION = u'mention'
1958 TYPE_REGISTRATION = u'registration'
1967 TYPE_REGISTRATION = u'registration'
1959 TYPE_PULL_REQUEST = u'pull_request'
1968 TYPE_PULL_REQUEST = u'pull_request'
1960 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1969 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1961
1970
1962 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1971 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1963 subject = Column('subject', Unicode(512), nullable=True)
1972 subject = Column('subject', Unicode(512), nullable=True)
1964 body = Column('body', UnicodeText(50000), nullable=True)
1973 body = Column('body', UnicodeText(50000), nullable=True)
1965 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1974 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1966 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1975 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1967 type_ = Column('type', Unicode(256))
1976 type_ = Column('type', Unicode(256))
1968
1977
1969 created_by_user = relationship('User')
1978 created_by_user = relationship('User')
1970 notifications_to_users = relationship('UserNotification', lazy='joined',
1979 notifications_to_users = relationship('UserNotification', lazy='joined',
1971 cascade="all, delete, delete-orphan")
1980 cascade="all, delete, delete-orphan")
1972
1981
1973 @property
1982 @property
1974 def recipients(self):
1983 def recipients(self):
1975 return [x.user for x in UserNotification.query()\
1984 return [x.user for x in UserNotification.query()\
1976 .filter(UserNotification.notification == self)\
1985 .filter(UserNotification.notification == self)\
1977 .order_by(UserNotification.user_id.asc()).all()]
1986 .order_by(UserNotification.user_id.asc()).all()]
1978
1987
1979 @classmethod
1988 @classmethod
1980 def create(cls, created_by, subject, body, recipients, type_=None):
1989 def create(cls, created_by, subject, body, recipients, type_=None):
1981 if type_ is None:
1990 if type_ is None:
1982 type_ = Notification.TYPE_MESSAGE
1991 type_ = Notification.TYPE_MESSAGE
1983
1992
1984 notification = cls()
1993 notification = cls()
1985 notification.created_by_user = created_by
1994 notification.created_by_user = created_by
1986 notification.subject = subject
1995 notification.subject = subject
1987 notification.body = body
1996 notification.body = body
1988 notification.type_ = type_
1997 notification.type_ = type_
1989 notification.created_on = datetime.datetime.now()
1998 notification.created_on = datetime.datetime.now()
1990
1999
1991 for u in recipients:
2000 for u in recipients:
1992 assoc = UserNotification()
2001 assoc = UserNotification()
1993 assoc.notification = notification
2002 assoc.notification = notification
1994 u.notifications.append(assoc)
2003 u.notifications.append(assoc)
1995 Session().add(notification)
2004 Session().add(notification)
1996 return notification
2005 return notification
1997
2006
1998 @property
2007 @property
1999 def description(self):
2008 def description(self):
2000 from rhodecode.model.notification import NotificationModel
2009 from rhodecode.model.notification import NotificationModel
2001 return NotificationModel().make_description(self)
2010 return NotificationModel().make_description(self)
2002
2011
2003
2012
2004 class UserNotification(Base, BaseModel):
2013 class UserNotification(Base, BaseModel):
2005 __tablename__ = 'user_to_notification'
2014 __tablename__ = 'user_to_notification'
2006 __table_args__ = (
2015 __table_args__ = (
2007 UniqueConstraint('user_id', 'notification_id'),
2016 UniqueConstraint('user_id', 'notification_id'),
2008 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2017 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2009 'mysql_charset': 'utf8'}
2018 'mysql_charset': 'utf8'}
2010 )
2019 )
2011 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2020 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2012 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2021 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2013 read = Column('read', Boolean, default=False)
2022 read = Column('read', Boolean, default=False)
2014 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2023 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2015
2024
2016 user = relationship('User', lazy="joined")
2025 user = relationship('User', lazy="joined")
2017 notification = relationship('Notification', lazy="joined",
2026 notification = relationship('Notification', lazy="joined",
2018 order_by=lambda: Notification.created_on.desc(),)
2027 order_by=lambda: Notification.created_on.desc(),)
2019
2028
2020 def mark_as_read(self):
2029 def mark_as_read(self):
2021 self.read = True
2030 self.read = True
2022 Session().add(self)
2031 Session().add(self)
2023
2032
2024
2033
2025 class DbMigrateVersion(Base, BaseModel):
2034 class DbMigrateVersion(Base, BaseModel):
2026 __tablename__ = 'db_migrate_version'
2035 __tablename__ = 'db_migrate_version'
2027 __table_args__ = (
2036 __table_args__ = (
2028 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2037 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2029 'mysql_charset': 'utf8'},
2038 'mysql_charset': 'utf8'},
2030 )
2039 )
2031 repository_id = Column('repository_id', String(250), primary_key=True)
2040 repository_id = Column('repository_id', String(250), primary_key=True)
2032 repository_path = Column('repository_path', Text)
2041 repository_path = Column('repository_path', Text)
2033 version = Column('version', Integer)
2042 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now