##// END OF EJS Templates
fixes #173, many thanks for slestak for contributing into this one.
marcink -
r1425:3dedf399 beta
parent child Browse files
Show More
@@ -1,9 +1,10 b''
1 List of contributors to RhodeCode project:
1 List of contributors to RhodeCode project:
2 Marcin Kuźmiński <marcin@python-works.com>
2 Marcin Kuźmiński <marcin@python-works.com>
3 Lukasz Balcerzak <lukaszbalcerzak@gmail.com>
3 Lukasz Balcerzak <lukaszbalcerzak@gmail.com>
4 Jason Harris <jason@jasonfharris.com>
4 Jason Harris <jason@jasonfharris.com>
5 Thayne Harbaugh <thayne@fusionio.com>
5 Thayne Harbaugh <thayne@fusionio.com>
6 cejones
6 cejones
7 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
7 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
8 Dmitri Kuznetsov
8 Dmitri Kuznetsov
9 Jared Bunting <jared.bunting@peachjean.com> No newline at end of file
9 Jared Bunting <jared.bunting@peachjean.com>
10 Steve Romanow <slestak989@gmail.com> No newline at end of file
@@ -1,613 +1,613 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 :copyright: (c) 2010 by marcink.
9 :copyright: (c) 2010 by marcink.
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
24
25 import random
25 import random
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import hashlib
28 import hashlib
29
29
30 from tempfile import _RandomNameSequence
30 from tempfile import _RandomNameSequence
31 from decorator import decorator
31 from decorator import decorator
32
32
33 from pylons import config, session, url, request
33 from pylons import config, session, url, request
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38
38
39 if __platform__ in PLATFORM_WIN:
39 if __platform__ in PLATFORM_WIN:
40 from hashlib import sha256
40 from hashlib import sha256
41 if __platform__ in PLATFORM_OTHERS:
41 if __platform__ in PLATFORM_OTHERS:
42 import bcrypt
42 import bcrypt
43
43
44 from rhodecode.lib import str2bool
44 from rhodecode.lib import str2bool, safe_unicode
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
46 from rhodecode.lib.utils import get_repo_slug
46 from rhodecode.lib.utils import get_repo_slug
47 from rhodecode.lib.auth_ldap import AuthLdap
47 from rhodecode.lib.auth_ldap import AuthLdap
48
48
49 from rhodecode.model import meta
49 from rhodecode.model import meta
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.db import Permission, RhodeCodeSettings
51 from rhodecode.model.db import Permission, RhodeCodeSettings
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class PasswordGenerator(object):
56 class PasswordGenerator(object):
57 """This is a simple class for generating password from
57 """This is a simple class for generating password from
58 different sets of characters
58 different sets of characters
59 usage:
59 usage:
60 passwd_gen = PasswordGenerator()
60 passwd_gen = PasswordGenerator()
61 #print 8-letter password containing only big and small letters
61 #print 8-letter password containing only big and small letters
62 of alphabet
62 of alphabet
63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 """
64 """
65 ALPHABETS_NUM = r'''1234567890'''
65 ALPHABETS_NUM = r'''1234567890'''
66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
70 + ALPHABETS_NUM + ALPHABETS_SPECIAL
70 + ALPHABETS_NUM + ALPHABETS_SPECIAL
71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
75
75
76 def __init__(self, passwd=''):
76 def __init__(self, passwd=''):
77 self.passwd = passwd
77 self.passwd = passwd
78
78
79 def gen_password(self, len, type):
79 def gen_password(self, len, type):
80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
81 return self.passwd
81 return self.passwd
82
82
83
83
84 class RhodeCodeCrypto(object):
84 class RhodeCodeCrypto(object):
85
85
86 @classmethod
86 @classmethod
87 def hash_string(cls, str_):
87 def hash_string(cls, str_):
88 """
88 """
89 Cryptographic function used for password hashing based on pybcrypt
89 Cryptographic function used for password hashing based on pybcrypt
90 or pycrypto in windows
90 or pycrypto in windows
91
91
92 :param password: password to hash
92 :param password: password to hash
93 """
93 """
94 if __platform__ in PLATFORM_WIN:
94 if __platform__ in PLATFORM_WIN:
95 return sha256(str_).hexdigest()
95 return sha256(str_).hexdigest()
96 elif __platform__ in PLATFORM_OTHERS:
96 elif __platform__ in PLATFORM_OTHERS:
97 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
97 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
98 else:
98 else:
99 raise Exception('Unknown or unsupported platform %s' \
99 raise Exception('Unknown or unsupported platform %s' \
100 % __platform__)
100 % __platform__)
101
101
102 @classmethod
102 @classmethod
103 def hash_check(cls, password, hashed):
103 def hash_check(cls, password, hashed):
104 """
104 """
105 Checks matching password with it's hashed value, runs different
105 Checks matching password with it's hashed value, runs different
106 implementation based on platform it runs on
106 implementation based on platform it runs on
107
107
108 :param password: password
108 :param password: password
109 :param hashed: password in hashed form
109 :param hashed: password in hashed form
110 """
110 """
111
111
112 if __platform__ in PLATFORM_WIN:
112 if __platform__ in PLATFORM_WIN:
113 return sha256(password).hexdigest() == hashed
113 return sha256(password).hexdigest() == hashed
114 elif __platform__ in PLATFORM_OTHERS:
114 elif __platform__ in PLATFORM_OTHERS:
115 return bcrypt.hashpw(password, hashed) == hashed
115 return bcrypt.hashpw(password, hashed) == hashed
116 else:
116 else:
117 raise Exception('Unknown or unsupported platform %s' \
117 raise Exception('Unknown or unsupported platform %s' \
118 % __platform__)
118 % __platform__)
119
119
120
120
121 def get_crypt_password(password):
121 def get_crypt_password(password):
122 return RhodeCodeCrypto.hash_string(password)
122 return RhodeCodeCrypto.hash_string(password)
123
123
124
124
125 def check_password(password, hashed):
125 def check_password(password, hashed):
126 return RhodeCodeCrypto.hash_check(password, hashed)
126 return RhodeCodeCrypto.hash_check(password, hashed)
127
127
128
128
129 def generate_api_key(username, salt=None):
129 def generate_api_key(username, salt=None):
130 if salt is None:
130 if salt is None:
131 salt = _RandomNameSequence().next()
131 salt = _RandomNameSequence().next()
132
132
133 return hashlib.sha1(username + salt).hexdigest()
133 return hashlib.sha1(username + salt).hexdigest()
134
134
135
135
136 def authfunc(environ, username, password):
136 def authfunc(environ, username, password):
137 """Dummy authentication function used in Mercurial/Git/ and access control,
137 """Dummy authentication function used in Mercurial/Git/ and access control,
138
138
139 :param environ: needed only for using in Basic auth
139 :param environ: needed only for using in Basic auth
140 """
140 """
141 return authenticate(username, password)
141 return authenticate(username, password)
142
142
143
143
144 def authenticate(username, password):
144 def authenticate(username, password):
145 """Authentication function used for access control,
145 """Authentication function used for access control,
146 firstly checks for db authentication then if ldap is enabled for ldap
146 firstly checks for db authentication then if ldap is enabled for ldap
147 authentication, also creates ldap user if not in database
147 authentication, also creates ldap user if not in database
148
148
149 :param username: username
149 :param username: username
150 :param password: password
150 :param password: password
151 """
151 """
152
152
153 user_model = UserModel()
153 user_model = UserModel()
154 user = user_model.get_by_username(username, cache=False)
154 user = user_model.get_by_username(username, cache=False)
155
155
156 log.debug('Authenticating user using RhodeCode account')
156 log.debug('Authenticating user using RhodeCode account')
157 if user is not None and not user.ldap_dn:
157 if user is not None and not user.ldap_dn:
158 if user.active:
158 if user.active:
159 if user.username == 'default' and user.active:
159 if user.username == 'default' and user.active:
160 log.info('user %s authenticated correctly as anonymous user',
160 log.info('user %s authenticated correctly as anonymous user',
161 username)
161 username)
162 return True
162 return True
163
163
164 elif user.username == username and check_password(password,
164 elif user.username == username and check_password(password,
165 user.password):
165 user.password):
166 log.info('user %s authenticated correctly', username)
166 log.info('user %s authenticated correctly', username)
167 return True
167 return True
168 else:
168 else:
169 log.warning('user %s is disabled', username)
169 log.warning('user %s is disabled', username)
170
170
171 else:
171 else:
172 log.debug('Regular authentication failed')
172 log.debug('Regular authentication failed')
173 user_obj = user_model.get_by_username(username, cache=False,
173 user_obj = user_model.get_by_username(username, cache=False,
174 case_insensitive=True)
174 case_insensitive=True)
175
175
176 if user_obj is not None and not user_obj.ldap_dn:
176 if user_obj is not None and not user_obj.ldap_dn:
177 log.debug('this user already exists as non ldap')
177 log.debug('this user already exists as non ldap')
178 return False
178 return False
179
179
180 ldap_settings = RhodeCodeSettings.get_ldap_settings()
180 ldap_settings = RhodeCodeSettings.get_ldap_settings()
181 #======================================================================
181 #======================================================================
182 # FALLBACK TO LDAP AUTH IF ENABLE
182 # FALLBACK TO LDAP AUTH IF ENABLE
183 #======================================================================
183 #======================================================================
184 if str2bool(ldap_settings.get('ldap_active')):
184 if str2bool(ldap_settings.get('ldap_active')):
185 log.debug("Authenticating user using ldap")
185 log.debug("Authenticating user using ldap")
186 kwargs = {
186 kwargs = {
187 'server': ldap_settings.get('ldap_host', ''),
187 'server': ldap_settings.get('ldap_host', ''),
188 'base_dn': ldap_settings.get('ldap_base_dn', ''),
188 'base_dn': ldap_settings.get('ldap_base_dn', ''),
189 'port': ldap_settings.get('ldap_port'),
189 'port': ldap_settings.get('ldap_port'),
190 'bind_dn': ldap_settings.get('ldap_dn_user'),
190 'bind_dn': ldap_settings.get('ldap_dn_user'),
191 'bind_pass': ldap_settings.get('ldap_dn_pass'),
191 'bind_pass': ldap_settings.get('ldap_dn_pass'),
192 'tls_kind': ldap_settings.get('ldap_tls_kind'),
192 'tls_kind': ldap_settings.get('ldap_tls_kind'),
193 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
193 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
194 'ldap_filter': ldap_settings.get('ldap_filter'),
194 'ldap_filter': ldap_settings.get('ldap_filter'),
195 'search_scope': ldap_settings.get('ldap_search_scope'),
195 'search_scope': ldap_settings.get('ldap_search_scope'),
196 'attr_login': ldap_settings.get('ldap_attr_login'),
196 'attr_login': ldap_settings.get('ldap_attr_login'),
197 'ldap_version': 3,
197 'ldap_version': 3,
198 }
198 }
199 log.debug('Checking for ldap authentication')
199 log.debug('Checking for ldap authentication')
200 try:
200 try:
201 aldap = AuthLdap(**kwargs)
201 aldap = AuthLdap(**kwargs)
202 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
202 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
203 password)
203 password)
204 log.debug('Got ldap DN response %s', user_dn)
204 log.debug('Got ldap DN response %s', user_dn)
205
205
206 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
206 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
207 .get(k), [''])[0]
207 .get(k), [''])[0]
208
208
209 user_attrs = {
209 user_attrs = {
210 'name': get_ldap_attr('ldap_attr_firstname'),
210 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
211 'lastname': get_ldap_attr('ldap_attr_lastname'),
211 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
212 'email': get_ldap_attr('ldap_attr_email'),
212 'email': get_ldap_attr('ldap_attr_email'),
213 }
213 }
214
214
215 if user_model.create_ldap(username, password, user_dn,
215 if user_model.create_ldap(username, password, user_dn,
216 user_attrs):
216 user_attrs):
217 log.info('created new ldap user %s', username)
217 log.info('created new ldap user %s', username)
218
218
219 return True
219 return True
220 except (LdapUsernameError, LdapPasswordError,):
220 except (LdapUsernameError, LdapPasswordError,):
221 pass
221 pass
222 except (Exception,):
222 except (Exception,):
223 log.error(traceback.format_exc())
223 log.error(traceback.format_exc())
224 pass
224 pass
225 return False
225 return False
226
226
227
227
228 class AuthUser(object):
228 class AuthUser(object):
229 """
229 """
230 A simple object that handles all attributes of user in RhodeCode
230 A simple object that handles all attributes of user in RhodeCode
231
231
232 It does lookup based on API key,given user, or user present in session
232 It does lookup based on API key,given user, or user present in session
233 Then it fills all required information for such user. It also checks if
233 Then it fills all required information for such user. It also checks if
234 anonymous access is enabled and if so, it returns default user as logged
234 anonymous access is enabled and if so, it returns default user as logged
235 in
235 in
236 """
236 """
237
237
238 def __init__(self, user_id=None, api_key=None):
238 def __init__(self, user_id=None, api_key=None):
239
239
240 self.user_id = user_id
240 self.user_id = user_id
241 self.api_key = None
241 self.api_key = None
242
242
243 self.username = 'None'
243 self.username = 'None'
244 self.name = ''
244 self.name = ''
245 self.lastname = ''
245 self.lastname = ''
246 self.email = ''
246 self.email = ''
247 self.is_authenticated = False
247 self.is_authenticated = False
248 self.admin = False
248 self.admin = False
249 self.permissions = {}
249 self.permissions = {}
250 self._api_key = api_key
250 self._api_key = api_key
251 self.propagate_data()
251 self.propagate_data()
252
252
253 def propagate_data(self):
253 def propagate_data(self):
254 user_model = UserModel()
254 user_model = UserModel()
255 self.anonymous_user = user_model.get_by_username('default', cache=True)
255 self.anonymous_user = user_model.get_by_username('default', cache=True)
256 if self._api_key and self._api_key != self.anonymous_user.api_key:
256 if self._api_key and self._api_key != self.anonymous_user.api_key:
257 #try go get user by api key
257 #try go get user by api key
258 log.debug('Auth User lookup by API KEY %s', self._api_key)
258 log.debug('Auth User lookup by API KEY %s', self._api_key)
259 user_model.fill_data(self, api_key=self._api_key)
259 user_model.fill_data(self, api_key=self._api_key)
260 else:
260 else:
261 log.debug('Auth User lookup by USER ID %s', self.user_id)
261 log.debug('Auth User lookup by USER ID %s', self.user_id)
262 if self.user_id is not None \
262 if self.user_id is not None \
263 and self.user_id != self.anonymous_user.user_id:
263 and self.user_id != self.anonymous_user.user_id:
264 user_model.fill_data(self, user_id=self.user_id)
264 user_model.fill_data(self, user_id=self.user_id)
265 else:
265 else:
266 if self.anonymous_user.active is True:
266 if self.anonymous_user.active is True:
267 user_model.fill_data(self,
267 user_model.fill_data(self,
268 user_id=self.anonymous_user.user_id)
268 user_id=self.anonymous_user.user_id)
269 #then we set this user is logged in
269 #then we set this user is logged in
270 self.is_authenticated = True
270 self.is_authenticated = True
271 else:
271 else:
272 self.is_authenticated = False
272 self.is_authenticated = False
273
273
274 log.debug('Auth User is now %s', self)
274 log.debug('Auth User is now %s', self)
275 user_model.fill_perms(self)
275 user_model.fill_perms(self)
276
276
277 @property
277 @property
278 def is_admin(self):
278 def is_admin(self):
279 return self.admin
279 return self.admin
280
280
281 @property
281 @property
282 def full_contact(self):
282 def full_contact(self):
283 return '%s %s <%s>' % (self.name, self.lastname, self.email)
283 return '%s %s <%s>' % (self.name, self.lastname, self.email)
284
284
285 def __repr__(self):
285 def __repr__(self):
286 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
286 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
287 self.is_authenticated)
287 self.is_authenticated)
288
288
289 def set_authenticated(self, authenticated=True):
289 def set_authenticated(self, authenticated=True):
290
290
291 if self.user_id != self.anonymous_user.user_id:
291 if self.user_id != self.anonymous_user.user_id:
292 self.is_authenticated = authenticated
292 self.is_authenticated = authenticated
293
293
294
294
295 def set_available_permissions(config):
295 def set_available_permissions(config):
296 """This function will propagate pylons globals with all available defined
296 """This function will propagate pylons globals with all available defined
297 permission given in db. We don't want to check each time from db for new
297 permission given in db. We don't want to check each time from db for new
298 permissions since adding a new permission also requires application restart
298 permissions since adding a new permission also requires application restart
299 ie. to decorate new views with the newly created permission
299 ie. to decorate new views with the newly created permission
300
300
301 :param config: current pylons config instance
301 :param config: current pylons config instance
302
302
303 """
303 """
304 log.info('getting information about all available permissions')
304 log.info('getting information about all available permissions')
305 try:
305 try:
306 sa = meta.Session()
306 sa = meta.Session()
307 all_perms = sa.query(Permission).all()
307 all_perms = sa.query(Permission).all()
308 except:
308 except:
309 pass
309 pass
310 finally:
310 finally:
311 meta.Session.remove()
311 meta.Session.remove()
312
312
313 config['available_permissions'] = [x.permission_name for x in all_perms]
313 config['available_permissions'] = [x.permission_name for x in all_perms]
314
314
315
315
316 #==============================================================================
316 #==============================================================================
317 # CHECK DECORATORS
317 # CHECK DECORATORS
318 #==============================================================================
318 #==============================================================================
319 class LoginRequired(object):
319 class LoginRequired(object):
320 """
320 """
321 Must be logged in to execute this function else
321 Must be logged in to execute this function else
322 redirect to login page
322 redirect to login page
323
323
324 :param api_access: if enabled this checks only for valid auth token
324 :param api_access: if enabled this checks only for valid auth token
325 and grants access based on valid token
325 and grants access based on valid token
326 """
326 """
327
327
328 def __init__(self, api_access=False):
328 def __init__(self, api_access=False):
329 self.api_access = api_access
329 self.api_access = api_access
330
330
331 def __call__(self, func):
331 def __call__(self, func):
332 return decorator(self.__wrapper, func)
332 return decorator(self.__wrapper, func)
333
333
334 def __wrapper(self, func, *fargs, **fkwargs):
334 def __wrapper(self, func, *fargs, **fkwargs):
335 cls = fargs[0]
335 cls = fargs[0]
336 user = cls.rhodecode_user
336 user = cls.rhodecode_user
337
337
338 api_access_ok = False
338 api_access_ok = False
339 if self.api_access:
339 if self.api_access:
340 log.debug('Checking API KEY access for %s', cls)
340 log.debug('Checking API KEY access for %s', cls)
341 if user.api_key == request.GET.get('api_key'):
341 if user.api_key == request.GET.get('api_key'):
342 api_access_ok = True
342 api_access_ok = True
343 else:
343 else:
344 log.debug("API KEY token not valid")
344 log.debug("API KEY token not valid")
345
345
346 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
346 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
347 if user.is_authenticated or api_access_ok:
347 if user.is_authenticated or api_access_ok:
348 log.debug('user %s is authenticated', user.username)
348 log.debug('user %s is authenticated', user.username)
349 return func(*fargs, **fkwargs)
349 return func(*fargs, **fkwargs)
350 else:
350 else:
351 log.warn('user %s NOT authenticated', user.username)
351 log.warn('user %s NOT authenticated', user.username)
352 p = url.current()
352 p = url.current()
353
353
354 log.debug('redirecting to login page with %s', p)
354 log.debug('redirecting to login page with %s', p)
355 return redirect(url('login_home', came_from=p))
355 return redirect(url('login_home', came_from=p))
356
356
357
357
358 class NotAnonymous(object):
358 class NotAnonymous(object):
359 """Must be logged in to execute this function else
359 """Must be logged in to execute this function else
360 redirect to login page"""
360 redirect to login page"""
361
361
362 def __call__(self, func):
362 def __call__(self, func):
363 return decorator(self.__wrapper, func)
363 return decorator(self.__wrapper, func)
364
364
365 def __wrapper(self, func, *fargs, **fkwargs):
365 def __wrapper(self, func, *fargs, **fkwargs):
366 cls = fargs[0]
366 cls = fargs[0]
367 self.user = cls.rhodecode_user
367 self.user = cls.rhodecode_user
368
368
369 log.debug('Checking if user is not anonymous @%s', cls)
369 log.debug('Checking if user is not anonymous @%s', cls)
370
370
371 anonymous = self.user.username == 'default'
371 anonymous = self.user.username == 'default'
372
372
373 if anonymous:
373 if anonymous:
374 p = url.current()
374 p = url.current()
375
375
376 import rhodecode.lib.helpers as h
376 import rhodecode.lib.helpers as h
377 h.flash(_('You need to be a registered user to '
377 h.flash(_('You need to be a registered user to '
378 'perform this action'),
378 'perform this action'),
379 category='warning')
379 category='warning')
380 return redirect(url('login_home', came_from=p))
380 return redirect(url('login_home', came_from=p))
381 else:
381 else:
382 return func(*fargs, **fkwargs)
382 return func(*fargs, **fkwargs)
383
383
384
384
385 class PermsDecorator(object):
385 class PermsDecorator(object):
386 """Base class for controller decorators"""
386 """Base class for controller decorators"""
387
387
388 def __init__(self, *required_perms):
388 def __init__(self, *required_perms):
389 available_perms = config['available_permissions']
389 available_perms = config['available_permissions']
390 for perm in required_perms:
390 for perm in required_perms:
391 if perm not in available_perms:
391 if perm not in available_perms:
392 raise Exception("'%s' permission is not defined" % perm)
392 raise Exception("'%s' permission is not defined" % perm)
393 self.required_perms = set(required_perms)
393 self.required_perms = set(required_perms)
394 self.user_perms = None
394 self.user_perms = None
395
395
396 def __call__(self, func):
396 def __call__(self, func):
397 return decorator(self.__wrapper, func)
397 return decorator(self.__wrapper, func)
398
398
399 def __wrapper(self, func, *fargs, **fkwargs):
399 def __wrapper(self, func, *fargs, **fkwargs):
400 cls = fargs[0]
400 cls = fargs[0]
401 self.user = cls.rhodecode_user
401 self.user = cls.rhodecode_user
402 self.user_perms = self.user.permissions
402 self.user_perms = self.user.permissions
403 log.debug('checking %s permissions %s for %s %s',
403 log.debug('checking %s permissions %s for %s %s',
404 self.__class__.__name__, self.required_perms, cls,
404 self.__class__.__name__, self.required_perms, cls,
405 self.user)
405 self.user)
406
406
407 if self.check_permissions():
407 if self.check_permissions():
408 log.debug('Permission granted for %s %s', cls, self.user)
408 log.debug('Permission granted for %s %s', cls, self.user)
409 return func(*fargs, **fkwargs)
409 return func(*fargs, **fkwargs)
410
410
411 else:
411 else:
412 log.warning('Permission denied for %s %s', cls, self.user)
412 log.warning('Permission denied for %s %s', cls, self.user)
413
413
414
414
415 anonymous = self.user.username == 'default'
415 anonymous = self.user.username == 'default'
416
416
417 if anonymous:
417 if anonymous:
418 p = url.current()
418 p = url.current()
419
419
420 import rhodecode.lib.helpers as h
420 import rhodecode.lib.helpers as h
421 h.flash(_('You need to be a signed in to '
421 h.flash(_('You need to be a signed in to '
422 'view this page'),
422 'view this page'),
423 category='warning')
423 category='warning')
424 return redirect(url('login_home', came_from=p))
424 return redirect(url('login_home', came_from=p))
425
425
426 else:
426 else:
427 #redirect with forbidden ret code
427 #redirect with forbidden ret code
428 return abort(403)
428 return abort(403)
429
429
430 def check_permissions(self):
430 def check_permissions(self):
431 """Dummy function for overriding"""
431 """Dummy function for overriding"""
432 raise Exception('You have to write this function in child class')
432 raise Exception('You have to write this function in child class')
433
433
434
434
435 class HasPermissionAllDecorator(PermsDecorator):
435 class HasPermissionAllDecorator(PermsDecorator):
436 """Checks for access permission for all given predicates. All of them
436 """Checks for access permission for all given predicates. All of them
437 have to be meet in order to fulfill the request
437 have to be meet in order to fulfill the request
438 """
438 """
439
439
440 def check_permissions(self):
440 def check_permissions(self):
441 if self.required_perms.issubset(self.user_perms.get('global')):
441 if self.required_perms.issubset(self.user_perms.get('global')):
442 return True
442 return True
443 return False
443 return False
444
444
445
445
446 class HasPermissionAnyDecorator(PermsDecorator):
446 class HasPermissionAnyDecorator(PermsDecorator):
447 """Checks for access permission for any of given predicates. In order to
447 """Checks for access permission for any of given predicates. In order to
448 fulfill the request any of predicates must be meet
448 fulfill the request any of predicates must be meet
449 """
449 """
450
450
451 def check_permissions(self):
451 def check_permissions(self):
452 if self.required_perms.intersection(self.user_perms.get('global')):
452 if self.required_perms.intersection(self.user_perms.get('global')):
453 return True
453 return True
454 return False
454 return False
455
455
456
456
457 class HasRepoPermissionAllDecorator(PermsDecorator):
457 class HasRepoPermissionAllDecorator(PermsDecorator):
458 """Checks for access permission for all given predicates for specific
458 """Checks for access permission for all given predicates for specific
459 repository. All of them have to be meet in order to fulfill the request
459 repository. All of them have to be meet in order to fulfill the request
460 """
460 """
461
461
462 def check_permissions(self):
462 def check_permissions(self):
463 repo_name = get_repo_slug(request)
463 repo_name = get_repo_slug(request)
464 try:
464 try:
465 user_perms = set([self.user_perms['repositories'][repo_name]])
465 user_perms = set([self.user_perms['repositories'][repo_name]])
466 except KeyError:
466 except KeyError:
467 return False
467 return False
468 if self.required_perms.issubset(user_perms):
468 if self.required_perms.issubset(user_perms):
469 return True
469 return True
470 return False
470 return False
471
471
472
472
473 class HasRepoPermissionAnyDecorator(PermsDecorator):
473 class HasRepoPermissionAnyDecorator(PermsDecorator):
474 """Checks for access permission for any of given predicates for specific
474 """Checks for access permission for any of given predicates for specific
475 repository. In order to fulfill the request any of predicates must be meet
475 repository. In order to fulfill the request any of predicates must be meet
476 """
476 """
477
477
478 def check_permissions(self):
478 def check_permissions(self):
479 repo_name = get_repo_slug(request)
479 repo_name = get_repo_slug(request)
480
480
481 try:
481 try:
482 user_perms = set([self.user_perms['repositories'][repo_name]])
482 user_perms = set([self.user_perms['repositories'][repo_name]])
483 except KeyError:
483 except KeyError:
484 return False
484 return False
485 if self.required_perms.intersection(user_perms):
485 if self.required_perms.intersection(user_perms):
486 return True
486 return True
487 return False
487 return False
488
488
489
489
490 #==============================================================================
490 #==============================================================================
491 # CHECK FUNCTIONS
491 # CHECK FUNCTIONS
492 #==============================================================================
492 #==============================================================================
493 class PermsFunction(object):
493 class PermsFunction(object):
494 """Base function for other check functions"""
494 """Base function for other check functions"""
495
495
496 def __init__(self, *perms):
496 def __init__(self, *perms):
497 available_perms = config['available_permissions']
497 available_perms = config['available_permissions']
498
498
499 for perm in perms:
499 for perm in perms:
500 if perm not in available_perms:
500 if perm not in available_perms:
501 raise Exception("'%s' permission in not defined" % perm)
501 raise Exception("'%s' permission in not defined" % perm)
502 self.required_perms = set(perms)
502 self.required_perms = set(perms)
503 self.user_perms = None
503 self.user_perms = None
504 self.granted_for = ''
504 self.granted_for = ''
505 self.repo_name = None
505 self.repo_name = None
506
506
507 def __call__(self, check_Location=''):
507 def __call__(self, check_Location=''):
508 user = session.get('rhodecode_user', False)
508 user = session.get('rhodecode_user', False)
509 if not user:
509 if not user:
510 return False
510 return False
511 self.user_perms = user.permissions
511 self.user_perms = user.permissions
512 self.granted_for = user
512 self.granted_for = user
513 log.debug('checking %s %s %s', self.__class__.__name__,
513 log.debug('checking %s %s %s', self.__class__.__name__,
514 self.required_perms, user)
514 self.required_perms, user)
515
515
516 if self.check_permissions():
516 if self.check_permissions():
517 log.debug('Permission granted %s @ %s', self.granted_for,
517 log.debug('Permission granted %s @ %s', self.granted_for,
518 check_Location or 'unspecified location')
518 check_Location or 'unspecified location')
519 return True
519 return True
520
520
521 else:
521 else:
522 log.warning('Permission denied for %s @ %s', self.granted_for,
522 log.warning('Permission denied for %s @ %s', self.granted_for,
523 check_Location or 'unspecified location')
523 check_Location or 'unspecified location')
524 return False
524 return False
525
525
526 def check_permissions(self):
526 def check_permissions(self):
527 """Dummy function for overriding"""
527 """Dummy function for overriding"""
528 raise Exception('You have to write this function in child class')
528 raise Exception('You have to write this function in child class')
529
529
530
530
531 class HasPermissionAll(PermsFunction):
531 class HasPermissionAll(PermsFunction):
532 def check_permissions(self):
532 def check_permissions(self):
533 if self.required_perms.issubset(self.user_perms.get('global')):
533 if self.required_perms.issubset(self.user_perms.get('global')):
534 return True
534 return True
535 return False
535 return False
536
536
537
537
538 class HasPermissionAny(PermsFunction):
538 class HasPermissionAny(PermsFunction):
539 def check_permissions(self):
539 def check_permissions(self):
540 if self.required_perms.intersection(self.user_perms.get('global')):
540 if self.required_perms.intersection(self.user_perms.get('global')):
541 return True
541 return True
542 return False
542 return False
543
543
544
544
545 class HasRepoPermissionAll(PermsFunction):
545 class HasRepoPermissionAll(PermsFunction):
546
546
547 def __call__(self, repo_name=None, check_Location=''):
547 def __call__(self, repo_name=None, check_Location=''):
548 self.repo_name = repo_name
548 self.repo_name = repo_name
549 return super(HasRepoPermissionAll, self).__call__(check_Location)
549 return super(HasRepoPermissionAll, self).__call__(check_Location)
550
550
551 def check_permissions(self):
551 def check_permissions(self):
552 if not self.repo_name:
552 if not self.repo_name:
553 self.repo_name = get_repo_slug(request)
553 self.repo_name = get_repo_slug(request)
554
554
555 try:
555 try:
556 self.user_perms = set([self.user_perms['reposit'
556 self.user_perms = set([self.user_perms['reposit'
557 'ories'][self.repo_name]])
557 'ories'][self.repo_name]])
558 except KeyError:
558 except KeyError:
559 return False
559 return False
560 self.granted_for = self.repo_name
560 self.granted_for = self.repo_name
561 if self.required_perms.issubset(self.user_perms):
561 if self.required_perms.issubset(self.user_perms):
562 return True
562 return True
563 return False
563 return False
564
564
565
565
566 class HasRepoPermissionAny(PermsFunction):
566 class HasRepoPermissionAny(PermsFunction):
567
567
568 def __call__(self, repo_name=None, check_Location=''):
568 def __call__(self, repo_name=None, check_Location=''):
569 self.repo_name = repo_name
569 self.repo_name = repo_name
570 return super(HasRepoPermissionAny, self).__call__(check_Location)
570 return super(HasRepoPermissionAny, self).__call__(check_Location)
571
571
572 def check_permissions(self):
572 def check_permissions(self):
573 if not self.repo_name:
573 if not self.repo_name:
574 self.repo_name = get_repo_slug(request)
574 self.repo_name = get_repo_slug(request)
575
575
576 try:
576 try:
577 self.user_perms = set([self.user_perms['reposi'
577 self.user_perms = set([self.user_perms['reposi'
578 'tories'][self.repo_name]])
578 'tories'][self.repo_name]])
579 except KeyError:
579 except KeyError:
580 return False
580 return False
581 self.granted_for = self.repo_name
581 self.granted_for = self.repo_name
582 if self.required_perms.intersection(self.user_perms):
582 if self.required_perms.intersection(self.user_perms):
583 return True
583 return True
584 return False
584 return False
585
585
586
586
587 #==============================================================================
587 #==============================================================================
588 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
588 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
589 #==============================================================================
589 #==============================================================================
590 class HasPermissionAnyMiddleware(object):
590 class HasPermissionAnyMiddleware(object):
591 def __init__(self, *perms):
591 def __init__(self, *perms):
592 self.required_perms = set(perms)
592 self.required_perms = set(perms)
593
593
594 def __call__(self, user, repo_name):
594 def __call__(self, user, repo_name):
595 usr = AuthUser(user.user_id)
595 usr = AuthUser(user.user_id)
596 try:
596 try:
597 self.user_perms = set([usr.permissions['repositories'][repo_name]])
597 self.user_perms = set([usr.permissions['repositories'][repo_name]])
598 except:
598 except:
599 self.user_perms = set()
599 self.user_perms = set()
600 self.granted_for = ''
600 self.granted_for = ''
601 self.username = user.username
601 self.username = user.username
602 self.repo_name = repo_name
602 self.repo_name = repo_name
603 return self.check_permissions()
603 return self.check_permissions()
604
604
605 def check_permissions(self):
605 def check_permissions(self):
606 log.debug('checking mercurial protocol '
606 log.debug('checking mercurial protocol '
607 'permissions %s for user:%s repository:%s', self.user_perms,
607 'permissions %s for user:%s repository:%s', self.user_perms,
608 self.username, self.repo_name)
608 self.username, self.repo_name)
609 if self.required_perms.intersection(self.user_perms):
609 if self.required_perms.intersection(self.user_perms):
610 log.debug('permission granted')
610 log.debug('permission granted')
611 return True
611 return True
612 log.debug('permission denied')
612 log.debug('permission denied')
613 return False
613 return False
@@ -1,21 +1,75 b''
1 from rhodecode.tests import *
1 from rhodecode.tests import *
2 from rhodecode.model.db import RhodeCodeSettings
3
4 try:
5 import ldap
6 except ImportError:
7 # means that python-ldap is not installed
8 pass
2
9
3 class TestLdapSettingsController(TestController):
10 class TestLdapSettingsController(TestController):
4
11
5 def test_index(self):
12 def test_index(self):
6 self.log_user()
13 self.log_user()
7 response = self.app.get(url(controller='admin/ldap_settings',
14 response = self.app.get(url(controller='admin/ldap_settings',
8 action='index'))
15 action='index'))
9 # Test response...
16 self.assertTrue('LDAP administration' in response.body)
10
17
11 def test_ldap_save_settings(self):
18 def test_ldap_save_settings(self):
12 pass
19 self.log_user()
20 test_url = url(controller='admin/ldap_settings',
21 action='ldap_settings')
22
23 response = self.app.post(url=test_url,
24 params={'ldap_host' : u'dc.example.com',
25 'ldap_port' : '999',
26 'ldap_tls_kind' : 'PLAIN',
27 'ldap_tls_reqcert' : 'NEVER',
28 'ldap_dn_user':'test_user',
29 'ldap_dn_pass':'test_pass',
30 'ldap_base_dn':'test_base_dn',
31 'ldap_filter':'test_filter',
32 'ldap_search_scope':'BASE',
33 'ldap_attr_login':'test_attr_login',
34 'ldap_attr_firstname':'ima',
35 'ldap_attr_lastname':'tester',
36 'ldap_attr_email':'test@example.com' })
37
38 new_settings = RhodeCodeSettings.get_ldap_settings()
39 self.assertEqual(new_settings['ldap_host'], u'dc.example.com',
40 'fail db write compare')
41
42 self.checkSessionFlash(response,
43 'Ldap settings updated successfully')
13
44
14 def test_ldap_error_form(self):
45 def test_ldap_error_form(self):
15 pass
46 self.log_user()
47 test_url = url(controller='admin/ldap_settings',
48 action='ldap_settings')
49
50 response = self.app.post(url=test_url,
51 params={'ldap_host' : '',
52 'ldap_port' : 'i-should-be-number',
53 'ldap_tls_kind' : 'PLAIN',
54 'ldap_tls_reqcert' : 'NEVER',
55 'ldap_dn_user':'',
56 'ldap_dn_pass':'',
57 'ldap_base_dn':'',
58 'ldap_filter':'',
59 'ldap_search_scope':'BASE',
60 'ldap_attr_login':'', # <----- missing required input
61 'ldap_attr_firstname':'',
62 'ldap_attr_lastname':'',
63 'ldap_attr_email':'' })
64
65 self.assertTrue("""<span class="error-message">The LDAP Login"""
66 """ attribute of the CN must be specified""" in
67 response.body)
68 self.assertTrue("""<span class="error-message">Please """
69 """enter a number</span>""" in response.body)
16
70
17 def test_ldap_login(self):
71 def test_ldap_login(self):
18 pass
72 pass
19
73
20 def test_ldap_login_incorrect(self):
74 def test_ldap_login_incorrect(self):
21 pass
75 pass
General Comments 0
You need to be logged in to leave comments. Login now