##// END OF EJS Templates
added optional password type in password generator
marcink -
r1993:4d3179d2 beta
parent child Browse files
Show More
@@ -1,779 +1,781 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 import str2bool, safe_unicode
46 from rhodecode.lib 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 print 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_):
83 def gen_password(self, length, type_=None):
84 if type_ is None:
85 type_ = self.ALPHABETS_FULL
84 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
86 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
85 return self.passwd
87 return self.passwd
86
88
87
89
88 class RhodeCodeCrypto(object):
90 class RhodeCodeCrypto(object):
89
91
90 @classmethod
92 @classmethod
91 def hash_string(cls, str_):
93 def hash_string(cls, str_):
92 """
94 """
93 Cryptographic function used for password hashing based on pybcrypt
95 Cryptographic function used for password hashing based on pybcrypt
94 or pycrypto in windows
96 or pycrypto in windows
95
97
96 :param password: password to hash
98 :param password: password to hash
97 """
99 """
98 if __platform__ in PLATFORM_WIN:
100 if __platform__ in PLATFORM_WIN:
99 return sha256(str_).hexdigest()
101 return sha256(str_).hexdigest()
100 elif __platform__ in PLATFORM_OTHERS:
102 elif __platform__ in PLATFORM_OTHERS:
101 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
103 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
102 else:
104 else:
103 raise Exception('Unknown or unsupported platform %s' \
105 raise Exception('Unknown or unsupported platform %s' \
104 % __platform__)
106 % __platform__)
105
107
106 @classmethod
108 @classmethod
107 def hash_check(cls, password, hashed):
109 def hash_check(cls, password, hashed):
108 """
110 """
109 Checks matching password with it's hashed value, runs different
111 Checks matching password with it's hashed value, runs different
110 implementation based on platform it runs on
112 implementation based on platform it runs on
111
113
112 :param password: password
114 :param password: password
113 :param hashed: password in hashed form
115 :param hashed: password in hashed form
114 """
116 """
115
117
116 if __platform__ in PLATFORM_WIN:
118 if __platform__ in PLATFORM_WIN:
117 return sha256(password).hexdigest() == hashed
119 return sha256(password).hexdigest() == hashed
118 elif __platform__ in PLATFORM_OTHERS:
120 elif __platform__ in PLATFORM_OTHERS:
119 return bcrypt.hashpw(password, hashed) == hashed
121 return bcrypt.hashpw(password, hashed) == hashed
120 else:
122 else:
121 raise Exception('Unknown or unsupported platform %s' \
123 raise Exception('Unknown or unsupported platform %s' \
122 % __platform__)
124 % __platform__)
123
125
124
126
125 def get_crypt_password(password):
127 def get_crypt_password(password):
126 return RhodeCodeCrypto.hash_string(password)
128 return RhodeCodeCrypto.hash_string(password)
127
129
128
130
129 def check_password(password, hashed):
131 def check_password(password, hashed):
130 return RhodeCodeCrypto.hash_check(password, hashed)
132 return RhodeCodeCrypto.hash_check(password, hashed)
131
133
132
134
133 def generate_api_key(str_, salt=None):
135 def generate_api_key(str_, salt=None):
134 """
136 """
135 Generates API KEY from given string
137 Generates API KEY from given string
136
138
137 :param str_:
139 :param str_:
138 :param salt:
140 :param salt:
139 """
141 """
140
142
141 if salt is None:
143 if salt is None:
142 salt = _RandomNameSequence().next()
144 salt = _RandomNameSequence().next()
143
145
144 return hashlib.sha1(str_ + salt).hexdigest()
146 return hashlib.sha1(str_ + salt).hexdigest()
145
147
146
148
147 def authfunc(environ, username, password):
149 def authfunc(environ, username, password):
148 """
150 """
149 Dummy authentication wrapper function used in Mercurial and Git for
151 Dummy authentication wrapper function used in Mercurial and Git for
150 access control.
152 access control.
151
153
152 :param environ: needed only for using in Basic auth
154 :param environ: needed only for using in Basic auth
153 """
155 """
154 return authenticate(username, password)
156 return authenticate(username, password)
155
157
156
158
157 def authenticate(username, password):
159 def authenticate(username, password):
158 """
160 """
159 Authentication function used for access control,
161 Authentication function used for access control,
160 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
161 authentication, also creates ldap user if not in database
163 authentication, also creates ldap user if not in database
162
164
163 :param username: username
165 :param username: username
164 :param password: password
166 :param password: password
165 """
167 """
166
168
167 user_model = UserModel()
169 user_model = UserModel()
168 user = User.get_by_username(username)
170 user = User.get_by_username(username)
169
171
170 log.debug('Authenticating user using RhodeCode account')
172 log.debug('Authenticating user using RhodeCode account')
171 if user is not None and not user.ldap_dn:
173 if user is not None and not user.ldap_dn:
172 if user.active:
174 if user.active:
173 if user.username == 'default' and user.active:
175 if user.username == 'default' and user.active:
174 log.info('user %s authenticated correctly as anonymous user',
176 log.info('user %s authenticated correctly as anonymous user',
175 username)
177 username)
176 return True
178 return True
177
179
178 elif user.username == username and check_password(password,
180 elif user.username == username and check_password(password,
179 user.password):
181 user.password):
180 log.info('user %s authenticated correctly' % username)
182 log.info('user %s authenticated correctly' % username)
181 return True
183 return True
182 else:
184 else:
183 log.warning('user %s is disabled' % username)
185 log.warning('user %s is disabled' % username)
184
186
185 else:
187 else:
186 log.debug('Regular authentication failed')
188 log.debug('Regular authentication failed')
187 user_obj = User.get_by_username(username, case_insensitive=True)
189 user_obj = User.get_by_username(username, case_insensitive=True)
188
190
189 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:
190 log.debug('this user already exists as non ldap')
192 log.debug('this user already exists as non ldap')
191 return False
193 return False
192
194
193 ldap_settings = RhodeCodeSetting.get_ldap_settings()
195 ldap_settings = RhodeCodeSetting.get_ldap_settings()
194 #======================================================================
196 #======================================================================
195 # FALLBACK TO LDAP AUTH IF ENABLE
197 # FALLBACK TO LDAP AUTH IF ENABLE
196 #======================================================================
198 #======================================================================
197 if str2bool(ldap_settings.get('ldap_active')):
199 if str2bool(ldap_settings.get('ldap_active')):
198 log.debug("Authenticating user using ldap")
200 log.debug("Authenticating user using ldap")
199 kwargs = {
201 kwargs = {
200 'server': ldap_settings.get('ldap_host', ''),
202 'server': ldap_settings.get('ldap_host', ''),
201 'base_dn': ldap_settings.get('ldap_base_dn', ''),
203 'base_dn': ldap_settings.get('ldap_base_dn', ''),
202 'port': ldap_settings.get('ldap_port'),
204 'port': ldap_settings.get('ldap_port'),
203 'bind_dn': ldap_settings.get('ldap_dn_user'),
205 'bind_dn': ldap_settings.get('ldap_dn_user'),
204 'bind_pass': ldap_settings.get('ldap_dn_pass'),
206 'bind_pass': ldap_settings.get('ldap_dn_pass'),
205 'tls_kind': ldap_settings.get('ldap_tls_kind'),
207 'tls_kind': ldap_settings.get('ldap_tls_kind'),
206 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
208 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
207 'ldap_filter': ldap_settings.get('ldap_filter'),
209 'ldap_filter': ldap_settings.get('ldap_filter'),
208 'search_scope': ldap_settings.get('ldap_search_scope'),
210 'search_scope': ldap_settings.get('ldap_search_scope'),
209 'attr_login': ldap_settings.get('ldap_attr_login'),
211 'attr_login': ldap_settings.get('ldap_attr_login'),
210 'ldap_version': 3,
212 'ldap_version': 3,
211 }
213 }
212 log.debug('Checking for ldap authentication')
214 log.debug('Checking for ldap authentication')
213 try:
215 try:
214 aldap = AuthLdap(**kwargs)
216 aldap = AuthLdap(**kwargs)
215 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
217 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
216 password)
218 password)
217 log.debug('Got ldap DN response %s' % user_dn)
219 log.debug('Got ldap DN response %s' % user_dn)
218
220
219 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
221 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
220 .get(k), [''])[0]
222 .get(k), [''])[0]
221
223
222 user_attrs = {
224 user_attrs = {
223 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
225 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
224 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
226 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
225 'email': get_ldap_attr('ldap_attr_email'),
227 'email': get_ldap_attr('ldap_attr_email'),
226 }
228 }
227
229
228 # 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
229 # with some random generated password
231 # with some random generated password
230 _password = PasswordGenerator().gen_password(length=8)
232 _password = PasswordGenerator().gen_password(length=8)
231 # 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
232 # database
234 # database
233 if user_model.create_ldap(username, _password, user_dn,
235 if user_model.create_ldap(username, _password, user_dn,
234 user_attrs):
236 user_attrs):
235 log.info('created new ldap user %s' % username)
237 log.info('created new ldap user %s' % username)
236
238
237 Session.commit()
239 Session.commit()
238 return True
240 return True
239 except (LdapUsernameError, LdapPasswordError,):
241 except (LdapUsernameError, LdapPasswordError,):
240 pass
242 pass
241 except (Exception,):
243 except (Exception,):
242 log.error(traceback.format_exc())
244 log.error(traceback.format_exc())
243 pass
245 pass
244 return False
246 return False
245
247
246
248
247 def login_container_auth(username):
249 def login_container_auth(username):
248 user = User.get_by_username(username)
250 user = User.get_by_username(username)
249 if user is None:
251 if user is None:
250 user_attrs = {
252 user_attrs = {
251 'name': username,
253 'name': username,
252 'lastname': None,
254 'lastname': None,
253 'email': None,
255 'email': None,
254 }
256 }
255 user = UserModel().create_for_container_auth(username, user_attrs)
257 user = UserModel().create_for_container_auth(username, user_attrs)
256 if not user:
258 if not user:
257 return None
259 return None
258 log.info('User %s was created by container authentication' % username)
260 log.info('User %s was created by container authentication' % username)
259
261
260 if not user.active:
262 if not user.active:
261 return None
263 return None
262
264
263 user.update_lastlogin()
265 user.update_lastlogin()
264 Session.commit()
266 Session.commit()
265
267
266 log.debug('User %s is now logged in by container authentication',
268 log.debug('User %s is now logged in by container authentication',
267 user.username)
269 user.username)
268 return user
270 return user
269
271
270
272
271 def get_container_username(environ, config):
273 def get_container_username(environ, config):
272 username = None
274 username = None
273
275
274 if str2bool(config.get('container_auth_enabled', False)):
276 if str2bool(config.get('container_auth_enabled', False)):
275 from paste.httpheaders import REMOTE_USER
277 from paste.httpheaders import REMOTE_USER
276 username = REMOTE_USER(environ)
278 username = REMOTE_USER(environ)
277
279
278 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
280 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
279 username = environ.get('HTTP_X_FORWARDED_USER')
281 username = environ.get('HTTP_X_FORWARDED_USER')
280
282
281 if username:
283 if username:
282 # Removing realm and domain from username
284 # Removing realm and domain from username
283 username = username.partition('@')[0]
285 username = username.partition('@')[0]
284 username = username.rpartition('\\')[2]
286 username = username.rpartition('\\')[2]
285 log.debug('Received username %s from container' % username)
287 log.debug('Received username %s from container' % username)
286
288
287 return username
289 return username
288
290
289
291
290 class AuthUser(object):
292 class AuthUser(object):
291 """
293 """
292 A simple object that handles all attributes of user in RhodeCode
294 A simple object that handles all attributes of user in RhodeCode
293
295
294 It does lookup based on API key,given user, or user present in session
296 It does lookup based on API key,given user, or user present in session
295 Then it fills all required information for such user. It also checks if
297 Then it fills all required information for such user. It also checks if
296 anonymous access is enabled and if so, it returns default user as logged
298 anonymous access is enabled and if so, it returns default user as logged
297 in
299 in
298 """
300 """
299
301
300 def __init__(self, user_id=None, api_key=None, username=None):
302 def __init__(self, user_id=None, api_key=None, username=None):
301
303
302 self.user_id = user_id
304 self.user_id = user_id
303 self.api_key = None
305 self.api_key = None
304 self.username = username
306 self.username = username
305
307
306 self.name = ''
308 self.name = ''
307 self.lastname = ''
309 self.lastname = ''
308 self.email = ''
310 self.email = ''
309 self.is_authenticated = False
311 self.is_authenticated = False
310 self.admin = False
312 self.admin = False
311 self.permissions = {}
313 self.permissions = {}
312 self._api_key = api_key
314 self._api_key = api_key
313 self.propagate_data()
315 self.propagate_data()
314 self._instance = None
316 self._instance = None
315
317
316 def propagate_data(self):
318 def propagate_data(self):
317 user_model = UserModel()
319 user_model = UserModel()
318 self.anonymous_user = User.get_by_username('default', cache=True)
320 self.anonymous_user = User.get_by_username('default', cache=True)
319 is_user_loaded = False
321 is_user_loaded = False
320
322
321 # try go get user by api key
323 # try go get user by api key
322 if self._api_key and self._api_key != self.anonymous_user.api_key:
324 if self._api_key and self._api_key != self.anonymous_user.api_key:
323 log.debug('Auth User lookup by API KEY %s' % self._api_key)
325 log.debug('Auth User lookup by API KEY %s' % self._api_key)
324 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
326 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
325 # lookup by userid
327 # lookup by userid
326 elif (self.user_id is not None and
328 elif (self.user_id is not None and
327 self.user_id != self.anonymous_user.user_id):
329 self.user_id != self.anonymous_user.user_id):
328 log.debug('Auth User lookup by USER ID %s' % self.user_id)
330 log.debug('Auth User lookup by USER ID %s' % self.user_id)
329 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
331 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
330 # lookup by username
332 # lookup by username
331 elif self.username and \
333 elif self.username and \
332 str2bool(config.get('container_auth_enabled', False)):
334 str2bool(config.get('container_auth_enabled', False)):
333
335
334 log.debug('Auth User lookup by USER NAME %s' % self.username)
336 log.debug('Auth User lookup by USER NAME %s' % self.username)
335 dbuser = login_container_auth(self.username)
337 dbuser = login_container_auth(self.username)
336 if dbuser is not None:
338 if dbuser is not None:
337 for k, v in dbuser.get_dict().items():
339 for k, v in dbuser.get_dict().items():
338 setattr(self, k, v)
340 setattr(self, k, v)
339 self.set_authenticated()
341 self.set_authenticated()
340 is_user_loaded = True
342 is_user_loaded = True
341
343
342 if not is_user_loaded:
344 if not is_user_loaded:
343 # if we cannot authenticate user try anonymous
345 # if we cannot authenticate user try anonymous
344 if self.anonymous_user.active is True:
346 if self.anonymous_user.active is True:
345 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
347 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
346 # then we set this user is logged in
348 # then we set this user is logged in
347 self.is_authenticated = True
349 self.is_authenticated = True
348 else:
350 else:
349 self.user_id = None
351 self.user_id = None
350 self.username = None
352 self.username = None
351 self.is_authenticated = False
353 self.is_authenticated = False
352
354
353 if not self.username:
355 if not self.username:
354 self.username = 'None'
356 self.username = 'None'
355
357
356 log.debug('Auth User is now %s' % self)
358 log.debug('Auth User is now %s' % self)
357 user_model.fill_perms(self)
359 user_model.fill_perms(self)
358
360
359 @property
361 @property
360 def is_admin(self):
362 def is_admin(self):
361 return self.admin
363 return self.admin
362
364
363 def __repr__(self):
365 def __repr__(self):
364 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
366 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
365 self.is_authenticated)
367 self.is_authenticated)
366
368
367 def set_authenticated(self, authenticated=True):
369 def set_authenticated(self, authenticated=True):
368 if self.user_id != self.anonymous_user.user_id:
370 if self.user_id != self.anonymous_user.user_id:
369 self.is_authenticated = authenticated
371 self.is_authenticated = authenticated
370
372
371 def get_cookie_store(self):
373 def get_cookie_store(self):
372 return {'username': self.username,
374 return {'username': self.username,
373 'user_id': self.user_id,
375 'user_id': self.user_id,
374 'is_authenticated': self.is_authenticated}
376 'is_authenticated': self.is_authenticated}
375
377
376 @classmethod
378 @classmethod
377 def from_cookie_store(cls, cookie_store):
379 def from_cookie_store(cls, cookie_store):
378 user_id = cookie_store.get('user_id')
380 user_id = cookie_store.get('user_id')
379 username = cookie_store.get('username')
381 username = cookie_store.get('username')
380 api_key = cookie_store.get('api_key')
382 api_key = cookie_store.get('api_key')
381 return AuthUser(user_id, api_key, username)
383 return AuthUser(user_id, api_key, username)
382
384
383
385
384 def set_available_permissions(config):
386 def set_available_permissions(config):
385 """
387 """
386 This function will propagate pylons globals with all available defined
388 This function will propagate pylons globals with all available defined
387 permission given in db. We don't want to check each time from db for new
389 permission given in db. We don't want to check each time from db for new
388 permissions since adding a new permission also requires application restart
390 permissions since adding a new permission also requires application restart
389 ie. to decorate new views with the newly created permission
391 ie. to decorate new views with the newly created permission
390
392
391 :param config: current pylons config instance
393 :param config: current pylons config instance
392
394
393 """
395 """
394 log.info('getting information about all available permissions')
396 log.info('getting information about all available permissions')
395 try:
397 try:
396 sa = meta.Session
398 sa = meta.Session
397 all_perms = sa.query(Permission).all()
399 all_perms = sa.query(Permission).all()
398 except Exception:
400 except Exception:
399 pass
401 pass
400 finally:
402 finally:
401 meta.Session.remove()
403 meta.Session.remove()
402
404
403 config['available_permissions'] = [x.permission_name for x in all_perms]
405 config['available_permissions'] = [x.permission_name for x in all_perms]
404
406
405
407
406 #==============================================================================
408 #==============================================================================
407 # CHECK DECORATORS
409 # CHECK DECORATORS
408 #==============================================================================
410 #==============================================================================
409 class LoginRequired(object):
411 class LoginRequired(object):
410 """
412 """
411 Must be logged in to execute this function else
413 Must be logged in to execute this function else
412 redirect to login page
414 redirect to login page
413
415
414 :param api_access: if enabled this checks only for valid auth token
416 :param api_access: if enabled this checks only for valid auth token
415 and grants access based on valid token
417 and grants access based on valid token
416 """
418 """
417
419
418 def __init__(self, api_access=False):
420 def __init__(self, api_access=False):
419 self.api_access = api_access
421 self.api_access = api_access
420
422
421 def __call__(self, func):
423 def __call__(self, func):
422 return decorator(self.__wrapper, func)
424 return decorator(self.__wrapper, func)
423
425
424 def __wrapper(self, func, *fargs, **fkwargs):
426 def __wrapper(self, func, *fargs, **fkwargs):
425 cls = fargs[0]
427 cls = fargs[0]
426 user = cls.rhodecode_user
428 user = cls.rhodecode_user
427
429
428 api_access_ok = False
430 api_access_ok = False
429 if self.api_access:
431 if self.api_access:
430 log.debug('Checking API KEY access for %s' % cls)
432 log.debug('Checking API KEY access for %s' % cls)
431 if user.api_key == request.GET.get('api_key'):
433 if user.api_key == request.GET.get('api_key'):
432 api_access_ok = True
434 api_access_ok = True
433 else:
435 else:
434 log.debug("API KEY token not valid")
436 log.debug("API KEY token not valid")
435
437
436 log.debug('Checking if %s is authenticated @ %s' % (user.username, cls))
438 log.debug('Checking if %s is authenticated @ %s' % (user.username, cls))
437 if user.is_authenticated or api_access_ok:
439 if user.is_authenticated or api_access_ok:
438 log.debug('user %s is authenticated' % user.username)
440 log.debug('user %s is authenticated' % user.username)
439 return func(*fargs, **fkwargs)
441 return func(*fargs, **fkwargs)
440 else:
442 else:
441 log.warn('user %s NOT authenticated' % user.username)
443 log.warn('user %s NOT authenticated' % user.username)
442 p = url.current()
444 p = url.current()
443
445
444 log.debug('redirecting to login page with %s' % p)
446 log.debug('redirecting to login page with %s' % p)
445 return redirect(url('login_home', came_from=p))
447 return redirect(url('login_home', came_from=p))
446
448
447
449
448 class NotAnonymous(object):
450 class NotAnonymous(object):
449 """
451 """
450 Must be logged in to execute this function else
452 Must be logged in to execute this function else
451 redirect to login page"""
453 redirect to login page"""
452
454
453 def __call__(self, func):
455 def __call__(self, func):
454 return decorator(self.__wrapper, func)
456 return decorator(self.__wrapper, func)
455
457
456 def __wrapper(self, func, *fargs, **fkwargs):
458 def __wrapper(self, func, *fargs, **fkwargs):
457 cls = fargs[0]
459 cls = fargs[0]
458 self.user = cls.rhodecode_user
460 self.user = cls.rhodecode_user
459
461
460 log.debug('Checking if user is not anonymous @%s' % cls)
462 log.debug('Checking if user is not anonymous @%s' % cls)
461
463
462 anonymous = self.user.username == 'default'
464 anonymous = self.user.username == 'default'
463
465
464 if anonymous:
466 if anonymous:
465 p = url.current()
467 p = url.current()
466
468
467 import rhodecode.lib.helpers as h
469 import rhodecode.lib.helpers as h
468 h.flash(_('You need to be a registered user to '
470 h.flash(_('You need to be a registered user to '
469 'perform this action'),
471 'perform this action'),
470 category='warning')
472 category='warning')
471 return redirect(url('login_home', came_from=p))
473 return redirect(url('login_home', came_from=p))
472 else:
474 else:
473 return func(*fargs, **fkwargs)
475 return func(*fargs, **fkwargs)
474
476
475
477
476 class PermsDecorator(object):
478 class PermsDecorator(object):
477 """Base class for controller decorators"""
479 """Base class for controller decorators"""
478
480
479 def __init__(self, *required_perms):
481 def __init__(self, *required_perms):
480 available_perms = config['available_permissions']
482 available_perms = config['available_permissions']
481 for perm in required_perms:
483 for perm in required_perms:
482 if perm not in available_perms:
484 if perm not in available_perms:
483 raise Exception("'%s' permission is not defined" % perm)
485 raise Exception("'%s' permission is not defined" % perm)
484 self.required_perms = set(required_perms)
486 self.required_perms = set(required_perms)
485 self.user_perms = None
487 self.user_perms = None
486
488
487 def __call__(self, func):
489 def __call__(self, func):
488 return decorator(self.__wrapper, func)
490 return decorator(self.__wrapper, func)
489
491
490 def __wrapper(self, func, *fargs, **fkwargs):
492 def __wrapper(self, func, *fargs, **fkwargs):
491 cls = fargs[0]
493 cls = fargs[0]
492 self.user = cls.rhodecode_user
494 self.user = cls.rhodecode_user
493 self.user_perms = self.user.permissions
495 self.user_perms = self.user.permissions
494 log.debug('checking %s permissions %s for %s %s',
496 log.debug('checking %s permissions %s for %s %s',
495 self.__class__.__name__, self.required_perms, cls,
497 self.__class__.__name__, self.required_perms, cls,
496 self.user)
498 self.user)
497
499
498 if self.check_permissions():
500 if self.check_permissions():
499 log.debug('Permission granted for %s %s' % (cls, self.user))
501 log.debug('Permission granted for %s %s' % (cls, self.user))
500 return func(*fargs, **fkwargs)
502 return func(*fargs, **fkwargs)
501
503
502 else:
504 else:
503 log.warning('Permission denied for %s %s' % (cls, self.user))
505 log.warning('Permission denied for %s %s' % (cls, self.user))
504 anonymous = self.user.username == 'default'
506 anonymous = self.user.username == 'default'
505
507
506 if anonymous:
508 if anonymous:
507 p = url.current()
509 p = url.current()
508
510
509 import rhodecode.lib.helpers as h
511 import rhodecode.lib.helpers as h
510 h.flash(_('You need to be a signed in to '
512 h.flash(_('You need to be a signed in to '
511 'view this page'),
513 'view this page'),
512 category='warning')
514 category='warning')
513 return redirect(url('login_home', came_from=p))
515 return redirect(url('login_home', came_from=p))
514
516
515 else:
517 else:
516 # redirect with forbidden ret code
518 # redirect with forbidden ret code
517 return abort(403)
519 return abort(403)
518
520
519 def check_permissions(self):
521 def check_permissions(self):
520 """Dummy function for overriding"""
522 """Dummy function for overriding"""
521 raise Exception('You have to write this function in child class')
523 raise Exception('You have to write this function in child class')
522
524
523
525
524 class HasPermissionAllDecorator(PermsDecorator):
526 class HasPermissionAllDecorator(PermsDecorator):
525 """
527 """
526 Checks for access permission for all given predicates. All of them
528 Checks for access permission for all given predicates. All of them
527 have to be meet in order to fulfill the request
529 have to be meet in order to fulfill the request
528 """
530 """
529
531
530 def check_permissions(self):
532 def check_permissions(self):
531 if self.required_perms.issubset(self.user_perms.get('global')):
533 if self.required_perms.issubset(self.user_perms.get('global')):
532 return True
534 return True
533 return False
535 return False
534
536
535
537
536 class HasPermissionAnyDecorator(PermsDecorator):
538 class HasPermissionAnyDecorator(PermsDecorator):
537 """
539 """
538 Checks for access permission for any of given predicates. In order to
540 Checks for access permission for any of given predicates. In order to
539 fulfill the request any of predicates must be meet
541 fulfill the request any of predicates must be meet
540 """
542 """
541
543
542 def check_permissions(self):
544 def check_permissions(self):
543 if self.required_perms.intersection(self.user_perms.get('global')):
545 if self.required_perms.intersection(self.user_perms.get('global')):
544 return True
546 return True
545 return False
547 return False
546
548
547
549
548 class HasRepoPermissionAllDecorator(PermsDecorator):
550 class HasRepoPermissionAllDecorator(PermsDecorator):
549 """
551 """
550 Checks for access permission for all given predicates for specific
552 Checks for access permission for all given predicates for specific
551 repository. All of them have to be meet in order to fulfill the request
553 repository. All of them have to be meet in order to fulfill the request
552 """
554 """
553
555
554 def check_permissions(self):
556 def check_permissions(self):
555 repo_name = get_repo_slug(request)
557 repo_name = get_repo_slug(request)
556 try:
558 try:
557 user_perms = set([self.user_perms['repositories'][repo_name]])
559 user_perms = set([self.user_perms['repositories'][repo_name]])
558 except KeyError:
560 except KeyError:
559 return False
561 return False
560 if self.required_perms.issubset(user_perms):
562 if self.required_perms.issubset(user_perms):
561 return True
563 return True
562 return False
564 return False
563
565
564
566
565 class HasRepoPermissionAnyDecorator(PermsDecorator):
567 class HasRepoPermissionAnyDecorator(PermsDecorator):
566 """
568 """
567 Checks for access permission for any of given predicates for specific
569 Checks for access permission for any of given predicates for specific
568 repository. In order to fulfill the request any of predicates must be meet
570 repository. In order to fulfill the request any of predicates must be meet
569 """
571 """
570
572
571 def check_permissions(self):
573 def check_permissions(self):
572 repo_name = get_repo_slug(request)
574 repo_name = get_repo_slug(request)
573
575
574 try:
576 try:
575 user_perms = set([self.user_perms['repositories'][repo_name]])
577 user_perms = set([self.user_perms['repositories'][repo_name]])
576 except KeyError:
578 except KeyError:
577 return False
579 return False
578 if self.required_perms.intersection(user_perms):
580 if self.required_perms.intersection(user_perms):
579 return True
581 return True
580 return False
582 return False
581
583
582
584
583 class HasReposGroupPermissionAllDecorator(PermsDecorator):
585 class HasReposGroupPermissionAllDecorator(PermsDecorator):
584 """
586 """
585 Checks for access permission for all given predicates for specific
587 Checks for access permission for all given predicates for specific
586 repository. All of them have to be meet in order to fulfill the request
588 repository. All of them have to be meet in order to fulfill the request
587 """
589 """
588
590
589 def check_permissions(self):
591 def check_permissions(self):
590 group_name = get_repos_group_slug(request)
592 group_name = get_repos_group_slug(request)
591 try:
593 try:
592 user_perms = set([self.user_perms['repositories_groups'][group_name]])
594 user_perms = set([self.user_perms['repositories_groups'][group_name]])
593 except KeyError:
595 except KeyError:
594 return False
596 return False
595 if self.required_perms.issubset(user_perms):
597 if self.required_perms.issubset(user_perms):
596 return True
598 return True
597 return False
599 return False
598
600
599
601
600 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
602 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
601 """
603 """
602 Checks for access permission for any of given predicates for specific
604 Checks for access permission for any of given predicates for specific
603 repository. In order to fulfill the request any of predicates must be meet
605 repository. In order to fulfill the request any of predicates must be meet
604 """
606 """
605
607
606 def check_permissions(self):
608 def check_permissions(self):
607 group_name = get_repos_group_slug(request)
609 group_name = get_repos_group_slug(request)
608
610
609 try:
611 try:
610 user_perms = set([self.user_perms['repositories_groups'][group_name]])
612 user_perms = set([self.user_perms['repositories_groups'][group_name]])
611 except KeyError:
613 except KeyError:
612 return False
614 return False
613 if self.required_perms.intersection(user_perms):
615 if self.required_perms.intersection(user_perms):
614 return True
616 return True
615 return False
617 return False
616
618
617
619
618 #==============================================================================
620 #==============================================================================
619 # CHECK FUNCTIONS
621 # CHECK FUNCTIONS
620 #==============================================================================
622 #==============================================================================
621 class PermsFunction(object):
623 class PermsFunction(object):
622 """Base function for other check functions"""
624 """Base function for other check functions"""
623
625
624 def __init__(self, *perms):
626 def __init__(self, *perms):
625 available_perms = config['available_permissions']
627 available_perms = config['available_permissions']
626
628
627 for perm in perms:
629 for perm in perms:
628 if perm not in available_perms:
630 if perm not in available_perms:
629 raise Exception("'%s' permission in not defined" % perm)
631 raise Exception("'%s' permission in not defined" % perm)
630 self.required_perms = set(perms)
632 self.required_perms = set(perms)
631 self.user_perms = None
633 self.user_perms = None
632 self.granted_for = ''
634 self.granted_for = ''
633 self.repo_name = None
635 self.repo_name = None
634
636
635 def __call__(self, check_Location=''):
637 def __call__(self, check_Location=''):
636 user = request.user
638 user = request.user
637 if not user:
639 if not user:
638 return False
640 return False
639 self.user_perms = user.permissions
641 self.user_perms = user.permissions
640 self.granted_for = user
642 self.granted_for = user
641 log.debug('checking %s %s %s', self.__class__.__name__,
643 log.debug('checking %s %s %s', self.__class__.__name__,
642 self.required_perms, user)
644 self.required_perms, user)
643
645
644 if self.check_permissions():
646 if self.check_permissions():
645 log.debug('Permission granted %s @ %s', self.granted_for,
647 log.debug('Permission granted %s @ %s', self.granted_for,
646 check_Location or 'unspecified location')
648 check_Location or 'unspecified location')
647 return True
649 return True
648
650
649 else:
651 else:
650 log.warning('Permission denied for %s @ %s', self.granted_for,
652 log.warning('Permission denied for %s @ %s', self.granted_for,
651 check_Location or 'unspecified location')
653 check_Location or 'unspecified location')
652 return False
654 return False
653
655
654 def check_permissions(self):
656 def check_permissions(self):
655 """Dummy function for overriding"""
657 """Dummy function for overriding"""
656 raise Exception('You have to write this function in child class')
658 raise Exception('You have to write this function in child class')
657
659
658
660
659 class HasPermissionAll(PermsFunction):
661 class HasPermissionAll(PermsFunction):
660 def check_permissions(self):
662 def check_permissions(self):
661 if self.required_perms.issubset(self.user_perms.get('global')):
663 if self.required_perms.issubset(self.user_perms.get('global')):
662 return True
664 return True
663 return False
665 return False
664
666
665
667
666 class HasPermissionAny(PermsFunction):
668 class HasPermissionAny(PermsFunction):
667 def check_permissions(self):
669 def check_permissions(self):
668 if self.required_perms.intersection(self.user_perms.get('global')):
670 if self.required_perms.intersection(self.user_perms.get('global')):
669 return True
671 return True
670 return False
672 return False
671
673
672
674
673 class HasRepoPermissionAll(PermsFunction):
675 class HasRepoPermissionAll(PermsFunction):
674
676
675 def __call__(self, repo_name=None, check_Location=''):
677 def __call__(self, repo_name=None, check_Location=''):
676 self.repo_name = repo_name
678 self.repo_name = repo_name
677 return super(HasRepoPermissionAll, self).__call__(check_Location)
679 return super(HasRepoPermissionAll, self).__call__(check_Location)
678
680
679 def check_permissions(self):
681 def check_permissions(self):
680 if not self.repo_name:
682 if not self.repo_name:
681 self.repo_name = get_repo_slug(request)
683 self.repo_name = get_repo_slug(request)
682
684
683 try:
685 try:
684 self.user_perms = set(
686 self.user_perms = set(
685 [self.user_perms['repositories'][self.repo_name]]
687 [self.user_perms['repositories'][self.repo_name]]
686 )
688 )
687 except KeyError:
689 except KeyError:
688 return False
690 return False
689 self.granted_for = self.repo_name
691 self.granted_for = self.repo_name
690 if self.required_perms.issubset(self.user_perms):
692 if self.required_perms.issubset(self.user_perms):
691 return True
693 return True
692 return False
694 return False
693
695
694
696
695 class HasRepoPermissionAny(PermsFunction):
697 class HasRepoPermissionAny(PermsFunction):
696
698
697 def __call__(self, repo_name=None, check_Location=''):
699 def __call__(self, repo_name=None, check_Location=''):
698 self.repo_name = repo_name
700 self.repo_name = repo_name
699 return super(HasRepoPermissionAny, self).__call__(check_Location)
701 return super(HasRepoPermissionAny, self).__call__(check_Location)
700
702
701 def check_permissions(self):
703 def check_permissions(self):
702 if not self.repo_name:
704 if not self.repo_name:
703 self.repo_name = get_repo_slug(request)
705 self.repo_name = get_repo_slug(request)
704
706
705 try:
707 try:
706 self.user_perms = set(
708 self.user_perms = set(
707 [self.user_perms['repositories'][self.repo_name]]
709 [self.user_perms['repositories'][self.repo_name]]
708 )
710 )
709 except KeyError:
711 except KeyError:
710 return False
712 return False
711 self.granted_for = self.repo_name
713 self.granted_for = self.repo_name
712 if self.required_perms.intersection(self.user_perms):
714 if self.required_perms.intersection(self.user_perms):
713 return True
715 return True
714 return False
716 return False
715
717
716
718
717 class HasReposGroupPermissionAny(PermsFunction):
719 class HasReposGroupPermissionAny(PermsFunction):
718 def __call__(self, group_name=None, check_Location=''):
720 def __call__(self, group_name=None, check_Location=''):
719 self.group_name = group_name
721 self.group_name = group_name
720 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
722 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
721
723
722 def check_permissions(self):
724 def check_permissions(self):
723 try:
725 try:
724 self.user_perms = set(
726 self.user_perms = set(
725 [self.user_perms['repositories_groups'][self.group_name]]
727 [self.user_perms['repositories_groups'][self.group_name]]
726 )
728 )
727 except KeyError:
729 except KeyError:
728 return False
730 return False
729 self.granted_for = self.repo_name
731 self.granted_for = self.repo_name
730 if self.required_perms.intersection(self.user_perms):
732 if self.required_perms.intersection(self.user_perms):
731 return True
733 return True
732 return False
734 return False
733
735
734
736
735 class HasReposGroupPermissionAll(PermsFunction):
737 class HasReposGroupPermissionAll(PermsFunction):
736 def __call__(self, group_name=None, check_Location=''):
738 def __call__(self, group_name=None, check_Location=''):
737 self.group_name = group_name
739 self.group_name = group_name
738 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
740 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
739
741
740 def check_permissions(self):
742 def check_permissions(self):
741 try:
743 try:
742 self.user_perms = set(
744 self.user_perms = set(
743 [self.user_perms['repositories_groups'][self.group_name]]
745 [self.user_perms['repositories_groups'][self.group_name]]
744 )
746 )
745 except KeyError:
747 except KeyError:
746 return False
748 return False
747 self.granted_for = self.repo_name
749 self.granted_for = self.repo_name
748 if self.required_perms.issubset(self.user_perms):
750 if self.required_perms.issubset(self.user_perms):
749 return True
751 return True
750 return False
752 return False
751
753
752
754
753 #==============================================================================
755 #==============================================================================
754 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
756 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
755 #==============================================================================
757 #==============================================================================
756 class HasPermissionAnyMiddleware(object):
758 class HasPermissionAnyMiddleware(object):
757 def __init__(self, *perms):
759 def __init__(self, *perms):
758 self.required_perms = set(perms)
760 self.required_perms = set(perms)
759
761
760 def __call__(self, user, repo_name):
762 def __call__(self, user, repo_name):
761 usr = AuthUser(user.user_id)
763 usr = AuthUser(user.user_id)
762 try:
764 try:
763 self.user_perms = set([usr.permissions['repositories'][repo_name]])
765 self.user_perms = set([usr.permissions['repositories'][repo_name]])
764 except:
766 except:
765 self.user_perms = set()
767 self.user_perms = set()
766 self.granted_for = ''
768 self.granted_for = ''
767 self.username = user.username
769 self.username = user.username
768 self.repo_name = repo_name
770 self.repo_name = repo_name
769 return self.check_permissions()
771 return self.check_permissions()
770
772
771 def check_permissions(self):
773 def check_permissions(self):
772 log.debug('checking mercurial protocol '
774 log.debug('checking mercurial protocol '
773 'permissions %s for user:%s repository:%s', self.user_perms,
775 'permissions %s for user:%s repository:%s', self.user_perms,
774 self.username, self.repo_name)
776 self.username, self.repo_name)
775 if self.required_perms.intersection(self.user_perms):
777 if self.required_perms.intersection(self.user_perms):
776 log.debug('permission granted')
778 log.debug('permission granted')
777 return True
779 return True
778 log.debug('permission denied')
780 log.debug('permission denied')
779 return False
781 return False
General Comments 0
You need to be logged in to leave comments. Login now