##// END OF EJS Templates
print statement cleanup
marcink -
r2278:24095abd beta
parent child Browse files
Show More
@@ -1,821 +1,821 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
37
38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40
40
41 if __platform__ in PLATFORM_WIN:
41 if __platform__ in PLATFORM_WIN:
42 from hashlib import sha256
42 from hashlib import sha256
43 if __platform__ in PLATFORM_OTHERS:
43 if __platform__ in PLATFORM_OTHERS:
44 import bcrypt
44 import bcrypt
45
45
46 from rhodecode.lib.utils2 import str2bool, safe_unicode
46 from rhodecode.lib.utils2 import str2bool, safe_unicode
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
48 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
48 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
49 from rhodecode.lib.auth_ldap import AuthLdap
49 from rhodecode.lib.auth_ldap import AuthLdap
50
50
51 from rhodecode.model import meta
51 from rhodecode.model import meta
52 from rhodecode.model.user import UserModel
52 from rhodecode.model.user import UserModel
53 from rhodecode.model.db import Permission, RhodeCodeSetting, User
53 from rhodecode.model.db import Permission, RhodeCodeSetting, User
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class PasswordGenerator(object):
58 class PasswordGenerator(object):
59 """
59 """
60 This is a simple class for generating password from different sets of
60 This is a simple class for generating password from different sets of
61 characters
61 characters
62 usage::
62 usage::
63
63
64 passwd_gen = PasswordGenerator()
64 passwd_gen = PasswordGenerator()
65 #print 8-letter password containing only big and small letters
65 #print 8-letter password containing only big and small letters
66 of alphabet
66 of alphabet
67 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
67 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
68 """
68 """
69 ALPHABETS_NUM = r'''1234567890'''
69 ALPHABETS_NUM = r'''1234567890'''
70 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
70 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
71 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
71 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
72 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
72 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
73 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
73 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
74 + ALPHABETS_NUM + ALPHABETS_SPECIAL
74 + ALPHABETS_NUM + ALPHABETS_SPECIAL
75 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
75 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
76 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
76 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
77 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
77 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
78 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
78 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
79
79
80 def __init__(self, passwd=''):
80 def __init__(self, passwd=''):
81 self.passwd = passwd
81 self.passwd = passwd
82
82
83 def gen_password(self, length, type_=None):
83 def gen_password(self, length, type_=None):
84 if type_ is None:
84 if type_ is None:
85 type_ = self.ALPHABETS_FULL
85 type_ = self.ALPHABETS_FULL
86 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
86 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
87 return self.passwd
87 return self.passwd
88
88
89
89
90 class RhodeCodeCrypto(object):
90 class RhodeCodeCrypto(object):
91
91
92 @classmethod
92 @classmethod
93 def hash_string(cls, str_):
93 def hash_string(cls, str_):
94 """
94 """
95 Cryptographic function used for password hashing based on pybcrypt
95 Cryptographic function used for password hashing based on pybcrypt
96 or pycrypto in windows
96 or pycrypto in windows
97
97
98 :param password: password to hash
98 :param password: password to hash
99 """
99 """
100 if __platform__ in PLATFORM_WIN:
100 if __platform__ in PLATFORM_WIN:
101 return sha256(str_).hexdigest()
101 return sha256(str_).hexdigest()
102 elif __platform__ in PLATFORM_OTHERS:
102 elif __platform__ in PLATFORM_OTHERS:
103 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
103 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
104 else:
104 else:
105 raise Exception('Unknown or unsupported platform %s' \
105 raise Exception('Unknown or unsupported platform %s' \
106 % __platform__)
106 % __platform__)
107
107
108 @classmethod
108 @classmethod
109 def hash_check(cls, password, hashed):
109 def hash_check(cls, password, hashed):
110 """
110 """
111 Checks matching password with it's hashed value, runs different
111 Checks matching password with it's hashed value, runs different
112 implementation based on platform it runs on
112 implementation based on platform it runs on
113
113
114 :param password: password
114 :param password: password
115 :param hashed: password in hashed form
115 :param hashed: password in hashed form
116 """
116 """
117
117
118 if __platform__ in PLATFORM_WIN:
118 if __platform__ in PLATFORM_WIN:
119 return sha256(password).hexdigest() == hashed
119 return sha256(password).hexdigest() == hashed
120 elif __platform__ in PLATFORM_OTHERS:
120 elif __platform__ in PLATFORM_OTHERS:
121 return bcrypt.hashpw(password, hashed) == hashed
121 return bcrypt.hashpw(password, hashed) == hashed
122 else:
122 else:
123 raise Exception('Unknown or unsupported platform %s' \
123 raise Exception('Unknown or unsupported platform %s' \
124 % __platform__)
124 % __platform__)
125
125
126
126
127 def get_crypt_password(password):
127 def get_crypt_password(password):
128 return RhodeCodeCrypto.hash_string(password)
128 return RhodeCodeCrypto.hash_string(password)
129
129
130
130
131 def check_password(password, hashed):
131 def check_password(password, hashed):
132 return RhodeCodeCrypto.hash_check(password, hashed)
132 return RhodeCodeCrypto.hash_check(password, hashed)
133
133
134
134
135 def generate_api_key(str_, salt=None):
135 def generate_api_key(str_, salt=None):
136 """
136 """
137 Generates API KEY from given string
137 Generates API KEY from given string
138
138
139 :param str_:
139 :param str_:
140 :param salt:
140 :param salt:
141 """
141 """
142
142
143 if salt is None:
143 if salt is None:
144 salt = _RandomNameSequence().next()
144 salt = _RandomNameSequence().next()
145
145
146 return hashlib.sha1(str_ + salt).hexdigest()
146 return hashlib.sha1(str_ + salt).hexdigest()
147
147
148
148
149 def authfunc(environ, username, password):
149 def authfunc(environ, username, password):
150 """
150 """
151 Dummy authentication wrapper function used in Mercurial and Git for
151 Dummy authentication wrapper function used in Mercurial and Git for
152 access control.
152 access control.
153
153
154 :param environ: needed only for using in Basic auth
154 :param environ: needed only for using in Basic auth
155 """
155 """
156 return authenticate(username, password)
156 return authenticate(username, password)
157
157
158
158
159 def authenticate(username, password):
159 def authenticate(username, password):
160 """
160 """
161 Authentication function used for access control,
161 Authentication function used for access control,
162 firstly checks for db authentication then if ldap is enabled for ldap
162 firstly checks for db authentication then if ldap is enabled for ldap
163 authentication, also creates ldap user if not in database
163 authentication, also creates ldap user if not in database
164
164
165 :param username: username
165 :param username: username
166 :param password: password
166 :param password: password
167 """
167 """
168
168
169 user_model = UserModel()
169 user_model = UserModel()
170 user = User.get_by_username(username)
170 user = User.get_by_username(username)
171
171
172 log.debug('Authenticating user using RhodeCode account')
172 log.debug('Authenticating user using RhodeCode account')
173 if user is not None and not user.ldap_dn:
173 if user is not None and not user.ldap_dn:
174 if user.active:
174 if user.active:
175 if user.username == 'default' and user.active:
175 if user.username == 'default' and user.active:
176 log.info('user %s authenticated correctly as anonymous user' %
176 log.info('user %s authenticated correctly as anonymous user' %
177 username)
177 username)
178 return True
178 return True
179
179
180 elif user.username == username and check_password(password,
180 elif user.username == username and check_password(password,
181 user.password):
181 user.password):
182 log.info('user %s authenticated correctly' % username)
182 log.info('user %s authenticated correctly' % username)
183 return True
183 return True
184 else:
184 else:
185 log.warning('user %s tried auth but is disabled' % username)
185 log.warning('user %s tried auth but is disabled' % username)
186
186
187 else:
187 else:
188 log.debug('Regular authentication failed')
188 log.debug('Regular authentication failed')
189 user_obj = User.get_by_username(username, case_insensitive=True)
189 user_obj = User.get_by_username(username, case_insensitive=True)
190
190
191 if user_obj is not None and not user_obj.ldap_dn:
191 if user_obj is not None and not user_obj.ldap_dn:
192 log.debug('this user already exists as non ldap')
192 log.debug('this user already exists as non ldap')
193 return False
193 return False
194
194
195 ldap_settings = RhodeCodeSetting.get_ldap_settings()
195 ldap_settings = RhodeCodeSetting.get_ldap_settings()
196 #======================================================================
196 #======================================================================
197 # FALLBACK TO LDAP AUTH IF ENABLE
197 # FALLBACK TO LDAP AUTH IF ENABLE
198 #======================================================================
198 #======================================================================
199 if str2bool(ldap_settings.get('ldap_active')):
199 if str2bool(ldap_settings.get('ldap_active')):
200 log.debug("Authenticating user using ldap")
200 log.debug("Authenticating user using ldap")
201 kwargs = {
201 kwargs = {
202 'server': ldap_settings.get('ldap_host', ''),
202 'server': ldap_settings.get('ldap_host', ''),
203 'base_dn': ldap_settings.get('ldap_base_dn', ''),
203 'base_dn': ldap_settings.get('ldap_base_dn', ''),
204 'port': ldap_settings.get('ldap_port'),
204 'port': ldap_settings.get('ldap_port'),
205 'bind_dn': ldap_settings.get('ldap_dn_user'),
205 'bind_dn': ldap_settings.get('ldap_dn_user'),
206 'bind_pass': ldap_settings.get('ldap_dn_pass'),
206 'bind_pass': ldap_settings.get('ldap_dn_pass'),
207 'tls_kind': ldap_settings.get('ldap_tls_kind'),
207 'tls_kind': ldap_settings.get('ldap_tls_kind'),
208 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
208 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
209 'ldap_filter': ldap_settings.get('ldap_filter'),
209 'ldap_filter': ldap_settings.get('ldap_filter'),
210 'search_scope': ldap_settings.get('ldap_search_scope'),
210 'search_scope': ldap_settings.get('ldap_search_scope'),
211 'attr_login': ldap_settings.get('ldap_attr_login'),
211 'attr_login': ldap_settings.get('ldap_attr_login'),
212 'ldap_version': 3,
212 'ldap_version': 3,
213 }
213 }
214 log.debug('Checking for ldap authentication')
214 log.debug('Checking for ldap authentication')
215 try:
215 try:
216 aldap = AuthLdap(**kwargs)
216 aldap = AuthLdap(**kwargs)
217 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
217 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
218 password)
218 password)
219 log.debug('Got ldap DN response %s' % user_dn)
219 log.debug('Got ldap DN response %s' % user_dn)
220
220
221 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
221 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
222 .get(k), [''])[0]
222 .get(k), [''])[0]
223
223
224 user_attrs = {
224 user_attrs = {
225 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
225 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
226 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
226 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
227 'email': get_ldap_attr('ldap_attr_email'),
227 'email': get_ldap_attr('ldap_attr_email'),
228 }
228 }
229
229
230 # don't store LDAP password since we don't need it. Override
230 # don't store LDAP password since we don't need it. Override
231 # with some random generated password
231 # with some random generated password
232 _password = PasswordGenerator().gen_password(length=8)
232 _password = PasswordGenerator().gen_password(length=8)
233 # create this user on the fly if it doesn't exist in rhodecode
233 # create this user on the fly if it doesn't exist in rhodecode
234 # database
234 # database
235 if user_model.create_ldap(username, _password, user_dn,
235 if user_model.create_ldap(username, _password, user_dn,
236 user_attrs):
236 user_attrs):
237 log.info('created new ldap user %s' % username)
237 log.info('created new ldap user %s' % username)
238
238
239 Session.commit()
239 Session.commit()
240 return True
240 return True
241 except (LdapUsernameError, LdapPasswordError,):
241 except (LdapUsernameError, LdapPasswordError,):
242 pass
242 pass
243 except (Exception,):
243 except (Exception,):
244 log.error(traceback.format_exc())
244 log.error(traceback.format_exc())
245 pass
245 pass
246 return False
246 return False
247
247
248
248
249 def login_container_auth(username):
249 def login_container_auth(username):
250 user = User.get_by_username(username)
250 user = User.get_by_username(username)
251 if user is None:
251 if user is None:
252 user_attrs = {
252 user_attrs = {
253 'name': username,
253 'name': username,
254 'lastname': None,
254 'lastname': None,
255 'email': None,
255 'email': None,
256 }
256 }
257 user = UserModel().create_for_container_auth(username, user_attrs)
257 user = UserModel().create_for_container_auth(username, user_attrs)
258 if not user:
258 if not user:
259 return None
259 return None
260 log.info('User %s was created by container authentication' % username)
260 log.info('User %s was created by container authentication' % username)
261
261
262 if not user.active:
262 if not user.active:
263 return None
263 return None
264
264
265 user.update_lastlogin()
265 user.update_lastlogin()
266 Session.commit()
266 Session.commit()
267
267
268 log.debug('User %s is now logged in by container authentication',
268 log.debug('User %s is now logged in by container authentication',
269 user.username)
269 user.username)
270 return user
270 return user
271
271
272
272
273 def get_container_username(environ, config):
273 def get_container_username(environ, config):
274 username = None
274 username = None
275
275
276 if str2bool(config.get('container_auth_enabled', False)):
276 if str2bool(config.get('container_auth_enabled', False)):
277 from paste.httpheaders import REMOTE_USER
277 from paste.httpheaders import REMOTE_USER
278 username = REMOTE_USER(environ)
278 username = REMOTE_USER(environ)
279
279
280 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
280 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
281 username = environ.get('HTTP_X_FORWARDED_USER')
281 username = environ.get('HTTP_X_FORWARDED_USER')
282
282
283 if username:
283 if username:
284 # Removing realm and domain from username
284 # Removing realm and domain from username
285 username = username.partition('@')[0]
285 username = username.partition('@')[0]
286 username = username.rpartition('\\')[2]
286 username = username.rpartition('\\')[2]
287 log.debug('Received username %s from container' % username)
287 log.debug('Received username %s from container' % username)
288
288
289 return username
289 return username
290
290
291
291
292 class CookieStoreWrapper(object):
292 class CookieStoreWrapper(object):
293
293
294 def __init__(self, cookie_store):
294 def __init__(self, cookie_store):
295 self.cookie_store = cookie_store
295 self.cookie_store = cookie_store
296
296
297 def __repr__(self):
297 def __repr__(self):
298 return 'CookieStore<%s>' % (self.cookie_store)
298 return 'CookieStore<%s>' % (self.cookie_store)
299
299
300 def get(self, key, other=None):
300 def get(self, key, other=None):
301 if isinstance(self.cookie_store, dict):
301 if isinstance(self.cookie_store, dict):
302 return self.cookie_store.get(key, other)
302 return self.cookie_store.get(key, other)
303 elif isinstance(self.cookie_store, AuthUser):
303 elif isinstance(self.cookie_store, AuthUser):
304 return self.cookie_store.__dict__.get(key, other)
304 return self.cookie_store.__dict__.get(key, other)
305
305
306
306
307 class AuthUser(object):
307 class AuthUser(object):
308 """
308 """
309 A simple object that handles all attributes of user in RhodeCode
309 A simple object that handles all attributes of user in RhodeCode
310
310
311 It does lookup based on API key,given user, or user present in session
311 It does lookup based on API key,given user, or user present in session
312 Then it fills all required information for such user. It also checks if
312 Then it fills all required information for such user. It also checks if
313 anonymous access is enabled and if so, it returns default user as logged
313 anonymous access is enabled and if so, it returns default user as logged
314 in
314 in
315 """
315 """
316
316
317 def __init__(self, user_id=None, api_key=None, username=None):
317 def __init__(self, user_id=None, api_key=None, username=None):
318
318
319 self.user_id = user_id
319 self.user_id = user_id
320 self.api_key = None
320 self.api_key = None
321 self.username = username
321 self.username = username
322
322
323 self.name = ''
323 self.name = ''
324 self.lastname = ''
324 self.lastname = ''
325 self.email = ''
325 self.email = ''
326 self.is_authenticated = False
326 self.is_authenticated = False
327 self.admin = False
327 self.admin = False
328 self.permissions = {}
328 self.permissions = {}
329 self._api_key = api_key
329 self._api_key = api_key
330 self.propagate_data()
330 self.propagate_data()
331 self._instance = None
331 self._instance = None
332
332
333 def propagate_data(self):
333 def propagate_data(self):
334 user_model = UserModel()
334 user_model = UserModel()
335 self.anonymous_user = User.get_by_username('default', cache=True)
335 self.anonymous_user = User.get_by_username('default', cache=True)
336 is_user_loaded = False
336 is_user_loaded = False
337
337
338 # try go get user by api key
338 # try go get user by api key
339 if self._api_key and self._api_key != self.anonymous_user.api_key:
339 if self._api_key and self._api_key != self.anonymous_user.api_key:
340 log.debug('Auth User lookup by API KEY %s' % self._api_key)
340 log.debug('Auth User lookup by API KEY %s' % self._api_key)
341 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
341 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
342 # lookup by userid
342 # lookup by userid
343 elif (self.user_id is not None and
343 elif (self.user_id is not None and
344 self.user_id != self.anonymous_user.user_id):
344 self.user_id != self.anonymous_user.user_id):
345 log.debug('Auth User lookup by USER ID %s' % self.user_id)
345 log.debug('Auth User lookup by USER ID %s' % self.user_id)
346 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
346 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
347 # lookup by username
347 # lookup by username
348 elif self.username and \
348 elif self.username and \
349 str2bool(config.get('container_auth_enabled', False)):
349 str2bool(config.get('container_auth_enabled', False)):
350
350
351 log.debug('Auth User lookup by USER NAME %s' % self.username)
351 log.debug('Auth User lookup by USER NAME %s' % self.username)
352 dbuser = login_container_auth(self.username)
352 dbuser = login_container_auth(self.username)
353 if dbuser is not None:
353 if dbuser is not None:
354 for k, v in dbuser.get_dict().items():
354 for k, v in dbuser.get_dict().items():
355 setattr(self, k, v)
355 setattr(self, k, v)
356 self.set_authenticated()
356 self.set_authenticated()
357 is_user_loaded = True
357 is_user_loaded = True
358 else:
358 else:
359 log.debug('No data in %s that could been used to log in' % self)
359 log.debug('No data in %s that could been used to log in' % self)
360
360
361 if not is_user_loaded:
361 if not is_user_loaded:
362 # if we cannot authenticate user try anonymous
362 # if we cannot authenticate user try anonymous
363 if self.anonymous_user.active is True:
363 if self.anonymous_user.active is True:
364 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
364 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
365 # then we set this user is logged in
365 # then we set this user is logged in
366 self.is_authenticated = True
366 self.is_authenticated = True
367 else:
367 else:
368 self.user_id = None
368 self.user_id = None
369 self.username = None
369 self.username = None
370 self.is_authenticated = False
370 self.is_authenticated = False
371
371
372 if not self.username:
372 if not self.username:
373 self.username = 'None'
373 self.username = 'None'
374
374
375 log.debug('Auth User is now %s' % self)
375 log.debug('Auth User is now %s' % self)
376 user_model.fill_perms(self)
376 user_model.fill_perms(self)
377
377
378 @property
378 @property
379 def is_admin(self):
379 def is_admin(self):
380 return self.admin
380 return self.admin
381
381
382 def __repr__(self):
382 def __repr__(self):
383 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
383 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
384 self.is_authenticated)
384 self.is_authenticated)
385
385
386 def set_authenticated(self, authenticated=True):
386 def set_authenticated(self, authenticated=True):
387 if self.user_id != self.anonymous_user.user_id:
387 if self.user_id != self.anonymous_user.user_id:
388 self.is_authenticated = authenticated
388 self.is_authenticated = authenticated
389
389
390 def get_cookie_store(self):
390 def get_cookie_store(self):
391 return {'username': self.username,
391 return {'username': self.username,
392 'user_id': self.user_id,
392 'user_id': self.user_id,
393 'is_authenticated': self.is_authenticated}
393 'is_authenticated': self.is_authenticated}
394
394
395 @classmethod
395 @classmethod
396 def from_cookie_store(cls, cookie_store):
396 def from_cookie_store(cls, cookie_store):
397 """
397 """
398 Creates AuthUser from a cookie store
398 Creates AuthUser from a cookie store
399
399
400 :param cls:
400 :param cls:
401 :param cookie_store:
401 :param cookie_store:
402 """
402 """
403 user_id = cookie_store.get('user_id')
403 user_id = cookie_store.get('user_id')
404 username = cookie_store.get('username')
404 username = cookie_store.get('username')
405 api_key = cookie_store.get('api_key')
405 api_key = cookie_store.get('api_key')
406 return AuthUser(user_id, api_key, username)
406 return AuthUser(user_id, api_key, username)
407
407
408
408
409 def set_available_permissions(config):
409 def set_available_permissions(config):
410 """
410 """
411 This function will propagate pylons globals with all available defined
411 This function will propagate pylons globals with all available defined
412 permission given in db. We don't want to check each time from db for new
412 permission given in db. We don't want to check each time from db for new
413 permissions since adding a new permission also requires application restart
413 permissions since adding a new permission also requires application restart
414 ie. to decorate new views with the newly created permission
414 ie. to decorate new views with the newly created permission
415
415
416 :param config: current pylons config instance
416 :param config: current pylons config instance
417
417
418 """
418 """
419 log.info('getting information about all available permissions')
419 log.info('getting information about all available permissions')
420 try:
420 try:
421 sa = meta.Session
421 sa = meta.Session
422 all_perms = sa.query(Permission).all()
422 all_perms = sa.query(Permission).all()
423 except Exception:
423 except Exception:
424 pass
424 pass
425 finally:
425 finally:
426 meta.Session.remove()
426 meta.Session.remove()
427
427
428 config['available_permissions'] = [x.permission_name for x in all_perms]
428 config['available_permissions'] = [x.permission_name for x in all_perms]
429
429
430
430
431 #==============================================================================
431 #==============================================================================
432 # CHECK DECORATORS
432 # CHECK DECORATORS
433 #==============================================================================
433 #==============================================================================
434 class LoginRequired(object):
434 class LoginRequired(object):
435 """
435 """
436 Must be logged in to execute this function else
436 Must be logged in to execute this function else
437 redirect to login page
437 redirect to login page
438
438
439 :param api_access: if enabled this checks only for valid auth token
439 :param api_access: if enabled this checks only for valid auth token
440 and grants access based on valid token
440 and grants access based on valid token
441 """
441 """
442
442
443 def __init__(self, api_access=False):
443 def __init__(self, api_access=False):
444 self.api_access = api_access
444 self.api_access = api_access
445
445
446 def __call__(self, func):
446 def __call__(self, func):
447 return decorator(self.__wrapper, func)
447 return decorator(self.__wrapper, func)
448
448
449 def __wrapper(self, func, *fargs, **fkwargs):
449 def __wrapper(self, func, *fargs, **fkwargs):
450 cls = fargs[0]
450 cls = fargs[0]
451 user = cls.rhodecode_user
451 user = cls.rhodecode_user
452
452
453 api_access_ok = False
453 api_access_ok = False
454 if self.api_access:
454 if self.api_access:
455 log.debug('Checking API KEY access for %s' % cls)
455 log.debug('Checking API KEY access for %s' % cls)
456 if user.api_key == request.GET.get('api_key'):
456 if user.api_key == request.GET.get('api_key'):
457 api_access_ok = True
457 api_access_ok = True
458 else:
458 else:
459 log.debug("API KEY token not valid")
459 log.debug("API KEY token not valid")
460 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
460 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
461 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
461 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
462 if user.is_authenticated or api_access_ok:
462 if user.is_authenticated or api_access_ok:
463 log.info('user %s is authenticated and granted access to %s' % (
463 log.info('user %s is authenticated and granted access to %s' % (
464 user.username, loc)
464 user.username, loc)
465 )
465 )
466 return func(*fargs, **fkwargs)
466 return func(*fargs, **fkwargs)
467 else:
467 else:
468 log.warn('user %s NOT authenticated on func: %s' % (
468 log.warn('user %s NOT authenticated on func: %s' % (
469 user, loc)
469 user, loc)
470 )
470 )
471 p = url.current()
471 p = url.current()
472
472
473 log.debug('redirecting to login page with %s' % p)
473 log.debug('redirecting to login page with %s' % p)
474 return redirect(url('login_home', came_from=p))
474 return redirect(url('login_home', came_from=p))
475
475
476
476
477 class NotAnonymous(object):
477 class NotAnonymous(object):
478 """
478 """
479 Must be logged in to execute this function else
479 Must be logged in to execute this function else
480 redirect to login page"""
480 redirect to login page"""
481
481
482 def __call__(self, func):
482 def __call__(self, func):
483 return decorator(self.__wrapper, func)
483 return decorator(self.__wrapper, func)
484
484
485 def __wrapper(self, func, *fargs, **fkwargs):
485 def __wrapper(self, func, *fargs, **fkwargs):
486 cls = fargs[0]
486 cls = fargs[0]
487 self.user = cls.rhodecode_user
487 self.user = cls.rhodecode_user
488
488
489 log.debug('Checking if user is not anonymous @%s' % cls)
489 log.debug('Checking if user is not anonymous @%s' % cls)
490
490
491 anonymous = self.user.username == 'default'
491 anonymous = self.user.username == 'default'
492
492
493 if anonymous:
493 if anonymous:
494 p = url.current()
494 p = url.current()
495
495
496 import rhodecode.lib.helpers as h
496 import rhodecode.lib.helpers as h
497 h.flash(_('You need to be a registered user to '
497 h.flash(_('You need to be a registered user to '
498 'perform this action'),
498 'perform this action'),
499 category='warning')
499 category='warning')
500 return redirect(url('login_home', came_from=p))
500 return redirect(url('login_home', came_from=p))
501 else:
501 else:
502 return func(*fargs, **fkwargs)
502 return func(*fargs, **fkwargs)
503
503
504
504
505 class PermsDecorator(object):
505 class PermsDecorator(object):
506 """Base class for controller decorators"""
506 """Base class for controller decorators"""
507
507
508 def __init__(self, *required_perms):
508 def __init__(self, *required_perms):
509 available_perms = config['available_permissions']
509 available_perms = config['available_permissions']
510 for perm in required_perms:
510 for perm in required_perms:
511 if perm not in available_perms:
511 if perm not in available_perms:
512 raise Exception("'%s' permission is not defined" % perm)
512 raise Exception("'%s' permission is not defined" % perm)
513 self.required_perms = set(required_perms)
513 self.required_perms = set(required_perms)
514 self.user_perms = None
514 self.user_perms = None
515
515
516 def __call__(self, func):
516 def __call__(self, func):
517 return decorator(self.__wrapper, func)
517 return decorator(self.__wrapper, func)
518
518
519 def __wrapper(self, func, *fargs, **fkwargs):
519 def __wrapper(self, func, *fargs, **fkwargs):
520 cls = fargs[0]
520 cls = fargs[0]
521 self.user = cls.rhodecode_user
521 self.user = cls.rhodecode_user
522 self.user_perms = self.user.permissions
522 self.user_perms = self.user.permissions
523 log.debug('checking %s permissions %s for %s %s',
523 log.debug('checking %s permissions %s for %s %s',
524 self.__class__.__name__, self.required_perms, cls, self.user)
524 self.__class__.__name__, self.required_perms, cls, self.user)
525
525
526 if self.check_permissions():
526 if self.check_permissions():
527 log.debug('Permission granted for %s %s' % (cls, self.user))
527 log.debug('Permission granted for %s %s' % (cls, self.user))
528 return func(*fargs, **fkwargs)
528 return func(*fargs, **fkwargs)
529
529
530 else:
530 else:
531 log.debug('Permission denied for %s %s' % (cls, self.user))
531 log.debug('Permission denied for %s %s' % (cls, self.user))
532 anonymous = self.user.username == 'default'
532 anonymous = self.user.username == 'default'
533
533
534 if anonymous:
534 if anonymous:
535 p = url.current()
535 p = url.current()
536
536
537 import rhodecode.lib.helpers as h
537 import rhodecode.lib.helpers as h
538 h.flash(_('You need to be a signed in to '
538 h.flash(_('You need to be a signed in to '
539 'view this page'),
539 'view this page'),
540 category='warning')
540 category='warning')
541 return redirect(url('login_home', came_from=p))
541 return redirect(url('login_home', came_from=p))
542
542
543 else:
543 else:
544 # redirect with forbidden ret code
544 # redirect with forbidden ret code
545 return abort(403)
545 return abort(403)
546
546
547 def check_permissions(self):
547 def check_permissions(self):
548 """Dummy function for overriding"""
548 """Dummy function for overriding"""
549 raise Exception('You have to write this function in child class')
549 raise Exception('You have to write this function in child class')
550
550
551
551
552 class HasPermissionAllDecorator(PermsDecorator):
552 class HasPermissionAllDecorator(PermsDecorator):
553 """
553 """
554 Checks for access permission for all given predicates. All of them
554 Checks for access permission for all given predicates. All of them
555 have to be meet in order to fulfill the request
555 have to be meet in order to fulfill the request
556 """
556 """
557
557
558 def check_permissions(self):
558 def check_permissions(self):
559 if self.required_perms.issubset(self.user_perms.get('global')):
559 if self.required_perms.issubset(self.user_perms.get('global')):
560 return True
560 return True
561 return False
561 return False
562
562
563
563
564 class HasPermissionAnyDecorator(PermsDecorator):
564 class HasPermissionAnyDecorator(PermsDecorator):
565 """
565 """
566 Checks for access permission for any of given predicates. In order to
566 Checks for access permission for any of given predicates. In order to
567 fulfill the request any of predicates must be meet
567 fulfill the request any of predicates must be meet
568 """
568 """
569
569
570 def check_permissions(self):
570 def check_permissions(self):
571 if self.required_perms.intersection(self.user_perms.get('global')):
571 if self.required_perms.intersection(self.user_perms.get('global')):
572 return True
572 return True
573 return False
573 return False
574
574
575
575
576 class HasRepoPermissionAllDecorator(PermsDecorator):
576 class HasRepoPermissionAllDecorator(PermsDecorator):
577 """
577 """
578 Checks for access permission for all given predicates for specific
578 Checks for access permission for all given predicates for specific
579 repository. All of them have to be meet in order to fulfill the request
579 repository. All of them have to be meet in order to fulfill the request
580 """
580 """
581
581
582 def check_permissions(self):
582 def check_permissions(self):
583 repo_name = get_repo_slug(request)
583 repo_name = get_repo_slug(request)
584 try:
584 try:
585 user_perms = set([self.user_perms['repositories'][repo_name]])
585 user_perms = set([self.user_perms['repositories'][repo_name]])
586 except KeyError:
586 except KeyError:
587 return False
587 return False
588 if self.required_perms.issubset(user_perms):
588 if self.required_perms.issubset(user_perms):
589 return True
589 return True
590 return False
590 return False
591
591
592
592
593 class HasRepoPermissionAnyDecorator(PermsDecorator):
593 class HasRepoPermissionAnyDecorator(PermsDecorator):
594 """
594 """
595 Checks for access permission for any of given predicates for specific
595 Checks for access permission for any of given predicates for specific
596 repository. In order to fulfill the request any of predicates must be meet
596 repository. In order to fulfill the request any of predicates must be meet
597 """
597 """
598
598
599 def check_permissions(self):
599 def check_permissions(self):
600 repo_name = get_repo_slug(request)
600 repo_name = get_repo_slug(request)
601
601
602 try:
602 try:
603 user_perms = set([self.user_perms['repositories'][repo_name]])
603 user_perms = set([self.user_perms['repositories'][repo_name]])
604 except KeyError:
604 except KeyError:
605 return False
605 return False
606
606
607 if self.required_perms.intersection(user_perms):
607 if self.required_perms.intersection(user_perms):
608 return True
608 return True
609 return False
609 return False
610
610
611
611
612 class HasReposGroupPermissionAllDecorator(PermsDecorator):
612 class HasReposGroupPermissionAllDecorator(PermsDecorator):
613 """
613 """
614 Checks for access permission for all given predicates for specific
614 Checks for access permission for all given predicates for specific
615 repository. All of them have to be meet in order to fulfill the request
615 repository. All of them have to be meet in order to fulfill the request
616 """
616 """
617
617
618 def check_permissions(self):
618 def check_permissions(self):
619 group_name = get_repos_group_slug(request)
619 group_name = get_repos_group_slug(request)
620 try:
620 try:
621 user_perms = set([self.user_perms['repositories_groups'][group_name]])
621 user_perms = set([self.user_perms['repositories_groups'][group_name]])
622 except KeyError:
622 except KeyError:
623 return False
623 return False
624 if self.required_perms.issubset(user_perms):
624 if self.required_perms.issubset(user_perms):
625 return True
625 return True
626 return False
626 return False
627
627
628
628
629 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
629 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
630 """
630 """
631 Checks for access permission for any of given predicates for specific
631 Checks for access permission for any of given predicates for specific
632 repository. In order to fulfill the request any of predicates must be meet
632 repository. In order to fulfill the request any of predicates must be meet
633 """
633 """
634
634
635 def check_permissions(self):
635 def check_permissions(self):
636 group_name = get_repos_group_slug(request)
636 group_name = get_repos_group_slug(request)
637
637
638 try:
638 try:
639 user_perms = set([self.user_perms['repositories_groups'][group_name]])
639 user_perms = set([self.user_perms['repositories_groups'][group_name]])
640 except KeyError:
640 except KeyError:
641 return False
641 return False
642 if self.required_perms.intersection(user_perms):
642 if self.required_perms.intersection(user_perms):
643 return True
643 return True
644 return False
644 return False
645
645
646
646
647 #==============================================================================
647 #==============================================================================
648 # CHECK FUNCTIONS
648 # CHECK FUNCTIONS
649 #==============================================================================
649 #==============================================================================
650 class PermsFunction(object):
650 class PermsFunction(object):
651 """Base function for other check functions"""
651 """Base function for other check functions"""
652
652
653 def __init__(self, *perms):
653 def __init__(self, *perms):
654 available_perms = config['available_permissions']
654 available_perms = config['available_permissions']
655
655
656 for perm in perms:
656 for perm in perms:
657 if perm not in available_perms:
657 if perm not in available_perms:
658 raise Exception("'%s' permission is not defined" % perm)
658 raise Exception("'%s' permission is not defined" % perm)
659 self.required_perms = set(perms)
659 self.required_perms = set(perms)
660 self.user_perms = None
660 self.user_perms = None
661 self.repo_name = None
661 self.repo_name = None
662 self.group_name = None
662 self.group_name = None
663
663
664 def __call__(self, check_Location=''):
664 def __call__(self, check_Location=''):
665 user = request.user
665 user = request.user
666 cls_name = self.__class__.__name__
666 cls_name = self.__class__.__name__
667 check_scope = {
667 check_scope = {
668 'HasPermissionAll': '',
668 'HasPermissionAll': '',
669 'HasPermissionAny': '',
669 'HasPermissionAny': '',
670 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
670 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
671 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
671 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
672 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
672 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
673 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
673 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
674 }.get(cls_name, '?')
674 }.get(cls_name, '?')
675 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
675 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
676 self.required_perms, user, check_scope,
676 self.required_perms, user, check_scope,
677 check_Location or 'unspecified location')
677 check_Location or 'unspecified location')
678 if not user:
678 if not user:
679 log.debug('Empty request user')
679 log.debug('Empty request user')
680 return False
680 return False
681 self.user_perms = user.permissions
681 self.user_perms = user.permissions
682 if self.check_permissions():
682 if self.check_permissions():
683 log.debug('Permission granted for user: %s @ %s', user,
683 log.debug('Permission granted for user: %s @ %s', user,
684 check_Location or 'unspecified location')
684 check_Location or 'unspecified location')
685 return True
685 return True
686
686
687 else:
687 else:
688 log.debug('Permission denied for user: %s @ %s', user,
688 log.debug('Permission denied for user: %s @ %s', user,
689 check_Location or 'unspecified location')
689 check_Location or 'unspecified location')
690 return False
690 return False
691
691
692 def check_permissions(self):
692 def check_permissions(self):
693 """Dummy function for overriding"""
693 """Dummy function for overriding"""
694 raise Exception('You have to write this function in child class')
694 raise Exception('You have to write this function in child class')
695
695
696
696
697 class HasPermissionAll(PermsFunction):
697 class HasPermissionAll(PermsFunction):
698 def check_permissions(self):
698 def check_permissions(self):
699 if self.required_perms.issubset(self.user_perms.get('global')):
699 if self.required_perms.issubset(self.user_perms.get('global')):
700 return True
700 return True
701 return False
701 return False
702
702
703
703
704 class HasPermissionAny(PermsFunction):
704 class HasPermissionAny(PermsFunction):
705 def check_permissions(self):
705 def check_permissions(self):
706 if self.required_perms.intersection(self.user_perms.get('global')):
706 if self.required_perms.intersection(self.user_perms.get('global')):
707 return True
707 return True
708 return False
708 return False
709
709
710
710
711 class HasRepoPermissionAll(PermsFunction):
711 class HasRepoPermissionAll(PermsFunction):
712 def __call__(self, repo_name=None, check_Location=''):
712 def __call__(self, repo_name=None, check_Location=''):
713 self.repo_name = repo_name
713 self.repo_name = repo_name
714 return super(HasRepoPermissionAll, self).__call__(check_Location)
714 return super(HasRepoPermissionAll, self).__call__(check_Location)
715
715
716 def check_permissions(self):
716 def check_permissions(self):
717 if not self.repo_name:
717 if not self.repo_name:
718 self.repo_name = get_repo_slug(request)
718 self.repo_name = get_repo_slug(request)
719
719
720 try:
720 try:
721 self._user_perms = set(
721 self._user_perms = set(
722 [self.user_perms['repositories'][self.repo_name]]
722 [self.user_perms['repositories'][self.repo_name]]
723 )
723 )
724 except KeyError:
724 except KeyError:
725 return False
725 return False
726 if self.required_perms.issubset(self._user_perms):
726 if self.required_perms.issubset(self._user_perms):
727 return True
727 return True
728 return False
728 return False
729
729
730
730
731 class HasRepoPermissionAny(PermsFunction):
731 class HasRepoPermissionAny(PermsFunction):
732 def __call__(self, repo_name=None, check_Location=''):
732 def __call__(self, repo_name=None, check_Location=''):
733 self.repo_name = repo_name
733 self.repo_name = repo_name
734 return super(HasRepoPermissionAny, self).__call__(check_Location)
734 return super(HasRepoPermissionAny, self).__call__(check_Location)
735
735
736 def check_permissions(self):
736 def check_permissions(self):
737 if not self.repo_name:
737 if not self.repo_name:
738 self.repo_name = get_repo_slug(request)
738 self.repo_name = get_repo_slug(request)
739
739
740 try:
740 try:
741 self._user_perms = set(
741 self._user_perms = set(
742 [self.user_perms['repositories'][self.repo_name]]
742 [self.user_perms['repositories'][self.repo_name]]
743 )
743 )
744 except KeyError:
744 except KeyError:
745 return False
745 return False
746 if self.required_perms.intersection(self._user_perms):
746 if self.required_perms.intersection(self._user_perms):
747 return True
747 return True
748 return False
748 return False
749
749
750
750
751 class HasReposGroupPermissionAny(PermsFunction):
751 class HasReposGroupPermissionAny(PermsFunction):
752 def __call__(self, group_name=None, check_Location=''):
752 def __call__(self, group_name=None, check_Location=''):
753 self.group_name = group_name
753 self.group_name = group_name
754 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
754 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
755
755
756 def check_permissions(self):
756 def check_permissions(self):
757 try:
757 try:
758 self._user_perms = set(
758 self._user_perms = set(
759 [self.user_perms['repositories_groups'][self.group_name]]
759 [self.user_perms['repositories_groups'][self.group_name]]
760 )
760 )
761 except KeyError:
761 except KeyError:
762 return False
762 return False
763 if self.required_perms.intersection(self._user_perms):
763 if self.required_perms.intersection(self._user_perms):
764 return True
764 return True
765 return False
765 return False
766
766
767
767
768 class HasReposGroupPermissionAll(PermsFunction):
768 class HasReposGroupPermissionAll(PermsFunction):
769 def __call__(self, group_name=None, check_Location=''):
769 def __call__(self, group_name=None, check_Location=''):
770 self.group_name = group_name
770 self.group_name = group_name
771 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
771 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
772
772
773 def check_permissions(self):
773 def check_permissions(self):
774 try:
774 try:
775 self._user_perms = set(
775 self._user_perms = set(
776 [self.user_perms['repositories_groups'][self.group_name]]
776 [self.user_perms['repositories_groups'][self.group_name]]
777 )
777 )
778 except KeyError:
778 except KeyError:
779 return False
779 return False
780 if self.required_perms.issubset(self._user_perms):
780 if self.required_perms.issubset(self._user_perms):
781 return True
781 return True
782 return False
782 return False
783
783
784
784
785 #==============================================================================
785 #==============================================================================
786 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
786 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
787 #==============================================================================
787 #==============================================================================
788 class HasPermissionAnyMiddleware(object):
788 class HasPermissionAnyMiddleware(object):
789 def __init__(self, *perms):
789 def __init__(self, *perms):
790 self.required_perms = set(perms)
790 self.required_perms = set(perms)
791
791
792 def __call__(self, user, repo_name):
792 def __call__(self, user, repo_name):
793 # repo_name MUST be unicode, since we handle keys in permission
793 # repo_name MUST be unicode, since we handle keys in permission
794 # dict by unicode
794 # dict by unicode
795 repo_name = safe_unicode(repo_name)
795 repo_name = safe_unicode(repo_name)
796 usr = AuthUser(user.user_id)
796 usr = AuthUser(user.user_id)
797 try:
797 try:
798 self.user_perms = set([usr.permissions['repositories'][repo_name]])
798 self.user_perms = set([usr.permissions['repositories'][repo_name]])
799 except Exception:
799 except Exception:
800 log.error('Exception while accessing permissions %s' %
800 log.error('Exception while accessing permissions %s' %
801 traceback.format_exc())
801 traceback.format_exc())
802 self.user_perms = set()
802 self.user_perms = set()
803 self.username = user.username
803 self.username = user.username
804 self.repo_name = repo_name
804 self.repo_name = repo_name
805 return self.check_permissions()
805 return self.check_permissions()
806
806
807 def check_permissions(self):
807 def check_permissions(self):
808 log.debug('checking mercurial protocol '
808 log.debug('checking mercurial protocol '
809 'permissions %s for user:%s repository:%s', self.user_perms,
809 'permissions %s for user:%s repository:%s', self.user_perms,
810 self.username, self.repo_name)
810 self.username, self.repo_name)
811 if self.required_perms.intersection(self.user_perms):
811 if self.required_perms.intersection(self.user_perms):
812 log.debug('permission granted for user:%s on repo:%s' % (
812 log.debug('permission granted for user:%s on repo:%s' % (
813 self.username, self.repo_name
813 self.username, self.repo_name
814 )
814 )
815 )
815 )
816 return True
816 return True
817 log.debug('permission denied for user:%s on repo:%s' % (
817 log.debug('permission denied for user:%s on repo:%s' % (
818 self.username, self.repo_name
818 self.username, self.repo_name
819 )
819 )
820 )
820 )
821 return False
821 return False
@@ -1,408 +1,407 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-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 re
26 import re
27 from rhodecode.lib.vcs.utils.lazy import LazyProperty
27 from rhodecode.lib.vcs.utils.lazy import LazyProperty
28
28
29
29
30 def __get_lem():
30 def __get_lem():
31 """
31 """
32 Get language extension map based on what's inside pygments lexers
32 Get language extension map based on what's inside pygments lexers
33 """
33 """
34 from pygments import lexers
34 from pygments import lexers
35 from string import lower
35 from string import lower
36 from collections import defaultdict
36 from collections import defaultdict
37
37
38 d = defaultdict(lambda: [])
38 d = defaultdict(lambda: [])
39
39
40 def __clean(s):
40 def __clean(s):
41 s = s.lstrip('*')
41 s = s.lstrip('*')
42 s = s.lstrip('.')
42 s = s.lstrip('.')
43
43
44 if s.find('[') != -1:
44 if s.find('[') != -1:
45 exts = []
45 exts = []
46 start, stop = s.find('['), s.find(']')
46 start, stop = s.find('['), s.find(']')
47
47
48 for suffix in s[start + 1:stop]:
48 for suffix in s[start + 1:stop]:
49 exts.append(s[:s.find('[')] + suffix)
49 exts.append(s[:s.find('[')] + suffix)
50 return map(lower, exts)
50 return map(lower, exts)
51 else:
51 else:
52 return map(lower, [s])
52 return map(lower, [s])
53
53
54 for lx, t in sorted(lexers.LEXERS.items()):
54 for lx, t in sorted(lexers.LEXERS.items()):
55 m = map(__clean, t[-2])
55 m = map(__clean, t[-2])
56 if m:
56 if m:
57 m = reduce(lambda x, y: x + y, m)
57 m = reduce(lambda x, y: x + y, m)
58 for ext in m:
58 for ext in m:
59 desc = lx.replace('Lexer', '')
59 desc = lx.replace('Lexer', '')
60 d[ext].append(desc)
60 d[ext].append(desc)
61
61
62 return dict(d)
62 return dict(d)
63
63
64 def str2bool(_str):
64 def str2bool(_str):
65 """
65 """
66 returs True/False value from given string, it tries to translate the
66 returs True/False value from given string, it tries to translate the
67 string into boolean
67 string into boolean
68
68
69 :param _str: string value to translate into boolean
69 :param _str: string value to translate into boolean
70 :rtype: boolean
70 :rtype: boolean
71 :returns: boolean from given string
71 :returns: boolean from given string
72 """
72 """
73 if _str is None:
73 if _str is None:
74 return False
74 return False
75 if _str in (True, False):
75 if _str in (True, False):
76 return _str
76 return _str
77 _str = str(_str).strip().lower()
77 _str = str(_str).strip().lower()
78 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
78 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
79
79
80
80
81 def convert_line_endings(line, mode):
81 def convert_line_endings(line, mode):
82 """
82 """
83 Converts a given line "line end" accordingly to given mode
83 Converts a given line "line end" accordingly to given mode
84
84
85 Available modes are::
85 Available modes are::
86 0 - Unix
86 0 - Unix
87 1 - Mac
87 1 - Mac
88 2 - DOS
88 2 - DOS
89
89
90 :param line: given line to convert
90 :param line: given line to convert
91 :param mode: mode to convert to
91 :param mode: mode to convert to
92 :rtype: str
92 :rtype: str
93 :return: converted line according to mode
93 :return: converted line according to mode
94 """
94 """
95 from string import replace
95 from string import replace
96
96
97 if mode == 0:
97 if mode == 0:
98 line = replace(line, '\r\n', '\n')
98 line = replace(line, '\r\n', '\n')
99 line = replace(line, '\r', '\n')
99 line = replace(line, '\r', '\n')
100 elif mode == 1:
100 elif mode == 1:
101 line = replace(line, '\r\n', '\r')
101 line = replace(line, '\r\n', '\r')
102 line = replace(line, '\n', '\r')
102 line = replace(line, '\n', '\r')
103 elif mode == 2:
103 elif mode == 2:
104 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
104 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
105 return line
105 return line
106
106
107
107
108 def detect_mode(line, default):
108 def detect_mode(line, default):
109 """
109 """
110 Detects line break for given line, if line break couldn't be found
110 Detects line break for given line, if line break couldn't be found
111 given default value is returned
111 given default value is returned
112
112
113 :param line: str line
113 :param line: str line
114 :param default: default
114 :param default: default
115 :rtype: int
115 :rtype: int
116 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
116 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
117 """
117 """
118 if line.endswith('\r\n'):
118 if line.endswith('\r\n'):
119 return 2
119 return 2
120 elif line.endswith('\n'):
120 elif line.endswith('\n'):
121 return 0
121 return 0
122 elif line.endswith('\r'):
122 elif line.endswith('\r'):
123 return 1
123 return 1
124 else:
124 else:
125 return default
125 return default
126
126
127
127
128 def generate_api_key(username, salt=None):
128 def generate_api_key(username, salt=None):
129 """
129 """
130 Generates unique API key for given username, if salt is not given
130 Generates unique API key for given username, if salt is not given
131 it'll be generated from some random string
131 it'll be generated from some random string
132
132
133 :param username: username as string
133 :param username: username as string
134 :param salt: salt to hash generate KEY
134 :param salt: salt to hash generate KEY
135 :rtype: str
135 :rtype: str
136 :returns: sha1 hash from username+salt
136 :returns: sha1 hash from username+salt
137 """
137 """
138 from tempfile import _RandomNameSequence
138 from tempfile import _RandomNameSequence
139 import hashlib
139 import hashlib
140
140
141 if salt is None:
141 if salt is None:
142 salt = _RandomNameSequence().next()
142 salt = _RandomNameSequence().next()
143
143
144 return hashlib.sha1(username + salt).hexdigest()
144 return hashlib.sha1(username + salt).hexdigest()
145
145
146
146
147 def safe_unicode(str_, from_encoding=None):
147 def safe_unicode(str_, from_encoding=None):
148 """
148 """
149 safe unicode function. Does few trick to turn str_ into unicode
149 safe unicode function. Does few trick to turn str_ into unicode
150
150
151 In case of UnicodeDecode error we try to return it with encoding detected
151 In case of UnicodeDecode error we try to return it with encoding detected
152 by chardet library if it fails fallback to unicode with errors replaced
152 by chardet library if it fails fallback to unicode with errors replaced
153
153
154 :param str_: string to decode
154 :param str_: string to decode
155 :rtype: unicode
155 :rtype: unicode
156 :returns: unicode object
156 :returns: unicode object
157 """
157 """
158 if isinstance(str_, unicode):
158 if isinstance(str_, unicode):
159 return str_
159 return str_
160
160
161 if not from_encoding:
161 if not from_encoding:
162 import rhodecode
162 import rhodecode
163 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
163 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
164 from_encoding = DEFAULT_ENCODING
164 from_encoding = DEFAULT_ENCODING
165
165
166 try:
166 try:
167 return unicode(str_)
167 return unicode(str_)
168 except UnicodeDecodeError:
168 except UnicodeDecodeError:
169 pass
169 pass
170
170
171 try:
171 try:
172 return unicode(str_, from_encoding)
172 return unicode(str_, from_encoding)
173 except UnicodeDecodeError:
173 except UnicodeDecodeError:
174 pass
174 pass
175
175
176 try:
176 try:
177 import chardet
177 import chardet
178 encoding = chardet.detect(str_)['encoding']
178 encoding = chardet.detect(str_)['encoding']
179 if encoding is None:
179 if encoding is None:
180 raise Exception()
180 raise Exception()
181 return str_.decode(encoding)
181 return str_.decode(encoding)
182 except (ImportError, UnicodeDecodeError, Exception):
182 except (ImportError, UnicodeDecodeError, Exception):
183 return unicode(str_, from_encoding, 'replace')
183 return unicode(str_, from_encoding, 'replace')
184
184
185
185
186 def safe_str(unicode_, to_encoding=None):
186 def safe_str(unicode_, to_encoding=None):
187 """
187 """
188 safe str function. Does few trick to turn unicode_ into string
188 safe str function. Does few trick to turn unicode_ into string
189
189
190 In case of UnicodeEncodeError we try to return it with encoding detected
190 In case of UnicodeEncodeError we try to return it with encoding detected
191 by chardet library if it fails fallback to string with errors replaced
191 by chardet library if it fails fallback to string with errors replaced
192
192
193 :param unicode_: unicode to encode
193 :param unicode_: unicode to encode
194 :rtype: str
194 :rtype: str
195 :returns: str object
195 :returns: str object
196 """
196 """
197
197
198 # if it's not basestr cast to str
198 # if it's not basestr cast to str
199 if not isinstance(unicode_, basestring):
199 if not isinstance(unicode_, basestring):
200 return str(unicode_)
200 return str(unicode_)
201
201
202 if isinstance(unicode_, str):
202 if isinstance(unicode_, str):
203 return unicode_
203 return unicode_
204
204
205 if not to_encoding:
205 if not to_encoding:
206 import rhodecode
206 import rhodecode
207 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
207 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
208 to_encoding = DEFAULT_ENCODING
208 to_encoding = DEFAULT_ENCODING
209
209
210 try:
210 try:
211 return unicode_.encode(to_encoding)
211 return unicode_.encode(to_encoding)
212 except UnicodeEncodeError:
212 except UnicodeEncodeError:
213 pass
213 pass
214
214
215 try:
215 try:
216 import chardet
216 import chardet
217 encoding = chardet.detect(unicode_)['encoding']
217 encoding = chardet.detect(unicode_)['encoding']
218 print encoding
219 if encoding is None:
218 if encoding is None:
220 raise UnicodeEncodeError()
219 raise UnicodeEncodeError()
221
220
222 return unicode_.encode(encoding)
221 return unicode_.encode(encoding)
223 except (ImportError, UnicodeEncodeError):
222 except (ImportError, UnicodeEncodeError):
224 return unicode_.encode(to_encoding, 'replace')
223 return unicode_.encode(to_encoding, 'replace')
225
224
226 return safe_str
225 return safe_str
227
226
228
227
229 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
228 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
230 """
229 """
231 Custom engine_from_config functions that makes sure we use NullPool for
230 Custom engine_from_config functions that makes sure we use NullPool for
232 file based sqlite databases. This prevents errors on sqlite. This only
231 file based sqlite databases. This prevents errors on sqlite. This only
233 applies to sqlalchemy versions < 0.7.0
232 applies to sqlalchemy versions < 0.7.0
234
233
235 """
234 """
236 import sqlalchemy
235 import sqlalchemy
237 from sqlalchemy import engine_from_config as efc
236 from sqlalchemy import engine_from_config as efc
238 import logging
237 import logging
239
238
240 if int(sqlalchemy.__version__.split('.')[1]) < 7:
239 if int(sqlalchemy.__version__.split('.')[1]) < 7:
241
240
242 # This solution should work for sqlalchemy < 0.7.0, and should use
241 # This solution should work for sqlalchemy < 0.7.0, and should use
243 # proxy=TimerProxy() for execution time profiling
242 # proxy=TimerProxy() for execution time profiling
244
243
245 from sqlalchemy.pool import NullPool
244 from sqlalchemy.pool import NullPool
246 url = configuration[prefix + 'url']
245 url = configuration[prefix + 'url']
247
246
248 if url.startswith('sqlite'):
247 if url.startswith('sqlite'):
249 kwargs.update({'poolclass': NullPool})
248 kwargs.update({'poolclass': NullPool})
250 return efc(configuration, prefix, **kwargs)
249 return efc(configuration, prefix, **kwargs)
251 else:
250 else:
252 import time
251 import time
253 from sqlalchemy import event
252 from sqlalchemy import event
254 from sqlalchemy.engine import Engine
253 from sqlalchemy.engine import Engine
255
254
256 log = logging.getLogger('sqlalchemy.engine')
255 log = logging.getLogger('sqlalchemy.engine')
257 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
256 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
258 engine = efc(configuration, prefix, **kwargs)
257 engine = efc(configuration, prefix, **kwargs)
259
258
260 def color_sql(sql):
259 def color_sql(sql):
261 COLOR_SEQ = "\033[1;%dm"
260 COLOR_SEQ = "\033[1;%dm"
262 COLOR_SQL = YELLOW
261 COLOR_SQL = YELLOW
263 normal = '\x1b[0m'
262 normal = '\x1b[0m'
264 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
263 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
265
264
266 if configuration['debug']:
265 if configuration['debug']:
267 #attach events only for debug configuration
266 #attach events only for debug configuration
268
267
269 def before_cursor_execute(conn, cursor, statement,
268 def before_cursor_execute(conn, cursor, statement,
270 parameters, context, executemany):
269 parameters, context, executemany):
271 context._query_start_time = time.time()
270 context._query_start_time = time.time()
272 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
271 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
273
272
274
273
275 def after_cursor_execute(conn, cursor, statement,
274 def after_cursor_execute(conn, cursor, statement,
276 parameters, context, executemany):
275 parameters, context, executemany):
277 total = time.time() - context._query_start_time
276 total = time.time() - context._query_start_time
278 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
277 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
279
278
280 event.listen(engine, "before_cursor_execute",
279 event.listen(engine, "before_cursor_execute",
281 before_cursor_execute)
280 before_cursor_execute)
282 event.listen(engine, "after_cursor_execute",
281 event.listen(engine, "after_cursor_execute",
283 after_cursor_execute)
282 after_cursor_execute)
284
283
285 return engine
284 return engine
286
285
287
286
288 def age(curdate):
287 def age(curdate):
289 """
288 """
290 turns a datetime into an age string.
289 turns a datetime into an age string.
291
290
292 :param curdate: datetime object
291 :param curdate: datetime object
293 :rtype: unicode
292 :rtype: unicode
294 :returns: unicode words describing age
293 :returns: unicode words describing age
295 """
294 """
296
295
297 from datetime import datetime
296 from datetime import datetime
298 from webhelpers.date import time_ago_in_words
297 from webhelpers.date import time_ago_in_words
299
298
300 _ = lambda s: s
299 _ = lambda s: s
301
300
302 if not curdate:
301 if not curdate:
303 return ''
302 return ''
304
303
305 agescales = [(_(u"year"), 3600 * 24 * 365),
304 agescales = [(_(u"year"), 3600 * 24 * 365),
306 (_(u"month"), 3600 * 24 * 30),
305 (_(u"month"), 3600 * 24 * 30),
307 (_(u"day"), 3600 * 24),
306 (_(u"day"), 3600 * 24),
308 (_(u"hour"), 3600),
307 (_(u"hour"), 3600),
309 (_(u"minute"), 60),
308 (_(u"minute"), 60),
310 (_(u"second"), 1), ]
309 (_(u"second"), 1), ]
311
310
312 age = datetime.now() - curdate
311 age = datetime.now() - curdate
313 age_seconds = (age.days * agescales[2][1]) + age.seconds
312 age_seconds = (age.days * agescales[2][1]) + age.seconds
314 pos = 1
313 pos = 1
315 for scale in agescales:
314 for scale in agescales:
316 if scale[1] <= age_seconds:
315 if scale[1] <= age_seconds:
317 if pos == 6:
316 if pos == 6:
318 pos = 5
317 pos = 5
319 return '%s %s' % (time_ago_in_words(curdate,
318 return '%s %s' % (time_ago_in_words(curdate,
320 agescales[pos][0]), _('ago'))
319 agescales[pos][0]), _('ago'))
321 pos += 1
320 pos += 1
322
321
323 return _(u'just now')
322 return _(u'just now')
324
323
325
324
326 def uri_filter(uri):
325 def uri_filter(uri):
327 """
326 """
328 Removes user:password from given url string
327 Removes user:password from given url string
329
328
330 :param uri:
329 :param uri:
331 :rtype: unicode
330 :rtype: unicode
332 :returns: filtered list of strings
331 :returns: filtered list of strings
333 """
332 """
334 if not uri:
333 if not uri:
335 return ''
334 return ''
336
335
337 proto = ''
336 proto = ''
338
337
339 for pat in ('https://', 'http://'):
338 for pat in ('https://', 'http://'):
340 if uri.startswith(pat):
339 if uri.startswith(pat):
341 uri = uri[len(pat):]
340 uri = uri[len(pat):]
342 proto = pat
341 proto = pat
343 break
342 break
344
343
345 # remove passwords and username
344 # remove passwords and username
346 uri = uri[uri.find('@') + 1:]
345 uri = uri[uri.find('@') + 1:]
347
346
348 # get the port
347 # get the port
349 cred_pos = uri.find(':')
348 cred_pos = uri.find(':')
350 if cred_pos == -1:
349 if cred_pos == -1:
351 host, port = uri, None
350 host, port = uri, None
352 else:
351 else:
353 host, port = uri[:cred_pos], uri[cred_pos + 1:]
352 host, port = uri[:cred_pos], uri[cred_pos + 1:]
354
353
355 return filter(None, [proto, host, port])
354 return filter(None, [proto, host, port])
356
355
357
356
358 def credentials_filter(uri):
357 def credentials_filter(uri):
359 """
358 """
360 Returns a url with removed credentials
359 Returns a url with removed credentials
361
360
362 :param uri:
361 :param uri:
363 """
362 """
364
363
365 uri = uri_filter(uri)
364 uri = uri_filter(uri)
366 #check if we have port
365 #check if we have port
367 if len(uri) > 2 and uri[2]:
366 if len(uri) > 2 and uri[2]:
368 uri[2] = ':' + uri[2]
367 uri[2] = ':' + uri[2]
369
368
370 return ''.join(uri)
369 return ''.join(uri)
371
370
372
371
373 def get_changeset_safe(repo, rev):
372 def get_changeset_safe(repo, rev):
374 """
373 """
375 Safe version of get_changeset if this changeset doesn't exists for a
374 Safe version of get_changeset if this changeset doesn't exists for a
376 repo it returns a Dummy one instead
375 repo it returns a Dummy one instead
377
376
378 :param repo:
377 :param repo:
379 :param rev:
378 :param rev:
380 """
379 """
381 from rhodecode.lib.vcs.backends.base import BaseRepository
380 from rhodecode.lib.vcs.backends.base import BaseRepository
382 from rhodecode.lib.vcs.exceptions import RepositoryError
381 from rhodecode.lib.vcs.exceptions import RepositoryError
383 if not isinstance(repo, BaseRepository):
382 if not isinstance(repo, BaseRepository):
384 raise Exception('You must pass an Repository '
383 raise Exception('You must pass an Repository '
385 'object as first argument got %s', type(repo))
384 'object as first argument got %s', type(repo))
386
385
387 try:
386 try:
388 cs = repo.get_changeset(rev)
387 cs = repo.get_changeset(rev)
389 except RepositoryError:
388 except RepositoryError:
390 from rhodecode.lib.utils import EmptyChangeset
389 from rhodecode.lib.utils import EmptyChangeset
391 cs = EmptyChangeset(requested_revision=rev)
390 cs = EmptyChangeset(requested_revision=rev)
392 return cs
391 return cs
393
392
394
393
395 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
394 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
396
395
397
396
398 def extract_mentioned_users(s):
397 def extract_mentioned_users(s):
399 """
398 """
400 Returns unique usernames from given string s that have @mention
399 Returns unique usernames from given string s that have @mention
401
400
402 :param s: string to get mentions
401 :param s: string to get mentions
403 """
402 """
404 usrs = set()
403 usrs = set()
405 for username in re.findall(MENTIONS_REGEX, s):
404 for username in re.findall(MENTIONS_REGEX, s):
406 usrs.add(username)
405 usrs.add(username)
407
406
408 return sorted(list(usrs), key=lambda k: k.lower())
407 return sorted(list(usrs), key=lambda k: k.lower())
@@ -1,359 +1,357 b''
1 import os
1 import os
2 import posixpath
2 import posixpath
3
3
4 from rhodecode.lib.vcs.backends.base import BaseChangeset
4 from rhodecode.lib.vcs.backends.base import BaseChangeset
5 from rhodecode.lib.vcs.conf import settings
5 from rhodecode.lib.vcs.conf import settings
6 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError, \
6 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError, \
7 ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
7 ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
8 from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, \
8 from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, \
9 ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, \
9 ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, \
10 RemovedFileNodesGenerator, RootNode, SubModuleNode
10 RemovedFileNodesGenerator, RootNode, SubModuleNode
11
11
12 from rhodecode.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
12 from rhodecode.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
13 from rhodecode.lib.vcs.utils.lazy import LazyProperty
13 from rhodecode.lib.vcs.utils.lazy import LazyProperty
14 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
14 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
15
15
16 from ...utils.hgcompat import archival, hex
16 from ...utils.hgcompat import archival, hex
17
17
18
18
19 class MercurialChangeset(BaseChangeset):
19 class MercurialChangeset(BaseChangeset):
20 """
20 """
21 Represents state of the repository at the single revision.
21 Represents state of the repository at the single revision.
22 """
22 """
23
23
24 def __init__(self, repository, revision):
24 def __init__(self, repository, revision):
25 self.repository = repository
25 self.repository = repository
26 self.raw_id = revision
26 self.raw_id = revision
27 self._ctx = repository._repo[revision]
27 self._ctx = repository._repo[revision]
28 self.revision = self._ctx._rev
28 self.revision = self._ctx._rev
29 self.nodes = {}
29 self.nodes = {}
30
30
31 @LazyProperty
31 @LazyProperty
32 def tags(self):
32 def tags(self):
33 return map(safe_unicode, self._ctx.tags())
33 return map(safe_unicode, self._ctx.tags())
34
34
35 @LazyProperty
35 @LazyProperty
36 def branch(self):
36 def branch(self):
37 return safe_unicode(self._ctx.branch())
37 return safe_unicode(self._ctx.branch())
38
38
39 @LazyProperty
39 @LazyProperty
40 def bookmarks(self):
40 def bookmarks(self):
41 return map(safe_unicode, self._ctx.bookmarks())
41 return map(safe_unicode, self._ctx.bookmarks())
42
42
43 @LazyProperty
43 @LazyProperty
44 def message(self):
44 def message(self):
45 return safe_unicode(self._ctx.description())
45 return safe_unicode(self._ctx.description())
46
46
47 @LazyProperty
47 @LazyProperty
48 def author(self):
48 def author(self):
49 return safe_unicode(self._ctx.user())
49 return safe_unicode(self._ctx.user())
50
50
51 @LazyProperty
51 @LazyProperty
52 def date(self):
52 def date(self):
53 return date_fromtimestamp(*self._ctx.date())
53 return date_fromtimestamp(*self._ctx.date())
54
54
55 @LazyProperty
55 @LazyProperty
56 def status(self):
56 def status(self):
57 """
57 """
58 Returns modified, added, removed, deleted files for current changeset
58 Returns modified, added, removed, deleted files for current changeset
59 """
59 """
60 return self.repository._repo.status(self._ctx.p1().node(),
60 return self.repository._repo.status(self._ctx.p1().node(),
61 self._ctx.node())
61 self._ctx.node())
62
62
63 @LazyProperty
63 @LazyProperty
64 def _file_paths(self):
64 def _file_paths(self):
65 return list(self._ctx)
65 return list(self._ctx)
66
66
67 @LazyProperty
67 @LazyProperty
68 def _dir_paths(self):
68 def _dir_paths(self):
69 p = list(set(get_dirs_for_path(*self._file_paths)))
69 p = list(set(get_dirs_for_path(*self._file_paths)))
70 p.insert(0, '')
70 p.insert(0, '')
71 return p
71 return p
72
72
73 @LazyProperty
73 @LazyProperty
74 def _paths(self):
74 def _paths(self):
75 return self._dir_paths + self._file_paths
75 return self._dir_paths + self._file_paths
76
76
77 @LazyProperty
77 @LazyProperty
78 def id(self):
78 def id(self):
79 if self.last:
79 if self.last:
80 return u'tip'
80 return u'tip'
81 return self.short_id
81 return self.short_id
82
82
83 @LazyProperty
83 @LazyProperty
84 def short_id(self):
84 def short_id(self):
85 return self.raw_id[:12]
85 return self.raw_id[:12]
86
86
87 @LazyProperty
87 @LazyProperty
88 def parents(self):
88 def parents(self):
89 """
89 """
90 Returns list of parents changesets.
90 Returns list of parents changesets.
91 """
91 """
92 return [self.repository.get_changeset(parent.rev())
92 return [self.repository.get_changeset(parent.rev())
93 for parent in self._ctx.parents() if parent.rev() >= 0]
93 for parent in self._ctx.parents() if parent.rev() >= 0]
94
94
95 def next(self, branch=None):
95 def next(self, branch=None):
96
96
97 if branch and self.branch != branch:
97 if branch and self.branch != branch:
98 raise VCSError('Branch option used on changeset not belonging '
98 raise VCSError('Branch option used on changeset not belonging '
99 'to that branch')
99 'to that branch')
100
100
101 def _next(changeset, branch):
101 def _next(changeset, branch):
102 try:
102 try:
103 next_ = changeset.revision + 1
103 next_ = changeset.revision + 1
104 next_rev = changeset.repository.revisions[next_]
104 next_rev = changeset.repository.revisions[next_]
105 except IndexError:
105 except IndexError:
106 raise ChangesetDoesNotExistError
106 raise ChangesetDoesNotExistError
107 cs = changeset.repository.get_changeset(next_rev)
107 cs = changeset.repository.get_changeset(next_rev)
108
108
109 if branch and branch != cs.branch:
109 if branch and branch != cs.branch:
110 return _next(cs, branch)
110 return _next(cs, branch)
111
111
112 return cs
112 return cs
113
113
114 return _next(self, branch)
114 return _next(self, branch)
115
115
116 def prev(self, branch=None):
116 def prev(self, branch=None):
117 if branch and self.branch != branch:
117 if branch and self.branch != branch:
118 raise VCSError('Branch option used on changeset not belonging '
118 raise VCSError('Branch option used on changeset not belonging '
119 'to that branch')
119 'to that branch')
120
120
121 def _prev(changeset, branch):
121 def _prev(changeset, branch):
122 try:
122 try:
123 prev_ = changeset.revision - 1
123 prev_ = changeset.revision - 1
124 if prev_ < 0:
124 if prev_ < 0:
125 raise IndexError
125 raise IndexError
126 prev_rev = changeset.repository.revisions[prev_]
126 prev_rev = changeset.repository.revisions[prev_]
127 except IndexError:
127 except IndexError:
128 raise ChangesetDoesNotExistError
128 raise ChangesetDoesNotExistError
129
129
130 cs = changeset.repository.get_changeset(prev_rev)
130 cs = changeset.repository.get_changeset(prev_rev)
131
131
132 if branch and branch != cs.branch:
132 if branch and branch != cs.branch:
133 return _prev(cs, branch)
133 return _prev(cs, branch)
134
134
135 return cs
135 return cs
136
136
137 return _prev(self, branch)
137 return _prev(self, branch)
138
138
139 def _fix_path(self, path):
139 def _fix_path(self, path):
140 """
140 """
141 Paths are stored without trailing slash so we need to get rid off it if
141 Paths are stored without trailing slash so we need to get rid off it if
142 needed. Also mercurial keeps filenodes as str so we need to decode
142 needed. Also mercurial keeps filenodes as str so we need to decode
143 from unicode to str
143 from unicode to str
144 """
144 """
145 if path.endswith('/'):
145 if path.endswith('/'):
146 path = path.rstrip('/')
146 path = path.rstrip('/')
147
147
148 return safe_str(path)
148 return safe_str(path)
149
149
150 def _get_kind(self, path):
150 def _get_kind(self, path):
151 path = self._fix_path(path)
151 path = self._fix_path(path)
152 if path in self._file_paths:
152 if path in self._file_paths:
153 return NodeKind.FILE
153 return NodeKind.FILE
154 elif path in self._dir_paths:
154 elif path in self._dir_paths:
155 return NodeKind.DIR
155 return NodeKind.DIR
156 else:
156 else:
157 raise ChangesetError("Node does not exist at the given path %r"
157 raise ChangesetError("Node does not exist at the given path %r"
158 % (path))
158 % (path))
159
159
160 def _get_filectx(self, path):
160 def _get_filectx(self, path):
161 path = self._fix_path(path)
161 path = self._fix_path(path)
162 if self._get_kind(path) != NodeKind.FILE:
162 if self._get_kind(path) != NodeKind.FILE:
163 raise ChangesetError("File does not exist for revision %r at "
163 raise ChangesetError("File does not exist for revision %r at "
164 " %r" % (self.revision, path))
164 " %r" % (self.revision, path))
165 return self._ctx.filectx(path)
165 return self._ctx.filectx(path)
166
166
167 def _extract_submodules(self):
167 def _extract_submodules(self):
168 """
168 """
169 returns a dictionary with submodule information from substate file
169 returns a dictionary with submodule information from substate file
170 of hg repository
170 of hg repository
171 """
171 """
172 return self._ctx.substate
172 return self._ctx.substate
173
173
174 def get_file_mode(self, path):
174 def get_file_mode(self, path):
175 """
175 """
176 Returns stat mode of the file at the given ``path``.
176 Returns stat mode of the file at the given ``path``.
177 """
177 """
178 fctx = self._get_filectx(path)
178 fctx = self._get_filectx(path)
179 if 'x' in fctx.flags():
179 if 'x' in fctx.flags():
180 return 0100755
180 return 0100755
181 else:
181 else:
182 return 0100644
182 return 0100644
183
183
184 def get_file_content(self, path):
184 def get_file_content(self, path):
185 """
185 """
186 Returns content of the file at given ``path``.
186 Returns content of the file at given ``path``.
187 """
187 """
188 fctx = self._get_filectx(path)
188 fctx = self._get_filectx(path)
189 return fctx.data()
189 return fctx.data()
190
190
191 def get_file_size(self, path):
191 def get_file_size(self, path):
192 """
192 """
193 Returns size of the file at given ``path``.
193 Returns size of the file at given ``path``.
194 """
194 """
195 fctx = self._get_filectx(path)
195 fctx = self._get_filectx(path)
196 return fctx.size()
196 return fctx.size()
197
197
198 def get_file_changeset(self, path):
198 def get_file_changeset(self, path):
199 """
199 """
200 Returns last commit of the file at the given ``path``.
200 Returns last commit of the file at the given ``path``.
201 """
201 """
202 node = self.get_node(path)
202 node = self.get_node(path)
203 return node.history[0]
203 return node.history[0]
204
204
205 def get_file_history(self, path):
205 def get_file_history(self, path):
206 """
206 """
207 Returns history of file as reversed list of ``Changeset`` objects for
207 Returns history of file as reversed list of ``Changeset`` objects for
208 which file at given ``path`` has been modified.
208 which file at given ``path`` has been modified.
209 """
209 """
210 fctx = self._get_filectx(path)
210 fctx = self._get_filectx(path)
211 nodes = [fctx.filectx(x).node() for x in fctx.filelog()]
211 nodes = [fctx.filectx(x).node() for x in fctx.filelog()]
212 changesets = [self.repository.get_changeset(hex(node))
212 changesets = [self.repository.get_changeset(hex(node))
213 for node in reversed(nodes)]
213 for node in reversed(nodes)]
214 return changesets
214 return changesets
215
215
216 def get_file_annotate(self, path):
216 def get_file_annotate(self, path):
217 """
217 """
218 Returns a list of three element tuples with lineno,changeset and line
218 Returns a list of three element tuples with lineno,changeset and line
219 """
219 """
220 fctx = self._get_filectx(path)
220 fctx = self._get_filectx(path)
221 annotate = []
221 annotate = []
222 for i, annotate_data in enumerate(fctx.annotate()):
222 for i, annotate_data in enumerate(fctx.annotate()):
223 ln_no = i + 1
223 ln_no = i + 1
224 annotate.append((ln_no, self.repository\
224 annotate.append((ln_no, self.repository\
225 .get_changeset(hex(annotate_data[0].node())),
225 .get_changeset(hex(annotate_data[0].node())),
226 annotate_data[1],))
226 annotate_data[1],))
227
227
228 return annotate
228 return annotate
229
229
230 def fill_archive(self, stream=None, kind='tgz', prefix=None,
230 def fill_archive(self, stream=None, kind='tgz', prefix=None,
231 subrepos=False):
231 subrepos=False):
232 """
232 """
233 Fills up given stream.
233 Fills up given stream.
234
234
235 :param stream: file like object.
235 :param stream: file like object.
236 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
236 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
237 Default: ``tgz``.
237 Default: ``tgz``.
238 :param prefix: name of root directory in archive.
238 :param prefix: name of root directory in archive.
239 Default is repository name and changeset's raw_id joined with dash
239 Default is repository name and changeset's raw_id joined with dash
240 (``repo-tip.<KIND>``).
240 (``repo-tip.<KIND>``).
241 :param subrepos: include subrepos in this archive.
241 :param subrepos: include subrepos in this archive.
242
242
243 :raise ImproperArchiveTypeError: If given kind is wrong.
243 :raise ImproperArchiveTypeError: If given kind is wrong.
244 :raise VcsError: If given stream is None
244 :raise VcsError: If given stream is None
245 """
245 """
246
246
247 allowed_kinds = settings.ARCHIVE_SPECS.keys()
247 allowed_kinds = settings.ARCHIVE_SPECS.keys()
248 if kind not in allowed_kinds:
248 if kind not in allowed_kinds:
249 raise ImproperArchiveTypeError('Archive kind not supported use one'
249 raise ImproperArchiveTypeError('Archive kind not supported use one'
250 'of %s', allowed_kinds)
250 'of %s', allowed_kinds)
251
251
252 if stream is None:
252 if stream is None:
253 raise VCSError('You need to pass in a valid stream for filling'
253 raise VCSError('You need to pass in a valid stream for filling'
254 ' with archival data')
254 ' with archival data')
255
255
256 if prefix is None:
256 if prefix is None:
257 prefix = '%s-%s' % (self.repository.name, self.short_id)
257 prefix = '%s-%s' % (self.repository.name, self.short_id)
258 elif prefix.startswith('/'):
258 elif prefix.startswith('/'):
259 raise VCSError("Prefix cannot start with leading slash")
259 raise VCSError("Prefix cannot start with leading slash")
260 elif prefix.strip() == '':
260 elif prefix.strip() == '':
261 raise VCSError("Prefix cannot be empty")
261 raise VCSError("Prefix cannot be empty")
262
262
263 print stream.closed
264 archival.archive(self.repository._repo, stream, self.raw_id,
263 archival.archive(self.repository._repo, stream, self.raw_id,
265 kind, prefix=prefix, subrepos=subrepos)
264 kind, prefix=prefix, subrepos=subrepos)
266 print stream.closed
265
267
268 if stream.closed and hasattr(stream, 'name'):
266 if stream.closed and hasattr(stream, 'name'):
269 stream = open(stream.name, 'rb')
267 stream = open(stream.name, 'rb')
270 elif hasattr(stream, 'mode') and 'r' not in stream.mode:
268 elif hasattr(stream, 'mode') and 'r' not in stream.mode:
271 stream = open(stream.name, 'rb')
269 stream = open(stream.name, 'rb')
272 else:
270 else:
273 stream.seek(0)
271 stream.seek(0)
274
272
275 def get_nodes(self, path):
273 def get_nodes(self, path):
276 """
274 """
277 Returns combined ``DirNode`` and ``FileNode`` objects list representing
275 Returns combined ``DirNode`` and ``FileNode`` objects list representing
278 state of changeset at the given ``path``. If node at the given ``path``
276 state of changeset at the given ``path``. If node at the given ``path``
279 is not instance of ``DirNode``, ChangesetError would be raised.
277 is not instance of ``DirNode``, ChangesetError would be raised.
280 """
278 """
281
279
282 if self._get_kind(path) != NodeKind.DIR:
280 if self._get_kind(path) != NodeKind.DIR:
283 raise ChangesetError("Directory does not exist for revision %r at "
281 raise ChangesetError("Directory does not exist for revision %r at "
284 " %r" % (self.revision, path))
282 " %r" % (self.revision, path))
285 path = self._fix_path(path)
283 path = self._fix_path(path)
286
284
287 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
285 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
288 if os.path.dirname(f) == path]
286 if os.path.dirname(f) == path]
289 dirs = path == '' and '' or [d for d in self._dir_paths
287 dirs = path == '' and '' or [d for d in self._dir_paths
290 if d and posixpath.dirname(d) == path]
288 if d and posixpath.dirname(d) == path]
291 dirnodes = [DirNode(d, changeset=self) for d in dirs
289 dirnodes = [DirNode(d, changeset=self) for d in dirs
292 if os.path.dirname(d) == path]
290 if os.path.dirname(d) == path]
293
291
294 als = self.repository.alias
292 als = self.repository.alias
295 for k, vals in self._extract_submodules().iteritems():
293 for k, vals in self._extract_submodules().iteritems():
296 #vals = url,rev,type
294 #vals = url,rev,type
297 loc = vals[0]
295 loc = vals[0]
298 cs = vals[1]
296 cs = vals[1]
299 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
297 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
300 alias=als))
298 alias=als))
301 nodes = dirnodes + filenodes
299 nodes = dirnodes + filenodes
302 # cache nodes
300 # cache nodes
303 for node in nodes:
301 for node in nodes:
304 self.nodes[node.path] = node
302 self.nodes[node.path] = node
305 nodes.sort()
303 nodes.sort()
306
304
307 return nodes
305 return nodes
308
306
309 def get_node(self, path):
307 def get_node(self, path):
310 """
308 """
311 Returns ``Node`` object from the given ``path``. If there is no node at
309 Returns ``Node`` object from the given ``path``. If there is no node at
312 the given ``path``, ``ChangesetError`` would be raised.
310 the given ``path``, ``ChangesetError`` would be raised.
313 """
311 """
314
312
315 path = self._fix_path(path)
313 path = self._fix_path(path)
316
314
317 if not path in self.nodes:
315 if not path in self.nodes:
318 if path in self._file_paths:
316 if path in self._file_paths:
319 node = FileNode(path, changeset=self)
317 node = FileNode(path, changeset=self)
320 elif path in self._dir_paths or path in self._dir_paths:
318 elif path in self._dir_paths or path in self._dir_paths:
321 if path == '':
319 if path == '':
322 node = RootNode(changeset=self)
320 node = RootNode(changeset=self)
323 else:
321 else:
324 node = DirNode(path, changeset=self)
322 node = DirNode(path, changeset=self)
325 else:
323 else:
326 raise NodeDoesNotExistError("There is no file nor directory "
324 raise NodeDoesNotExistError("There is no file nor directory "
327 "at the given path: %r at revision %r"
325 "at the given path: %r at revision %r"
328 % (path, self.short_id))
326 % (path, self.short_id))
329 # cache node
327 # cache node
330 self.nodes[path] = node
328 self.nodes[path] = node
331 return self.nodes[path]
329 return self.nodes[path]
332
330
333 @LazyProperty
331 @LazyProperty
334 def affected_files(self):
332 def affected_files(self):
335 """
333 """
336 Get's a fast accessible file changes for given changeset
334 Get's a fast accessible file changes for given changeset
337 """
335 """
338 return self._ctx.files()
336 return self._ctx.files()
339
337
340 @property
338 @property
341 def added(self):
339 def added(self):
342 """
340 """
343 Returns list of added ``FileNode`` objects.
341 Returns list of added ``FileNode`` objects.
344 """
342 """
345 return AddedFileNodesGenerator([n for n in self.status[1]], self)
343 return AddedFileNodesGenerator([n for n in self.status[1]], self)
346
344
347 @property
345 @property
348 def changed(self):
346 def changed(self):
349 """
347 """
350 Returns list of modified ``FileNode`` objects.
348 Returns list of modified ``FileNode`` objects.
351 """
349 """
352 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
350 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
353
351
354 @property
352 @property
355 def removed(self):
353 def removed(self):
356 """
354 """
357 Returns list of removed ``FileNode`` objects.
355 Returns list of removed ``FileNode`` objects.
358 """
356 """
359 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
357 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
@@ -1,139 +1,138 b''
1 """
1 """
2 This module provides some useful tools for ``vcs`` like annotate/diff html
2 This module provides some useful tools for ``vcs`` like annotate/diff html
3 output. It also includes some internal helpers.
3 output. It also includes some internal helpers.
4 """
4 """
5 import sys
5 import sys
6 import time
6 import time
7 import datetime
7 import datetime
8
8
9
9
10 def makedate():
10 def makedate():
11 lt = time.localtime()
11 lt = time.localtime()
12 if lt[8] == 1 and time.daylight:
12 if lt[8] == 1 and time.daylight:
13 tz = time.altzone
13 tz = time.altzone
14 else:
14 else:
15 tz = time.timezone
15 tz = time.timezone
16 return time.mktime(lt), tz
16 return time.mktime(lt), tz
17
17
18
18
19 def date_fromtimestamp(unixts, tzoffset=0):
19 def date_fromtimestamp(unixts, tzoffset=0):
20 """
20 """
21 Makes a local datetime object out of unix timestamp
21 Makes a local datetime object out of unix timestamp
22
22
23 :param unixts:
23 :param unixts:
24 :param tzoffset:
24 :param tzoffset:
25 """
25 """
26
26
27 return datetime.datetime.fromtimestamp(float(unixts))
27 return datetime.datetime.fromtimestamp(float(unixts))
28
28
29
29
30 def safe_unicode(str_, from_encoding=None):
30 def safe_unicode(str_, from_encoding=None):
31 """
31 """
32 safe unicode function. Does few trick to turn str_ into unicode
32 safe unicode function. Does few trick to turn str_ into unicode
33
33
34 In case of UnicodeDecode error we try to return it with encoding detected
34 In case of UnicodeDecode error we try to return it with encoding detected
35 by chardet library if it fails fallback to unicode with errors replaced
35 by chardet library if it fails fallback to unicode with errors replaced
36
36
37 :param str_: string to decode
37 :param str_: string to decode
38 :rtype: unicode
38 :rtype: unicode
39 :returns: unicode object
39 :returns: unicode object
40 """
40 """
41 if isinstance(str_, unicode):
41 if isinstance(str_, unicode):
42 return str_
42 return str_
43 if not from_encoding:
43 if not from_encoding:
44 import rhodecode
44 import rhodecode
45 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding', 'utf8')
45 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding', 'utf8')
46 from_encoding = DEFAULT_ENCODING
46 from_encoding = DEFAULT_ENCODING
47 try:
47 try:
48 return unicode(str_)
48 return unicode(str_)
49 except UnicodeDecodeError:
49 except UnicodeDecodeError:
50 pass
50 pass
51
51
52 try:
52 try:
53 return unicode(str_, from_encoding)
53 return unicode(str_, from_encoding)
54 except UnicodeDecodeError:
54 except UnicodeDecodeError:
55 pass
55 pass
56
56
57 try:
57 try:
58 import chardet
58 import chardet
59 encoding = chardet.detect(str_)['encoding']
59 encoding = chardet.detect(str_)['encoding']
60 if encoding is None:
60 if encoding is None:
61 raise Exception()
61 raise Exception()
62 return str_.decode(encoding)
62 return str_.decode(encoding)
63 except (ImportError, UnicodeDecodeError, Exception):
63 except (ImportError, UnicodeDecodeError, Exception):
64 return unicode(str_, from_encoding, 'replace')
64 return unicode(str_, from_encoding, 'replace')
65
65
66
66
67 def safe_str(unicode_, to_encoding=None):
67 def safe_str(unicode_, to_encoding=None):
68 """
68 """
69 safe str function. Does few trick to turn unicode_ into string
69 safe str function. Does few trick to turn unicode_ into string
70
70
71 In case of UnicodeEncodeError we try to return it with encoding detected
71 In case of UnicodeEncodeError we try to return it with encoding detected
72 by chardet library if it fails fallback to string with errors replaced
72 by chardet library if it fails fallback to string with errors replaced
73
73
74 :param unicode_: unicode to encode
74 :param unicode_: unicode to encode
75 :rtype: str
75 :rtype: str
76 :returns: str object
76 :returns: str object
77 """
77 """
78
78
79 if isinstance(unicode_, str):
79 if isinstance(unicode_, str):
80 return unicode_
80 return unicode_
81 if not to_encoding:
81 if not to_encoding:
82 import rhodecode
82 import rhodecode
83 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding', 'utf8')
83 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding', 'utf8')
84 to_encoding = DEFAULT_ENCODING
84 to_encoding = DEFAULT_ENCODING
85 try:
85 try:
86 return unicode_.encode(to_encoding)
86 return unicode_.encode(to_encoding)
87 except UnicodeEncodeError:
87 except UnicodeEncodeError:
88 pass
88 pass
89
89
90 try:
90 try:
91 import chardet
91 import chardet
92 encoding = chardet.detect(unicode_)['encoding']
92 encoding = chardet.detect(unicode_)['encoding']
93 print encoding
94 if encoding is None:
93 if encoding is None:
95 raise UnicodeEncodeError()
94 raise UnicodeEncodeError()
96
95
97 return unicode_.encode(encoding)
96 return unicode_.encode(encoding)
98 except (ImportError, UnicodeEncodeError):
97 except (ImportError, UnicodeEncodeError):
99 return unicode_.encode(to_encoding, 'replace')
98 return unicode_.encode(to_encoding, 'replace')
100
99
101 return safe_str
100 return safe_str
102
101
103
102
104 def author_email(author):
103 def author_email(author):
105 """
104 """
106 returns email address of given author.
105 returns email address of given author.
107 If any of <,> sign are found, it fallbacks to regex findall()
106 If any of <,> sign are found, it fallbacks to regex findall()
108 and returns first found result or empty string
107 and returns first found result or empty string
109
108
110 Regex taken from http://www.regular-expressions.info/email.html
109 Regex taken from http://www.regular-expressions.info/email.html
111 """
110 """
112 import re
111 import re
113 r = author.find('>')
112 r = author.find('>')
114 l = author.find('<')
113 l = author.find('<')
115
114
116 if l == -1 or r == -1:
115 if l == -1 or r == -1:
117 # fallback to regex match of email out of a string
116 # fallback to regex match of email out of a string
118 email_re = re.compile(r"""[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!"""
117 email_re = re.compile(r"""[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!"""
119 r"""#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z"""
118 r"""#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z"""
120 r"""0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]"""
119 r"""0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]"""
121 r"""*[a-z0-9])?""", re.IGNORECASE)
120 r"""*[a-z0-9])?""", re.IGNORECASE)
122 m = re.findall(email_re, author)
121 m = re.findall(email_re, author)
123 return m[0] if m else ''
122 return m[0] if m else ''
124
123
125 return author[l + 1:r].strip()
124 return author[l + 1:r].strip()
126
125
127
126
128 def author_name(author):
127 def author_name(author):
129 """
128 """
130 get name of author, or else username.
129 get name of author, or else username.
131 It'll try to find an email in the author string and just cut it off
130 It'll try to find an email in the author string and just cut it off
132 to get the username
131 to get the username
133 """
132 """
134
133
135 if not '@' in author:
134 if not '@' in author:
136 return author
135 return author
137 else:
136 else:
138 return author.replace(author_email(author), '').replace('<', '')\
137 return author.replace(author_email(author), '').replace('<', '')\
139 .replace('>', '').strip()
138 .replace('>', '').strip()
@@ -1,590 +1,589 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user
3 rhodecode.model.user
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 users model for RhodeCode
6 users model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons import url
29 from pylons import url
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
32 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
33 from rhodecode.lib.caching_query import FromCache
33 from rhodecode.lib.caching_query import FromCache
34
34
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup,\
38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup,\
39 UsersGroupRepoGroupToPerm
39 UsersGroupRepoGroupToPerm
40 from rhodecode.lib.exceptions import DefaultUserException, \
40 from rhodecode.lib.exceptions import DefaultUserException, \
41 UserOwnsReposException
41 UserOwnsReposException
42
42
43 from sqlalchemy.exc import DatabaseError
43 from sqlalchemy.exc import DatabaseError
44
44
45 from sqlalchemy.orm import joinedload
45 from sqlalchemy.orm import joinedload
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 PERM_WEIGHTS = {
50 PERM_WEIGHTS = {
51 'repository.none': 0,
51 'repository.none': 0,
52 'repository.read': 1,
52 'repository.read': 1,
53 'repository.write': 3,
53 'repository.write': 3,
54 'repository.admin': 4,
54 'repository.admin': 4,
55 'group.none': 0,
55 'group.none': 0,
56 'group.read': 1,
56 'group.read': 1,
57 'group.write': 3,
57 'group.write': 3,
58 'group.admin': 4,
58 'group.admin': 4,
59 }
59 }
60
60
61
61
62 class UserModel(BaseModel):
62 class UserModel(BaseModel):
63
63
64 def __get_user(self, user):
64 def __get_user(self, user):
65 return self._get_instance(User, user, callback=User.get_by_username)
65 return self._get_instance(User, user, callback=User.get_by_username)
66
66
67 def __get_perm(self, permission):
67 def __get_perm(self, permission):
68 return self._get_instance(Permission, permission,
68 return self._get_instance(Permission, permission,
69 callback=Permission.get_by_key)
69 callback=Permission.get_by_key)
70
70
71 def get(self, user_id, cache=False):
71 def get(self, user_id, cache=False):
72 user = self.sa.query(User)
72 user = self.sa.query(User)
73 if cache:
73 if cache:
74 user = user.options(FromCache("sql_cache_short",
74 user = user.options(FromCache("sql_cache_short",
75 "get_user_%s" % user_id))
75 "get_user_%s" % user_id))
76 return user.get(user_id)
76 return user.get(user_id)
77
77
78 def get_user(self, user):
78 def get_user(self, user):
79 return self.__get_user(user)
79 return self.__get_user(user)
80
80
81 def get_by_username(self, username, cache=False, case_insensitive=False):
81 def get_by_username(self, username, cache=False, case_insensitive=False):
82
82
83 if case_insensitive:
83 if case_insensitive:
84 user = self.sa.query(User).filter(User.username.ilike(username))
84 user = self.sa.query(User).filter(User.username.ilike(username))
85 else:
85 else:
86 user = self.sa.query(User)\
86 user = self.sa.query(User)\
87 .filter(User.username == username)
87 .filter(User.username == username)
88 if cache:
88 if cache:
89 user = user.options(FromCache("sql_cache_short",
89 user = user.options(FromCache("sql_cache_short",
90 "get_user_%s" % username))
90 "get_user_%s" % username))
91 return user.scalar()
91 return user.scalar()
92
92
93 def get_by_api_key(self, api_key, cache=False):
93 def get_by_api_key(self, api_key, cache=False):
94 return User.get_by_api_key(api_key, cache)
94 return User.get_by_api_key(api_key, cache)
95
95
96 def create(self, form_data):
96 def create(self, form_data):
97 try:
97 try:
98 new_user = User()
98 new_user = User()
99 for k, v in form_data.items():
99 for k, v in form_data.items():
100 setattr(new_user, k, v)
100 setattr(new_user, k, v)
101
101
102 new_user.api_key = generate_api_key(form_data['username'])
102 new_user.api_key = generate_api_key(form_data['username'])
103 self.sa.add(new_user)
103 self.sa.add(new_user)
104 return new_user
104 return new_user
105 except:
105 except:
106 log.error(traceback.format_exc())
106 log.error(traceback.format_exc())
107 raise
107 raise
108
108
109 def create_or_update(self, username, password, email, name, lastname,
109 def create_or_update(self, username, password, email, name, lastname,
110 active=True, admin=False, ldap_dn=None):
110 active=True, admin=False, ldap_dn=None):
111 """
111 """
112 Creates a new instance if not found, or updates current one
112 Creates a new instance if not found, or updates current one
113
113
114 :param username:
114 :param username:
115 :param password:
115 :param password:
116 :param email:
116 :param email:
117 :param active:
117 :param active:
118 :param name:
118 :param name:
119 :param lastname:
119 :param lastname:
120 :param active:
120 :param active:
121 :param admin:
121 :param admin:
122 :param ldap_dn:
122 :param ldap_dn:
123 """
123 """
124
124
125 from rhodecode.lib.auth import get_crypt_password
125 from rhodecode.lib.auth import get_crypt_password
126
126
127 log.debug('Checking for %s account in RhodeCode database' % username)
127 log.debug('Checking for %s account in RhodeCode database' % username)
128 user = User.get_by_username(username, case_insensitive=True)
128 user = User.get_by_username(username, case_insensitive=True)
129 if user is None:
129 if user is None:
130 log.debug('creating new user %s' % username)
130 log.debug('creating new user %s' % username)
131 new_user = User()
131 new_user = User()
132 else:
132 else:
133 log.debug('updating user %s' % username)
133 log.debug('updating user %s' % username)
134 new_user = user
134 new_user = user
135
135
136 try:
136 try:
137 new_user.username = username
137 new_user.username = username
138 new_user.admin = admin
138 new_user.admin = admin
139 new_user.password = get_crypt_password(password)
139 new_user.password = get_crypt_password(password)
140 new_user.api_key = generate_api_key(username)
140 new_user.api_key = generate_api_key(username)
141 new_user.email = email
141 new_user.email = email
142 new_user.active = active
142 new_user.active = active
143 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
143 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
144 new_user.name = name
144 new_user.name = name
145 new_user.lastname = lastname
145 new_user.lastname = lastname
146 self.sa.add(new_user)
146 self.sa.add(new_user)
147 return new_user
147 return new_user
148 except (DatabaseError,):
148 except (DatabaseError,):
149 log.error(traceback.format_exc())
149 log.error(traceback.format_exc())
150 raise
150 raise
151
151
152 def create_for_container_auth(self, username, attrs):
152 def create_for_container_auth(self, username, attrs):
153 """
153 """
154 Creates the given user if it's not already in the database
154 Creates the given user if it's not already in the database
155
155
156 :param username:
156 :param username:
157 :param attrs:
157 :param attrs:
158 """
158 """
159 if self.get_by_username(username, case_insensitive=True) is None:
159 if self.get_by_username(username, case_insensitive=True) is None:
160
160
161 # autogenerate email for container account without one
161 # autogenerate email for container account without one
162 generate_email = lambda usr: '%s@container_auth.account' % usr
162 generate_email = lambda usr: '%s@container_auth.account' % usr
163
163
164 try:
164 try:
165 new_user = User()
165 new_user = User()
166 new_user.username = username
166 new_user.username = username
167 new_user.password = None
167 new_user.password = None
168 new_user.api_key = generate_api_key(username)
168 new_user.api_key = generate_api_key(username)
169 new_user.email = attrs['email']
169 new_user.email = attrs['email']
170 new_user.active = attrs.get('active', True)
170 new_user.active = attrs.get('active', True)
171 new_user.name = attrs['name'] or generate_email(username)
171 new_user.name = attrs['name'] or generate_email(username)
172 new_user.lastname = attrs['lastname']
172 new_user.lastname = attrs['lastname']
173
173
174 self.sa.add(new_user)
174 self.sa.add(new_user)
175 return new_user
175 return new_user
176 except (DatabaseError,):
176 except (DatabaseError,):
177 log.error(traceback.format_exc())
177 log.error(traceback.format_exc())
178 self.sa.rollback()
178 self.sa.rollback()
179 raise
179 raise
180 log.debug('User %s already exists. Skipping creation of account'
180 log.debug('User %s already exists. Skipping creation of account'
181 ' for container auth.', username)
181 ' for container auth.', username)
182 return None
182 return None
183
183
184 def create_ldap(self, username, password, user_dn, attrs):
184 def create_ldap(self, username, password, user_dn, attrs):
185 """
185 """
186 Checks if user is in database, if not creates this user marked
186 Checks if user is in database, if not creates this user marked
187 as ldap user
187 as ldap user
188
188
189 :param username:
189 :param username:
190 :param password:
190 :param password:
191 :param user_dn:
191 :param user_dn:
192 :param attrs:
192 :param attrs:
193 """
193 """
194 from rhodecode.lib.auth import get_crypt_password
194 from rhodecode.lib.auth import get_crypt_password
195 log.debug('Checking for such ldap account in RhodeCode database')
195 log.debug('Checking for such ldap account in RhodeCode database')
196 if self.get_by_username(username, case_insensitive=True) is None:
196 if self.get_by_username(username, case_insensitive=True) is None:
197
197
198 # autogenerate email for ldap account without one
198 # autogenerate email for ldap account without one
199 generate_email = lambda usr: '%s@ldap.account' % usr
199 generate_email = lambda usr: '%s@ldap.account' % usr
200
200
201 try:
201 try:
202 new_user = User()
202 new_user = User()
203 username = username.lower()
203 username = username.lower()
204 # add ldap account always lowercase
204 # add ldap account always lowercase
205 new_user.username = username
205 new_user.username = username
206 new_user.password = get_crypt_password(password)
206 new_user.password = get_crypt_password(password)
207 new_user.api_key = generate_api_key(username)
207 new_user.api_key = generate_api_key(username)
208 new_user.email = attrs['email'] or generate_email(username)
208 new_user.email = attrs['email'] or generate_email(username)
209 new_user.active = attrs.get('active', True)
209 new_user.active = attrs.get('active', True)
210 new_user.ldap_dn = safe_unicode(user_dn)
210 new_user.ldap_dn = safe_unicode(user_dn)
211 new_user.name = attrs['name']
211 new_user.name = attrs['name']
212 new_user.lastname = attrs['lastname']
212 new_user.lastname = attrs['lastname']
213
213
214 self.sa.add(new_user)
214 self.sa.add(new_user)
215 return new_user
215 return new_user
216 except (DatabaseError,):
216 except (DatabaseError,):
217 log.error(traceback.format_exc())
217 log.error(traceback.format_exc())
218 self.sa.rollback()
218 self.sa.rollback()
219 raise
219 raise
220 log.debug('this %s user exists skipping creation of ldap account',
220 log.debug('this %s user exists skipping creation of ldap account',
221 username)
221 username)
222 return None
222 return None
223
223
224 def create_registration(self, form_data):
224 def create_registration(self, form_data):
225 from rhodecode.model.notification import NotificationModel
225 from rhodecode.model.notification import NotificationModel
226
226
227 try:
227 try:
228 form_data['admin'] = False
228 form_data['admin'] = False
229 new_user = self.create(form_data)
229 new_user = self.create(form_data)
230
230
231 self.sa.add(new_user)
231 self.sa.add(new_user)
232 self.sa.flush()
232 self.sa.flush()
233
233
234 # notification to admins
234 # notification to admins
235 subject = _('new user registration')
235 subject = _('new user registration')
236 body = ('New user registration\n'
236 body = ('New user registration\n'
237 '---------------------\n'
237 '---------------------\n'
238 '- Username: %s\n'
238 '- Username: %s\n'
239 '- Full Name: %s\n'
239 '- Full Name: %s\n'
240 '- Email: %s\n')
240 '- Email: %s\n')
241 body = body % (new_user.username, new_user.full_name,
241 body = body % (new_user.username, new_user.full_name,
242 new_user.email)
242 new_user.email)
243 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
243 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
244 kw = {'registered_user_url': edit_url}
244 kw = {'registered_user_url': edit_url}
245 NotificationModel().create(created_by=new_user, subject=subject,
245 NotificationModel().create(created_by=new_user, subject=subject,
246 body=body, recipients=None,
246 body=body, recipients=None,
247 type_=Notification.TYPE_REGISTRATION,
247 type_=Notification.TYPE_REGISTRATION,
248 email_kwargs=kw)
248 email_kwargs=kw)
249
249
250 except:
250 except:
251 log.error(traceback.format_exc())
251 log.error(traceback.format_exc())
252 raise
252 raise
253
253
254 def update(self, user_id, form_data):
254 def update(self, user_id, form_data):
255 try:
255 try:
256 user = self.get(user_id, cache=False)
256 user = self.get(user_id, cache=False)
257 if user.username == 'default':
257 if user.username == 'default':
258 raise DefaultUserException(
258 raise DefaultUserException(
259 _("You can't Edit this user since it's"
259 _("You can't Edit this user since it's"
260 " crucial for entire application"))
260 " crucial for entire application"))
261
261
262 for k, v in form_data.items():
262 for k, v in form_data.items():
263 if k == 'new_password' and v != '':
263 if k == 'new_password' and v != '':
264 user.password = v
264 user.password = v
265 user.api_key = generate_api_key(user.username)
265 user.api_key = generate_api_key(user.username)
266 else:
266 else:
267 setattr(user, k, v)
267 setattr(user, k, v)
268
268
269 self.sa.add(user)
269 self.sa.add(user)
270 except:
270 except:
271 log.error(traceback.format_exc())
271 log.error(traceback.format_exc())
272 raise
272 raise
273
273
274 def update_my_account(self, user_id, form_data):
274 def update_my_account(self, user_id, form_data):
275 try:
275 try:
276 user = self.get(user_id, cache=False)
276 user = self.get(user_id, cache=False)
277 if user.username == 'default':
277 if user.username == 'default':
278 raise DefaultUserException(
278 raise DefaultUserException(
279 _("You can't Edit this user since it's"
279 _("You can't Edit this user since it's"
280 " crucial for entire application"))
280 " crucial for entire application"))
281 for k, v in form_data.items():
281 for k, v in form_data.items():
282 if k == 'new_password' and v != '':
282 if k == 'new_password' and v != '':
283 user.password = v
283 user.password = v
284 user.api_key = generate_api_key(user.username)
284 user.api_key = generate_api_key(user.username)
285 else:
285 else:
286 if k not in ['admin', 'active']:
286 if k not in ['admin', 'active']:
287 setattr(user, k, v)
287 setattr(user, k, v)
288
288
289 self.sa.add(user)
289 self.sa.add(user)
290 except:
290 except:
291 log.error(traceback.format_exc())
291 log.error(traceback.format_exc())
292 raise
292 raise
293
293
294 def delete(self, user):
294 def delete(self, user):
295 user = self.__get_user(user)
295 user = self.__get_user(user)
296
296
297 try:
297 try:
298 if user.username == 'default':
298 if user.username == 'default':
299 raise DefaultUserException(
299 raise DefaultUserException(
300 _(u"You can't remove this user since it's"
300 _(u"You can't remove this user since it's"
301 " crucial for entire application")
301 " crucial for entire application")
302 )
302 )
303 if user.repositories:
303 if user.repositories:
304 repos = [x.repo_name for x in user.repositories]
304 repos = [x.repo_name for x in user.repositories]
305 raise UserOwnsReposException(
305 raise UserOwnsReposException(
306 _(u'user "%s" still owns %s repositories and cannot be '
306 _(u'user "%s" still owns %s repositories and cannot be '
307 'removed. Switch owners or remove those repositories. %s')
307 'removed. Switch owners or remove those repositories. %s')
308 % (user.username, len(repos), ', '.join(repos))
308 % (user.username, len(repos), ', '.join(repos))
309 )
309 )
310 self.sa.delete(user)
310 self.sa.delete(user)
311 except:
311 except:
312 log.error(traceback.format_exc())
312 log.error(traceback.format_exc())
313 raise
313 raise
314
314
315 def reset_password_link(self, data):
315 def reset_password_link(self, data):
316 from rhodecode.lib.celerylib import tasks, run_task
316 from rhodecode.lib.celerylib import tasks, run_task
317 run_task(tasks.send_password_link, data['email'])
317 run_task(tasks.send_password_link, data['email'])
318
318
319 def reset_password(self, data):
319 def reset_password(self, data):
320 from rhodecode.lib.celerylib import tasks, run_task
320 from rhodecode.lib.celerylib import tasks, run_task
321 run_task(tasks.reset_user_password, data['email'])
321 run_task(tasks.reset_user_password, data['email'])
322
322
323 def fill_data(self, auth_user, user_id=None, api_key=None):
323 def fill_data(self, auth_user, user_id=None, api_key=None):
324 """
324 """
325 Fetches auth_user by user_id,or api_key if present.
325 Fetches auth_user by user_id,or api_key if present.
326 Fills auth_user attributes with those taken from database.
326 Fills auth_user attributes with those taken from database.
327 Additionally set's is_authenitated if lookup fails
327 Additionally set's is_authenitated if lookup fails
328 present in database
328 present in database
329
329
330 :param auth_user: instance of user to set attributes
330 :param auth_user: instance of user to set attributes
331 :param user_id: user id to fetch by
331 :param user_id: user id to fetch by
332 :param api_key: api key to fetch by
332 :param api_key: api key to fetch by
333 """
333 """
334 if user_id is None and api_key is None:
334 if user_id is None and api_key is None:
335 raise Exception('You need to pass user_id or api_key')
335 raise Exception('You need to pass user_id or api_key')
336
336
337 try:
337 try:
338 if api_key:
338 if api_key:
339 dbuser = self.get_by_api_key(api_key)
339 dbuser = self.get_by_api_key(api_key)
340 else:
340 else:
341 dbuser = self.get(user_id)
341 dbuser = self.get(user_id)
342
342
343 if dbuser is not None and dbuser.active:
343 if dbuser is not None and dbuser.active:
344 log.debug('filling %s data' % dbuser)
344 log.debug('filling %s data' % dbuser)
345 for k, v in dbuser.get_dict().items():
345 for k, v in dbuser.get_dict().items():
346 setattr(auth_user, k, v)
346 setattr(auth_user, k, v)
347 else:
347 else:
348 return False
348 return False
349
349
350 except:
350 except:
351 log.error(traceback.format_exc())
351 log.error(traceback.format_exc())
352 auth_user.is_authenticated = False
352 auth_user.is_authenticated = False
353 return False
353 return False
354
354
355 return True
355 return True
356
356
357 def fill_perms(self, user):
357 def fill_perms(self, user):
358 """
358 """
359 Fills user permission attribute with permissions taken from database
359 Fills user permission attribute with permissions taken from database
360 works for permissions given for repositories, and for permissions that
360 works for permissions given for repositories, and for permissions that
361 are granted to groups
361 are granted to groups
362
362
363 :param user: user instance to fill his perms
363 :param user: user instance to fill his perms
364 """
364 """
365 RK = 'repositories'
365 RK = 'repositories'
366 GK = 'repositories_groups'
366 GK = 'repositories_groups'
367 GLOBAL = 'global'
367 GLOBAL = 'global'
368 user.permissions[RK] = {}
368 user.permissions[RK] = {}
369 user.permissions[GK] = {}
369 user.permissions[GK] = {}
370 user.permissions[GLOBAL] = set()
370 user.permissions[GLOBAL] = set()
371
371
372 #======================================================================
372 #======================================================================
373 # fetch default permissions
373 # fetch default permissions
374 #======================================================================
374 #======================================================================
375 default_user = User.get_by_username('default', cache=True)
375 default_user = User.get_by_username('default', cache=True)
376 default_user_id = default_user.user_id
376 default_user_id = default_user.user_id
377
377
378 default_repo_perms = Permission.get_default_perms(default_user_id)
378 default_repo_perms = Permission.get_default_perms(default_user_id)
379 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
379 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
380
380
381 if user.is_admin:
381 if user.is_admin:
382 #==================================================================
382 #==================================================================
383 # admin user have all default rights for repositories
383 # admin user have all default rights for repositories
384 # and groups set to admin
384 # and groups set to admin
385 #==================================================================
385 #==================================================================
386 user.permissions[GLOBAL].add('hg.admin')
386 user.permissions[GLOBAL].add('hg.admin')
387
387
388 # repositories
388 # repositories
389 for perm in default_repo_perms:
389 for perm in default_repo_perms:
390 r_k = perm.UserRepoToPerm.repository.repo_name
390 r_k = perm.UserRepoToPerm.repository.repo_name
391 p = 'repository.admin'
391 p = 'repository.admin'
392 user.permissions[RK][r_k] = p
392 user.permissions[RK][r_k] = p
393
393
394 # repositories groups
394 # repositories groups
395 for perm in default_repo_groups_perms:
395 for perm in default_repo_groups_perms:
396 rg_k = perm.UserRepoGroupToPerm.group.group_name
396 rg_k = perm.UserRepoGroupToPerm.group.group_name
397 p = 'group.admin'
397 p = 'group.admin'
398 user.permissions[GK][rg_k] = p
398 user.permissions[GK][rg_k] = p
399 return user
399 return user
400
400
401 #==================================================================
401 #==================================================================
402 # set default permissions first for repositories and groups
402 # set default permissions first for repositories and groups
403 #==================================================================
403 #==================================================================
404 uid = user.user_id
404 uid = user.user_id
405
405
406 # default global permissions
406 # default global permissions
407 default_global_perms = self.sa.query(UserToPerm)\
407 default_global_perms = self.sa.query(UserToPerm)\
408 .filter(UserToPerm.user_id == default_user_id)
408 .filter(UserToPerm.user_id == default_user_id)
409
409
410 for perm in default_global_perms:
410 for perm in default_global_perms:
411 user.permissions[GLOBAL].add(perm.permission.permission_name)
411 user.permissions[GLOBAL].add(perm.permission.permission_name)
412
412
413 # defaults for repositories, taken from default user
413 # defaults for repositories, taken from default user
414 for perm in default_repo_perms:
414 for perm in default_repo_perms:
415 r_k = perm.UserRepoToPerm.repository.repo_name
415 r_k = perm.UserRepoToPerm.repository.repo_name
416 if perm.Repository.private and not (perm.Repository.user_id == uid):
416 if perm.Repository.private and not (perm.Repository.user_id == uid):
417 # disable defaults for private repos,
417 # disable defaults for private repos,
418 p = 'repository.none'
418 p = 'repository.none'
419 elif perm.Repository.user_id == uid:
419 elif perm.Repository.user_id == uid:
420 # set admin if owner
420 # set admin if owner
421 p = 'repository.admin'
421 p = 'repository.admin'
422 else:
422 else:
423 p = perm.Permission.permission_name
423 p = perm.Permission.permission_name
424
424
425 user.permissions[RK][r_k] = p
425 user.permissions[RK][r_k] = p
426
426
427 # defaults for repositories groups taken from default user permission
427 # defaults for repositories groups taken from default user permission
428 # on given group
428 # on given group
429 for perm in default_repo_groups_perms:
429 for perm in default_repo_groups_perms:
430 rg_k = perm.UserRepoGroupToPerm.group.group_name
430 rg_k = perm.UserRepoGroupToPerm.group.group_name
431 p = perm.Permission.permission_name
431 p = perm.Permission.permission_name
432 user.permissions[GK][rg_k] = p
432 user.permissions[GK][rg_k] = p
433
433
434 #==================================================================
434 #==================================================================
435 # overwrite defaults with user permissions if any found
435 # overwrite defaults with user permissions if any found
436 #==================================================================
436 #==================================================================
437
437
438 # user global permissions
438 # user global permissions
439 user_perms = self.sa.query(UserToPerm)\
439 user_perms = self.sa.query(UserToPerm)\
440 .options(joinedload(UserToPerm.permission))\
440 .options(joinedload(UserToPerm.permission))\
441 .filter(UserToPerm.user_id == uid).all()
441 .filter(UserToPerm.user_id == uid).all()
442
442
443 for perm in user_perms:
443 for perm in user_perms:
444 user.permissions[GLOBAL].add(perm.permission.permission_name)
444 user.permissions[GLOBAL].add(perm.permission.permission_name)
445
445
446 # user explicit permissions for repositories
446 # user explicit permissions for repositories
447 user_repo_perms = \
447 user_repo_perms = \
448 self.sa.query(UserRepoToPerm, Permission, Repository)\
448 self.sa.query(UserRepoToPerm, Permission, Repository)\
449 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
449 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
450 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
450 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
451 .filter(UserRepoToPerm.user_id == uid)\
451 .filter(UserRepoToPerm.user_id == uid)\
452 .all()
452 .all()
453
453
454 for perm in user_repo_perms:
454 for perm in user_repo_perms:
455 # set admin if owner
455 # set admin if owner
456 r_k = perm.UserRepoToPerm.repository.repo_name
456 r_k = perm.UserRepoToPerm.repository.repo_name
457 if perm.Repository.user_id == uid:
457 if perm.Repository.user_id == uid:
458 p = 'repository.admin'
458 p = 'repository.admin'
459 else:
459 else:
460 p = perm.Permission.permission_name
460 p = perm.Permission.permission_name
461 user.permissions[RK][r_k] = p
461 user.permissions[RK][r_k] = p
462
462
463 # USER GROUP
463 # USER GROUP
464 #==================================================================
464 #==================================================================
465 # check if user is part of user groups for this repository and
465 # check if user is part of user groups for this repository and
466 # fill in (or replace with higher) permissions
466 # fill in (or replace with higher) permissions
467 #==================================================================
467 #==================================================================
468
468
469 # users group global
469 # users group global
470 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
470 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
471 .options(joinedload(UsersGroupToPerm.permission))\
471 .options(joinedload(UsersGroupToPerm.permission))\
472 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
472 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
473 UsersGroupMember.users_group_id))\
473 UsersGroupMember.users_group_id))\
474 .filter(UsersGroupMember.user_id == uid).all()
474 .filter(UsersGroupMember.user_id == uid).all()
475
475
476 for perm in user_perms_from_users_groups:
476 for perm in user_perms_from_users_groups:
477 user.permissions[GLOBAL].add(perm.permission.permission_name)
477 user.permissions[GLOBAL].add(perm.permission.permission_name)
478
478
479 # users group for repositories permissions
479 # users group for repositories permissions
480 user_repo_perms_from_users_groups = \
480 user_repo_perms_from_users_groups = \
481 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
481 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
482 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
482 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
483 .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
483 .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
484 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
484 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
485 .filter(UsersGroupMember.user_id == uid)\
485 .filter(UsersGroupMember.user_id == uid)\
486 .all()
486 .all()
487
487
488 for perm in user_repo_perms_from_users_groups:
488 for perm in user_repo_perms_from_users_groups:
489 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
489 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
490 p = perm.Permission.permission_name
490 p = perm.Permission.permission_name
491 cur_perm = user.permissions[RK][r_k]
491 cur_perm = user.permissions[RK][r_k]
492 # overwrite permission only if it's greater than permission
492 # overwrite permission only if it's greater than permission
493 # given from other sources
493 # given from other sources
494 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
494 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
495 user.permissions[RK][r_k] = p
495 user.permissions[RK][r_k] = p
496
496
497 # REPO GROUP
497 # REPO GROUP
498 #==================================================================
498 #==================================================================
499 # get access for this user for repos group and override defaults
499 # get access for this user for repos group and override defaults
500 #==================================================================
500 #==================================================================
501
501
502 # user explicit permissions for repository
502 # user explicit permissions for repository
503 user_repo_groups_perms = \
503 user_repo_groups_perms = \
504 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
504 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
505 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
505 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
506 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
506 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
507 .filter(UserRepoGroupToPerm.user_id == uid)\
507 .filter(UserRepoGroupToPerm.user_id == uid)\
508 .all()
508 .all()
509
509
510 for perm in user_repo_groups_perms:
510 for perm in user_repo_groups_perms:
511 rg_k = perm.UserRepoGroupToPerm.group.group_name
511 rg_k = perm.UserRepoGroupToPerm.group.group_name
512 p = perm.Permission.permission_name
512 p = perm.Permission.permission_name
513 cur_perm = user.permissions[GK][rg_k]
513 cur_perm = user.permissions[GK][rg_k]
514 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
514 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
515 user.permissions[GK][rg_k] = p
515 user.permissions[GK][rg_k] = p
516
516
517 # REPO GROUP + USER GROUP
517 # REPO GROUP + USER GROUP
518 #==================================================================
518 #==================================================================
519 # check if user is part of user groups for this repo group and
519 # check if user is part of user groups for this repo group and
520 # fill in (or replace with higher) permissions
520 # fill in (or replace with higher) permissions
521 #==================================================================
521 #==================================================================
522
522
523 # users group for repositories permissions
523 # users group for repositories permissions
524 user_repo_group_perms_from_users_groups = \
524 user_repo_group_perms_from_users_groups = \
525 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
525 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
526 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
526 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
527 .join((Permission, UsersGroupRepoGroupToPerm.permission_id == Permission.permission_id))\
527 .join((Permission, UsersGroupRepoGroupToPerm.permission_id == Permission.permission_id))\
528 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
528 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
529 .filter(UsersGroupMember.user_id == uid)\
529 .filter(UsersGroupMember.user_id == uid)\
530 .all()
530 .all()
531
531
532 for perm in user_repo_group_perms_from_users_groups:
532 for perm in user_repo_group_perms_from_users_groups:
533 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
533 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
534 print perm, g_k
535 p = perm.Permission.permission_name
534 p = perm.Permission.permission_name
536 cur_perm = user.permissions[GK][g_k]
535 cur_perm = user.permissions[GK][g_k]
537 # overwrite permission only if it's greater than permission
536 # overwrite permission only if it's greater than permission
538 # given from other sources
537 # given from other sources
539 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
538 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
540 user.permissions[GK][g_k] = p
539 user.permissions[GK][g_k] = p
541
540
542 return user
541 return user
543
542
544 def has_perm(self, user, perm):
543 def has_perm(self, user, perm):
545 if not isinstance(perm, Permission):
544 if not isinstance(perm, Permission):
546 raise Exception('perm needs to be an instance of Permission class '
545 raise Exception('perm needs to be an instance of Permission class '
547 'got %s instead' % type(perm))
546 'got %s instead' % type(perm))
548
547
549 user = self.__get_user(user)
548 user = self.__get_user(user)
550
549
551 return UserToPerm.query().filter(UserToPerm.user == user)\
550 return UserToPerm.query().filter(UserToPerm.user == user)\
552 .filter(UserToPerm.permission == perm).scalar() is not None
551 .filter(UserToPerm.permission == perm).scalar() is not None
553
552
554 def grant_perm(self, user, perm):
553 def grant_perm(self, user, perm):
555 """
554 """
556 Grant user global permissions
555 Grant user global permissions
557
556
558 :param user:
557 :param user:
559 :param perm:
558 :param perm:
560 """
559 """
561 user = self.__get_user(user)
560 user = self.__get_user(user)
562 perm = self.__get_perm(perm)
561 perm = self.__get_perm(perm)
563 # if this permission is already granted skip it
562 # if this permission is already granted skip it
564 _perm = UserToPerm.query()\
563 _perm = UserToPerm.query()\
565 .filter(UserToPerm.user == user)\
564 .filter(UserToPerm.user == user)\
566 .filter(UserToPerm.permission == perm)\
565 .filter(UserToPerm.permission == perm)\
567 .scalar()
566 .scalar()
568 if _perm:
567 if _perm:
569 return
568 return
570 new = UserToPerm()
569 new = UserToPerm()
571 new.user = user
570 new.user = user
572 new.permission = perm
571 new.permission = perm
573 self.sa.add(new)
572 self.sa.add(new)
574
573
575 def revoke_perm(self, user, perm):
574 def revoke_perm(self, user, perm):
576 """
575 """
577 Revoke users global permissions
576 Revoke users global permissions
578
577
579 :param user:
578 :param user:
580 :param perm:
579 :param perm:
581 """
580 """
582 user = self.__get_user(user)
581 user = self.__get_user(user)
583 perm = self.__get_perm(perm)
582 perm = self.__get_perm(perm)
584
583
585 obj = UserToPerm.query()\
584 obj = UserToPerm.query()\
586 .filter(UserToPerm.user == user)\
585 .filter(UserToPerm.user == user)\
587 .filter(UserToPerm.permission == perm)\
586 .filter(UserToPerm.permission == perm)\
588 .scalar()
587 .scalar()
589 if obj:
588 if obj:
590 self.sa.delete(obj)
589 self.sa.delete(obj)
General Comments 0
You need to be logged in to leave comments. Login now