##// END OF EJS Templates
Automated merge with https://rhodecode.org/rhodecode
"Lorenzo M. Catucci" -
r1289:f56533aa merge beta
parent child Browse files
Show More
@@ -1,602 +1,602
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
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
51 from rhodecode.model.db import Permission
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 user_model = UserModel()
152 user_model = UserModel()
153 user = user_model.get_by_username(username, cache=False)
153 user = user_model.get_by_username(username, cache=False)
154
154
155 log.debug('Authenticating user using RhodeCode account')
155 log.debug('Authenticating user using RhodeCode account')
156 if user is not None and not user.ldap_dn:
156 if user is not None and not user.ldap_dn:
157 if user.active:
157 if user.active:
158 if user.username == 'default' and user.active:
158 if user.username == 'default' and user.active:
159 log.info('user %s authenticated correctly as anonymous user',
159 log.info('user %s authenticated correctly as anonymous user',
160 username)
160 username)
161 return True
161 return True
162
162
163 elif user.username == username and check_password(password,
163 elif user.username == username and check_password(password,
164 user.password):
164 user.password):
165 log.info('user %s authenticated correctly', username)
165 log.info('user %s authenticated correctly', username)
166 return True
166 return True
167 else:
167 else:
168 log.warning('user %s is disabled', username)
168 log.warning('user %s is disabled', username)
169
169
170 else:
170 else:
171 log.debug('Regular authentication failed')
171 log.debug('Regular authentication failed')
172 user_obj = user_model.get_by_username(username, cache=False,
172 user_obj = user_model.get_by_username(username, cache=False,
173 case_insensitive=True)
173 case_insensitive=True)
174
174
175 if user_obj is not None and not user_obj.ldap_dn:
175 if user_obj is not None and not user_obj.ldap_dn:
176 log.debug('this user already exists as non ldap')
176 log.debug('this user already exists as non ldap')
177 return False
177 return False
178
178
179 from rhodecode.model.settings import SettingsModel
179 from rhodecode.model.settings import SettingsModel
180 ldap_settings = SettingsModel().get_ldap_settings()
180 ldap_settings = SettingsModel().get_ldap_settings()
181
181
182 #======================================================================
182 #======================================================================
183 # FALLBACK TO LDAP AUTH IF ENABLE
183 # FALLBACK TO LDAP AUTH IF ENABLE
184 #======================================================================
184 #======================================================================
185 if str2bool(ldap_settings.get('ldap_active')):
185 if str2bool(ldap_settings.get('ldap_active')):
186 log.debug("Authenticating user using ldap")
186 log.debug("Authenticating user using ldap")
187 kwargs = {
187 kwargs = {
188 'server': ldap_settings.get('ldap_host', ''),
188 'server': ldap_settings.get('ldap_host', ''),
189 'base_dn': ldap_settings.get('ldap_base_dn', ''),
189 'base_dn': ldap_settings.get('ldap_base_dn', ''),
190 'port': ldap_settings.get('ldap_port'),
190 'port': ldap_settings.get('ldap_port'),
191 'bind_dn': ldap_settings.get('ldap_dn_user'),
191 'bind_dn': ldap_settings.get('ldap_dn_user'),
192 'bind_pass': ldap_settings.get('ldap_dn_pass'),
192 'bind_pass': ldap_settings.get('ldap_dn_pass'),
193 'use_ldaps': str2bool(ldap_settings.get('ldap_ldaps')),
193 'use_ldaps': str2bool(ldap_settings.get('ldap_ldaps')),
194 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
194 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
195 'ldap_filter': ldap_settings.get('ldap_filter'),
195 'ldap_filter': ldap_settings.get('ldap_filter'),
196 'search_scope': ldap_settings.get('ldap_search_scope'),
196 'search_scope': ldap_settings.get('ldap_search_scope'),
197 'attr_login': ldap_settings.get('ldap_attr_login'),
197 'attr_login': ldap_settings.get('ldap_attr_login'),
198 'ldap_version': 3,
198 'ldap_version': 3,
199 }
199 }
200 log.debug('Checking for ldap authentication')
200 log.debug('Checking for ldap authentication')
201 try:
201 try:
202 aldap = AuthLdap(**kwargs)
202 aldap = AuthLdap(**kwargs)
203 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
203 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
204 password)
204 password)
205 log.debug('Got ldap DN response %s', user_dn)
205 log.debug('Got ldap DN response %s', user_dn)
206
206
207 user_attrs = {
207 user_attrs = {
208 'name': ldap_attrs[ldap_settings\
208 'name': ldap_attrs.get(ldap_settings\
209 .get('ldap_attr_firstname')][0],
209 .get('ldap_attr_firstname'), [''])[0],
210 'lastname': ldap_attrs[ldap_settings\
210 'lastname': ldap_attrs.get(ldap_settings\
211 .get('ldap_attr_lastname')][0],
211 .get('ldap_attr_lastname'),[''])[0],
212 'email': ldap_attrs[ldap_settings\
212 'email': ldap_attrs.get(ldap_settings\
213 .get('ldap_attr_email')][0],
213 .get('ldap_attr_email'), [''])[0],
214 }
214 }
215
215
216 if user_model.create_ldap(username, password, user_dn,
216 if user_model.create_ldap(username, password, user_dn,
217 user_attrs):
217 user_attrs):
218 log.info('created new ldap user %s', username)
218 log.info('created new ldap user %s', username)
219
219
220 return True
220 return True
221 except (LdapUsernameError, LdapPasswordError,):
221 except (LdapUsernameError, LdapPasswordError,):
222 pass
222 pass
223 except (Exception,):
223 except (Exception,):
224 log.error(traceback.format_exc())
224 log.error(traceback.format_exc())
225 pass
225 pass
226 return False
226 return False
227
227
228
228
229 class AuthUser(object):
229 class AuthUser(object):
230 """
230 """
231 A simple object that handles all attributes of user in RhodeCode
231 A simple object that handles all attributes of user in RhodeCode
232
232
233 It does lookup based on API key,given user, or user present in session
233 It does lookup based on API key,given user, or user present in session
234 Then it fills all required information for such user. It also checks if
234 Then it fills all required information for such user. It also checks if
235 anonymous access is enabled and if so, it returns default user as logged
235 anonymous access is enabled and if so, it returns default user as logged
236 in
236 in
237 """
237 """
238
238
239 def __init__(self, user_id=None, api_key=None):
239 def __init__(self, user_id=None, api_key=None):
240
240
241 self.user_id = user_id
241 self.user_id = user_id
242 self.api_key = None
242 self.api_key = None
243
243
244 self.username = 'None'
244 self.username = 'None'
245 self.name = ''
245 self.name = ''
246 self.lastname = ''
246 self.lastname = ''
247 self.email = ''
247 self.email = ''
248 self.is_authenticated = False
248 self.is_authenticated = False
249 self.admin = False
249 self.admin = False
250 self.permissions = {}
250 self.permissions = {}
251 self._api_key = api_key
251 self._api_key = api_key
252 self.propagate_data()
252 self.propagate_data()
253
253
254 def propagate_data(self):
254 def propagate_data(self):
255 user_model = UserModel()
255 user_model = UserModel()
256 self.anonymous_user = user_model.get_by_username('default', cache=True)
256 self.anonymous_user = user_model.get_by_username('default', cache=True)
257 if self._api_key and self._api_key != self.anonymous_user.api_key:
257 if self._api_key and self._api_key != self.anonymous_user.api_key:
258 #try go get user by api key
258 #try go get user by api key
259 log.debug('Auth User lookup by API KEY %s', self._api_key)
259 log.debug('Auth User lookup by API KEY %s', self._api_key)
260 user_model.fill_data(self, api_key=self._api_key)
260 user_model.fill_data(self, api_key=self._api_key)
261 else:
261 else:
262 log.debug('Auth User lookup by USER ID %s', self.user_id)
262 log.debug('Auth User lookup by USER ID %s', self.user_id)
263 if self.user_id is not None \
263 if self.user_id is not None \
264 and self.user_id != self.anonymous_user.user_id:
264 and self.user_id != self.anonymous_user.user_id:
265 user_model.fill_data(self, user_id=self.user_id)
265 user_model.fill_data(self, user_id=self.user_id)
266 else:
266 else:
267 if self.anonymous_user.active is True:
267 if self.anonymous_user.active is True:
268 user_model.fill_data(self,
268 user_model.fill_data(self,
269 user_id=self.anonymous_user.user_id)
269 user_id=self.anonymous_user.user_id)
270 #then we set this user is logged in
270 #then we set this user is logged in
271 self.is_authenticated = True
271 self.is_authenticated = True
272 else:
272 else:
273 self.is_authenticated = False
273 self.is_authenticated = False
274
274
275 log.debug('Auth User is now %s', self)
275 log.debug('Auth User is now %s', self)
276 user_model.fill_perms(self)
276 user_model.fill_perms(self)
277
277
278 @property
278 @property
279 def is_admin(self):
279 def is_admin(self):
280 return self.admin
280 return self.admin
281
281
282 def __repr__(self):
282 def __repr__(self):
283 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
283 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
284 self.is_authenticated)
284 self.is_authenticated)
285
285
286 def set_authenticated(self, authenticated=True):
286 def set_authenticated(self, authenticated=True):
287
287
288 if self.user_id != self.anonymous_user.user_id:
288 if self.user_id != self.anonymous_user.user_id:
289 self.is_authenticated = authenticated
289 self.is_authenticated = authenticated
290
290
291
291
292 def set_available_permissions(config):
292 def set_available_permissions(config):
293 """This function will propagate pylons globals with all available defined
293 """This function will propagate pylons globals with all available defined
294 permission given in db. We don't want to check each time from db for new
294 permission given in db. We don't want to check each time from db for new
295 permissions since adding a new permission also requires application restart
295 permissions since adding a new permission also requires application restart
296 ie. to decorate new views with the newly created permission
296 ie. to decorate new views with the newly created permission
297
297
298 :param config: current pylons config instance
298 :param config: current pylons config instance
299
299
300 """
300 """
301 log.info('getting information about all available permissions')
301 log.info('getting information about all available permissions')
302 try:
302 try:
303 sa = meta.Session()
303 sa = meta.Session()
304 all_perms = sa.query(Permission).all()
304 all_perms = sa.query(Permission).all()
305 except:
305 except:
306 pass
306 pass
307 finally:
307 finally:
308 meta.Session.remove()
308 meta.Session.remove()
309
309
310 config['available_permissions'] = [x.permission_name for x in all_perms]
310 config['available_permissions'] = [x.permission_name for x in all_perms]
311
311
312
312
313 #==============================================================================
313 #==============================================================================
314 # CHECK DECORATORS
314 # CHECK DECORATORS
315 #==============================================================================
315 #==============================================================================
316 class LoginRequired(object):
316 class LoginRequired(object):
317 """
317 """
318 Must be logged in to execute this function else
318 Must be logged in to execute this function else
319 redirect to login page
319 redirect to login page
320
320
321 :param api_access: if enabled this checks only for valid auth token
321 :param api_access: if enabled this checks only for valid auth token
322 and grants access based on valid token
322 and grants access based on valid token
323 """
323 """
324
324
325 def __init__(self, api_access=False):
325 def __init__(self, api_access=False):
326 self.api_access = api_access
326 self.api_access = api_access
327
327
328 def __call__(self, func):
328 def __call__(self, func):
329 return decorator(self.__wrapper, func)
329 return decorator(self.__wrapper, func)
330
330
331 def __wrapper(self, func, *fargs, **fkwargs):
331 def __wrapper(self, func, *fargs, **fkwargs):
332 cls = fargs[0]
332 cls = fargs[0]
333 user = cls.rhodecode_user
333 user = cls.rhodecode_user
334
334
335 api_access_ok = False
335 api_access_ok = False
336 if self.api_access:
336 if self.api_access:
337 log.debug('Checking API KEY access for %s', cls)
337 log.debug('Checking API KEY access for %s', cls)
338 if user.api_key == request.GET.get('api_key'):
338 if user.api_key == request.GET.get('api_key'):
339 api_access_ok = True
339 api_access_ok = True
340 else:
340 else:
341 log.debug("API KEY token not valid")
341 log.debug("API KEY token not valid")
342
342
343 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
343 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
344 if user.is_authenticated or api_access_ok:
344 if user.is_authenticated or api_access_ok:
345 log.debug('user %s is authenticated', user.username)
345 log.debug('user %s is authenticated', user.username)
346 return func(*fargs, **fkwargs)
346 return func(*fargs, **fkwargs)
347 else:
347 else:
348 log.warn('user %s NOT authenticated', user.username)
348 log.warn('user %s NOT authenticated', user.username)
349 p = url.current()
349 p = url.current()
350
350
351 log.debug('redirecting to login page with %s', p)
351 log.debug('redirecting to login page with %s', p)
352 return redirect(url('login_home', came_from=p))
352 return redirect(url('login_home', came_from=p))
353
353
354
354
355 class NotAnonymous(object):
355 class NotAnonymous(object):
356 """Must be logged in to execute this function else
356 """Must be logged in to execute this function else
357 redirect to login page"""
357 redirect to login page"""
358
358
359 def __call__(self, func):
359 def __call__(self, func):
360 return decorator(self.__wrapper, func)
360 return decorator(self.__wrapper, func)
361
361
362 def __wrapper(self, func, *fargs, **fkwargs):
362 def __wrapper(self, func, *fargs, **fkwargs):
363 cls = fargs[0]
363 cls = fargs[0]
364 self.user = cls.rhodecode_user
364 self.user = cls.rhodecode_user
365
365
366 log.debug('Checking if user is not anonymous @%s', cls)
366 log.debug('Checking if user is not anonymous @%s', cls)
367
367
368 anonymous = self.user.username == 'default'
368 anonymous = self.user.username == 'default'
369
369
370 if anonymous:
370 if anonymous:
371 p = ''
371 p = ''
372 if request.environ.get('SCRIPT_NAME') != '/':
372 if request.environ.get('SCRIPT_NAME') != '/':
373 p += request.environ.get('SCRIPT_NAME')
373 p += request.environ.get('SCRIPT_NAME')
374
374
375 p += request.environ.get('PATH_INFO')
375 p += request.environ.get('PATH_INFO')
376 if request.environ.get('QUERY_STRING'):
376 if request.environ.get('QUERY_STRING'):
377 p += '?' + request.environ.get('QUERY_STRING')
377 p += '?' + request.environ.get('QUERY_STRING')
378
378
379 import rhodecode.lib.helpers as h
379 import rhodecode.lib.helpers as h
380 h.flash(_('You need to be a registered user to '
380 h.flash(_('You need to be a registered user to '
381 'perform this action'),
381 'perform this action'),
382 category='warning')
382 category='warning')
383 return redirect(url('login_home', came_from=p))
383 return redirect(url('login_home', came_from=p))
384 else:
384 else:
385 return func(*fargs, **fkwargs)
385 return func(*fargs, **fkwargs)
386
386
387
387
388 class PermsDecorator(object):
388 class PermsDecorator(object):
389 """Base class for controller decorators"""
389 """Base class for controller decorators"""
390
390
391 def __init__(self, *required_perms):
391 def __init__(self, *required_perms):
392 available_perms = config['available_permissions']
392 available_perms = config['available_permissions']
393 for perm in required_perms:
393 for perm in required_perms:
394 if perm not in available_perms:
394 if perm not in available_perms:
395 raise Exception("'%s' permission is not defined" % perm)
395 raise Exception("'%s' permission is not defined" % perm)
396 self.required_perms = set(required_perms)
396 self.required_perms = set(required_perms)
397 self.user_perms = None
397 self.user_perms = None
398
398
399 def __call__(self, func):
399 def __call__(self, func):
400 return decorator(self.__wrapper, func)
400 return decorator(self.__wrapper, func)
401
401
402 def __wrapper(self, func, *fargs, **fkwargs):
402 def __wrapper(self, func, *fargs, **fkwargs):
403 cls = fargs[0]
403 cls = fargs[0]
404 self.user = cls.rhodecode_user
404 self.user = cls.rhodecode_user
405 self.user_perms = self.user.permissions
405 self.user_perms = self.user.permissions
406 log.debug('checking %s permissions %s for %s %s',
406 log.debug('checking %s permissions %s for %s %s',
407 self.__class__.__name__, self.required_perms, cls,
407 self.__class__.__name__, self.required_perms, cls,
408 self.user)
408 self.user)
409
409
410 if self.check_permissions():
410 if self.check_permissions():
411 log.debug('Permission granted for %s %s', cls, self.user)
411 log.debug('Permission granted for %s %s', cls, self.user)
412 return func(*fargs, **fkwargs)
412 return func(*fargs, **fkwargs)
413
413
414 else:
414 else:
415 log.warning('Permission denied for %s %s', cls, self.user)
415 log.warning('Permission denied for %s %s', cls, self.user)
416 #redirect with forbidden ret code
416 #redirect with forbidden ret code
417 return abort(403)
417 return abort(403)
418
418
419 def check_permissions(self):
419 def check_permissions(self):
420 """Dummy function for overriding"""
420 """Dummy function for overriding"""
421 raise Exception('You have to write this function in child class')
421 raise Exception('You have to write this function in child class')
422
422
423
423
424 class HasPermissionAllDecorator(PermsDecorator):
424 class HasPermissionAllDecorator(PermsDecorator):
425 """Checks for access permission for all given predicates. All of them
425 """Checks for access permission for all given predicates. All of them
426 have to be meet in order to fulfill the request
426 have to be meet in order to fulfill the request
427 """
427 """
428
428
429 def check_permissions(self):
429 def check_permissions(self):
430 if self.required_perms.issubset(self.user_perms.get('global')):
430 if self.required_perms.issubset(self.user_perms.get('global')):
431 return True
431 return True
432 return False
432 return False
433
433
434
434
435 class HasPermissionAnyDecorator(PermsDecorator):
435 class HasPermissionAnyDecorator(PermsDecorator):
436 """Checks for access permission for any of given predicates. In order to
436 """Checks for access permission for any of given predicates. In order to
437 fulfill the request any of predicates must be meet
437 fulfill the request any of predicates must be meet
438 """
438 """
439
439
440 def check_permissions(self):
440 def check_permissions(self):
441 if self.required_perms.intersection(self.user_perms.get('global')):
441 if self.required_perms.intersection(self.user_perms.get('global')):
442 return True
442 return True
443 return False
443 return False
444
444
445
445
446 class HasRepoPermissionAllDecorator(PermsDecorator):
446 class HasRepoPermissionAllDecorator(PermsDecorator):
447 """Checks for access permission for all given predicates for specific
447 """Checks for access permission for all given predicates for specific
448 repository. All of them have to be meet in order to fulfill the request
448 repository. All of them have to be meet in order to fulfill the request
449 """
449 """
450
450
451 def check_permissions(self):
451 def check_permissions(self):
452 repo_name = get_repo_slug(request)
452 repo_name = get_repo_slug(request)
453 try:
453 try:
454 user_perms = set([self.user_perms['repositories'][repo_name]])
454 user_perms = set([self.user_perms['repositories'][repo_name]])
455 except KeyError:
455 except KeyError:
456 return False
456 return False
457 if self.required_perms.issubset(user_perms):
457 if self.required_perms.issubset(user_perms):
458 return True
458 return True
459 return False
459 return False
460
460
461
461
462 class HasRepoPermissionAnyDecorator(PermsDecorator):
462 class HasRepoPermissionAnyDecorator(PermsDecorator):
463 """Checks for access permission for any of given predicates for specific
463 """Checks for access permission for any of given predicates for specific
464 repository. In order to fulfill the request any of predicates must be meet
464 repository. In order to fulfill the request any of predicates must be meet
465 """
465 """
466
466
467 def check_permissions(self):
467 def check_permissions(self):
468 repo_name = get_repo_slug(request)
468 repo_name = get_repo_slug(request)
469
469
470 try:
470 try:
471 user_perms = set([self.user_perms['repositories'][repo_name]])
471 user_perms = set([self.user_perms['repositories'][repo_name]])
472 except KeyError:
472 except KeyError:
473 return False
473 return False
474 if self.required_perms.intersection(user_perms):
474 if self.required_perms.intersection(user_perms):
475 return True
475 return True
476 return False
476 return False
477
477
478
478
479 #==============================================================================
479 #==============================================================================
480 # CHECK FUNCTIONS
480 # CHECK FUNCTIONS
481 #==============================================================================
481 #==============================================================================
482 class PermsFunction(object):
482 class PermsFunction(object):
483 """Base function for other check functions"""
483 """Base function for other check functions"""
484
484
485 def __init__(self, *perms):
485 def __init__(self, *perms):
486 available_perms = config['available_permissions']
486 available_perms = config['available_permissions']
487
487
488 for perm in perms:
488 for perm in perms:
489 if perm not in available_perms:
489 if perm not in available_perms:
490 raise Exception("'%s' permission in not defined" % perm)
490 raise Exception("'%s' permission in not defined" % perm)
491 self.required_perms = set(perms)
491 self.required_perms = set(perms)
492 self.user_perms = None
492 self.user_perms = None
493 self.granted_for = ''
493 self.granted_for = ''
494 self.repo_name = None
494 self.repo_name = None
495
495
496 def __call__(self, check_Location=''):
496 def __call__(self, check_Location=''):
497 user = session.get('rhodecode_user', False)
497 user = session.get('rhodecode_user', False)
498 if not user:
498 if not user:
499 return False
499 return False
500 self.user_perms = user.permissions
500 self.user_perms = user.permissions
501 self.granted_for = user
501 self.granted_for = user
502 log.debug('checking %s %s %s', self.__class__.__name__,
502 log.debug('checking %s %s %s', self.__class__.__name__,
503 self.required_perms, user)
503 self.required_perms, user)
504
504
505 if self.check_permissions():
505 if self.check_permissions():
506 log.debug('Permission granted %s @ %s', self.granted_for,
506 log.debug('Permission granted %s @ %s', self.granted_for,
507 check_Location or 'unspecified location')
507 check_Location or 'unspecified location')
508 return True
508 return True
509
509
510 else:
510 else:
511 log.warning('Permission denied for %s @ %s', self.granted_for,
511 log.warning('Permission denied for %s @ %s', self.granted_for,
512 check_Location or 'unspecified location')
512 check_Location or 'unspecified location')
513 return False
513 return False
514
514
515 def check_permissions(self):
515 def check_permissions(self):
516 """Dummy function for overriding"""
516 """Dummy function for overriding"""
517 raise Exception('You have to write this function in child class')
517 raise Exception('You have to write this function in child class')
518
518
519
519
520 class HasPermissionAll(PermsFunction):
520 class HasPermissionAll(PermsFunction):
521 def check_permissions(self):
521 def check_permissions(self):
522 if self.required_perms.issubset(self.user_perms.get('global')):
522 if self.required_perms.issubset(self.user_perms.get('global')):
523 return True
523 return True
524 return False
524 return False
525
525
526
526
527 class HasPermissionAny(PermsFunction):
527 class HasPermissionAny(PermsFunction):
528 def check_permissions(self):
528 def check_permissions(self):
529 if self.required_perms.intersection(self.user_perms.get('global')):
529 if self.required_perms.intersection(self.user_perms.get('global')):
530 return True
530 return True
531 return False
531 return False
532
532
533
533
534 class HasRepoPermissionAll(PermsFunction):
534 class HasRepoPermissionAll(PermsFunction):
535
535
536 def __call__(self, repo_name=None, check_Location=''):
536 def __call__(self, repo_name=None, check_Location=''):
537 self.repo_name = repo_name
537 self.repo_name = repo_name
538 return super(HasRepoPermissionAll, self).__call__(check_Location)
538 return super(HasRepoPermissionAll, self).__call__(check_Location)
539
539
540 def check_permissions(self):
540 def check_permissions(self):
541 if not self.repo_name:
541 if not self.repo_name:
542 self.repo_name = get_repo_slug(request)
542 self.repo_name = get_repo_slug(request)
543
543
544 try:
544 try:
545 self.user_perms = set([self.user_perms['reposit'
545 self.user_perms = set([self.user_perms['reposit'
546 'ories'][self.repo_name]])
546 'ories'][self.repo_name]])
547 except KeyError:
547 except KeyError:
548 return False
548 return False
549 self.granted_for = self.repo_name
549 self.granted_for = self.repo_name
550 if self.required_perms.issubset(self.user_perms):
550 if self.required_perms.issubset(self.user_perms):
551 return True
551 return True
552 return False
552 return False
553
553
554
554
555 class HasRepoPermissionAny(PermsFunction):
555 class HasRepoPermissionAny(PermsFunction):
556
556
557 def __call__(self, repo_name=None, check_Location=''):
557 def __call__(self, repo_name=None, check_Location=''):
558 self.repo_name = repo_name
558 self.repo_name = repo_name
559 return super(HasRepoPermissionAny, self).__call__(check_Location)
559 return super(HasRepoPermissionAny, self).__call__(check_Location)
560
560
561 def check_permissions(self):
561 def check_permissions(self):
562 if not self.repo_name:
562 if not self.repo_name:
563 self.repo_name = get_repo_slug(request)
563 self.repo_name = get_repo_slug(request)
564
564
565 try:
565 try:
566 self.user_perms = set([self.user_perms['reposi'
566 self.user_perms = set([self.user_perms['reposi'
567 'tories'][self.repo_name]])
567 'tories'][self.repo_name]])
568 except KeyError:
568 except KeyError:
569 return False
569 return False
570 self.granted_for = self.repo_name
570 self.granted_for = self.repo_name
571 if self.required_perms.intersection(self.user_perms):
571 if self.required_perms.intersection(self.user_perms):
572 return True
572 return True
573 return False
573 return False
574
574
575
575
576 #==============================================================================
576 #==============================================================================
577 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
577 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
578 #==============================================================================
578 #==============================================================================
579 class HasPermissionAnyMiddleware(object):
579 class HasPermissionAnyMiddleware(object):
580 def __init__(self, *perms):
580 def __init__(self, *perms):
581 self.required_perms = set(perms)
581 self.required_perms = set(perms)
582
582
583 def __call__(self, user, repo_name):
583 def __call__(self, user, repo_name):
584 usr = AuthUser(user.user_id)
584 usr = AuthUser(user.user_id)
585 try:
585 try:
586 self.user_perms = set([usr.permissions['repositories'][repo_name]])
586 self.user_perms = set([usr.permissions['repositories'][repo_name]])
587 except:
587 except:
588 self.user_perms = set()
588 self.user_perms = set()
589 self.granted_for = ''
589 self.granted_for = ''
590 self.username = user.username
590 self.username = user.username
591 self.repo_name = repo_name
591 self.repo_name = repo_name
592 return self.check_permissions()
592 return self.check_permissions()
593
593
594 def check_permissions(self):
594 def check_permissions(self):
595 log.debug('checking mercurial protocol '
595 log.debug('checking mercurial protocol '
596 'permissions %s for user:%s repository:%s', self.user_perms,
596 'permissions %s for user:%s repository:%s', self.user_perms,
597 self.username, self.repo_name)
597 self.username, self.repo_name)
598 if self.required_perms.intersection(self.user_perms):
598 if self.required_perms.intersection(self.user_perms):
599 log.debug('permission granted')
599 log.debug('permission granted')
600 return True
600 return True
601 log.debug('permission denied')
601 log.debug('permission denied')
602 return False
602 return False
@@ -1,128 +1,129
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # ldap authentication lib
3 # ldap authentication lib
4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
5 #
5 #
6 # This program is free software: you can redistribute it and/or modify
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
9 # (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 """
18 """
19 Created on Nov 17, 2010
19 Created on Nov 17, 2010
20
20
21 @author: marcink
21 @author: marcink
22 """
22 """
23
23
24 from rhodecode.lib.exceptions import *
24 from rhodecode.lib.exceptions import *
25 import logging
25 import logging
26
26
27 log = logging.getLogger(__name__)
27 log = logging.getLogger(__name__)
28
28
29 try:
29 try:
30 import ldap
30 import ldap
31 except ImportError:
31 except ImportError:
32 pass
32 pass
33
33
34 class AuthLdap(object):
34 class AuthLdap(object):
35
35
36 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
36 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
37 use_ldaps=False, tls_reqcert='DEMAND', ldap_version=3,
37 use_ldaps=False, tls_reqcert='DEMAND', ldap_version=3,
38 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
38 ldap_filter='(&(objectClass=user)(!(objectClass=computer)))',
39 search_scope='SUBTREE',
39 search_scope='SUBTREE',
40 attr_login='uid'):
40 attr_login='uid'):
41 self.ldap_version = ldap_version
41 self.ldap_version = ldap_version
42 if use_ldaps:
42 if use_ldaps:
43 port = port or 689
43 port = port or 689
44 self.LDAP_USE_LDAPS = use_ldaps
44 self.LDAP_USE_LDAPS = use_ldaps
45 self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
45 self.TLS_REQCERT = ldap.__dict__['OPT_X_TLS_' + tls_reqcert]
46 self.LDAP_SERVER_ADDRESS = server
46 self.LDAP_SERVER_ADDRESS = server
47 self.LDAP_SERVER_PORT = port
47 self.LDAP_SERVER_PORT = port
48
48
49 #USE FOR READ ONLY BIND TO LDAP SERVER
49 #USE FOR READ ONLY BIND TO LDAP SERVER
50 self.LDAP_BIND_DN = bind_dn
50 self.LDAP_BIND_DN = bind_dn
51 self.LDAP_BIND_PASS = bind_pass
51 self.LDAP_BIND_PASS = bind_pass
52
52
53 ldap_server_type = 'ldap'
53 ldap_server_type = 'ldap'
54 if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
54 if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
55 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
55 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
56 self.LDAP_SERVER_ADDRESS,
56 self.LDAP_SERVER_ADDRESS,
57 self.LDAP_SERVER_PORT)
57 self.LDAP_SERVER_PORT)
58
58
59 self.BASE_DN = base_dn
59 self.BASE_DN = base_dn
60 self.LDAP_FILTER = ldap_filter
60 self.LDAP_FILTER = ldap_filter
61 self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
61 self.SEARCH_SCOPE = ldap.__dict__['SCOPE_' + search_scope]
62 self.attr_login = attr_login
62 self.attr_login = attr_login
63
63
64
64
65 def authenticate_ldap(self, username, password):
65 def authenticate_ldap(self, username, password):
66 """Authenticate a user via LDAP and return his/her LDAP properties.
66 """Authenticate a user via LDAP and return his/her LDAP properties.
67
67
68 Raises AuthenticationError if the credentials are rejected, or
68 Raises AuthenticationError if the credentials are rejected, or
69 EnvironmentError if the LDAP server can't be reached.
69 EnvironmentError if the LDAP server can't be reached.
70
70
71 :param username: username
71 :param username: username
72 :param password: password
72 :param password: password
73 """
73 """
74
74
75 from rhodecode.lib.helpers import chop_at
75 from rhodecode.lib.helpers import chop_at
76
76
77 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
77 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
78
78
79 if "," in username:
79 if "," in username:
80 raise LdapUsernameError("invalid character in username: ,")
80 raise LdapUsernameError("invalid character in username: ,")
81 try:
81 try:
82 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
82 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
83 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
83 ldap.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
84 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
84 ldap.set_option(ldap.OPT_RESTART, ldap.OPT_ON)
85 ldap.set_option(ldap.OPT_TIMEOUT, 20)
85 ldap.set_option(ldap.OPT_TIMEOUT, 20)
86 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
86 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
87 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
87 ldap.set_option(ldap.OPT_TIMELIMIT, 15)
88 if self.LDAP_USE_LDAPS:
88 if self.LDAP_USE_LDAPS:
89 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
89 ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, self.TLS_REQCERT)
90 server = ldap.initialize(self.LDAP_SERVER)
90 server = ldap.initialize(self.LDAP_SERVER)
91 if self.ldap_version == 2:
91 if self.ldap_version == 2:
92 server.protocol = ldap.VERSION2
92 server.protocol = ldap.VERSION2
93 else:
93 else:
94 server.protocol = ldap.VERSION3
94 server.protocol = ldap.VERSION3
95
95
96 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
96 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
97 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
97 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
98
98
99 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, username)
99 filt = '(&%s(%s=%s))' % (self.LDAP_FILTER, self.attr_login, username)
100 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
100 log.debug("Authenticating %r filt %s at %s", self.BASE_DN,
101 filt, self.LDAP_SERVER)
101 filt, self.LDAP_SERVER)
102 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
102 lobjects = server.search_ext_s(self.BASE_DN, self.SEARCH_SCOPE,
103 filt)
103 filt)
104
104
105 if not lobjects:
105 if not lobjects:
106 raise ldap.NO_SUCH_OBJECT()
106 raise ldap.NO_SUCH_OBJECT()
107
107
108 for (dn, attrs) in lobjects:
108 for (dn, _attrs) in lobjects:
109 try:
109 try:
110 server.simple_bind_s(dn, password)
110 server.simple_bind_s(dn, password)
111 attrs = server.search_ext_s(dn, ldap.SCOPE_BASE, '(objectClass=*)')[0][1]
111 break
112 break
112
113
113 except ldap.INVALID_CREDENTIALS, e:
114 except ldap.INVALID_CREDENTIALS, e:
114 log.debug("LDAP rejected password for user '%s' (%s): %s",
115 log.debug("LDAP rejected password for user '%s' (%s): %s",
115 uid, username, dn)
116 uid, username, dn)
116
117
117 else:
118 else:
118 log.debug("No matching LDAP objects for authentication "
119 log.debug("No matching LDAP objects for authentication "
119 "of '%s' (%s)", uid, username)
120 "of '%s' (%s)", uid, username)
120 raise LdapPasswordError()
121 raise LdapPasswordError()
121
122
122 except ldap.NO_SUCH_OBJECT, e:
123 except ldap.NO_SUCH_OBJECT, e:
123 log.debug("LDAP says no such user '%s' (%s)", uid, username)
124 log.debug("LDAP says no such user '%s' (%s)", uid, username)
124 raise LdapUsernameError()
125 raise LdapUsernameError()
125 except ldap.SERVER_DOWN, e:
126 except ldap.SERVER_DOWN, e:
126 raise LdapConnectionError("LDAP can't access authentication server")
127 raise LdapConnectionError("LDAP can't access authentication server")
127
128
128 return (dn, attrs)
129 return (dn, attrs)
General Comments 0
You need to be logged in to leave comments. Login now