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