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