##// END OF EJS Templates
fixes #136 leftover patch
marcink -
r1168:7327a0d1 default
parent child Browse files
Show More
@@ -1,614 +1,614 b''
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
13 13 # modify it under the terms of the GNU General Public License
14 14 # as published by the Free Software Foundation; version 2
15 15 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
24 24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 25 # MA 02110-1301, USA.
26 26
27 27 import random
28 28 import logging
29 29 import traceback
30 30
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, RepoToPerm, Repository, \
52 52 User, UserToPerm
53 53
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57 class PasswordGenerator(object):
58 58 """This is a simple class for generating password from
59 59 different sets of characters
60 60 usage:
61 61 passwd_gen = PasswordGenerator()
62 62 #print 8-letter password containing only big and small letters of alphabet
63 63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 64 """
65 65 ALPHABETS_NUM = r'''1234567890'''#[0]
66 66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
67 67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
68 68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
69 69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
70 70 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
71 71 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
72 72 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
73 73 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
74 74
75 75 def __init__(self, passwd=''):
76 76 self.passwd = passwd
77 77
78 78 def gen_password(self, len, type):
79 79 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
80 80 return self.passwd
81 81
82 82 class RhodeCodeCrypto(object):
83 83
84 84 @classmethod
85 85 def hash_string(cls, str_):
86 86 """
87 87 Cryptographic function used for password hashing based on pybcrypt
88 88 or pycrypto in windows
89 89
90 90 :param password: password to hash
91 91 """
92 92 if __platform__ in PLATFORM_WIN:
93 93 return sha256(str_).hexdigest()
94 94 elif __platform__ in PLATFORM_OTHERS:
95 95 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
96 96 else:
97 97 raise Exception('Unknown or unsupported platform %s' % __platform__)
98 98
99 99 @classmethod
100 100 def hash_check(cls, password, hashed):
101 101 """
102 102 Checks matching password with it's hashed value, runs different
103 103 implementation based on platform it runs on
104 104
105 105 :param password: password
106 106 :param hashed: password in hashed form
107 107 """
108 108
109 if __platform__ == 'Windows':
109 if __platform__ in PLATFORM_WIN:
110 110 return sha256(password).hexdigest() == hashed
111 elif __platform__ in ('Linux', 'Darwin'):
111 elif __platform__ in PLATFORM_OTHERS:
112 112 return bcrypt.hashpw(password, hashed) == hashed
113 113 else:
114 114 raise Exception('Unknown or unsupported platform %s' % __platform__)
115 115
116 116
117 117 def get_crypt_password(password):
118 118 return RhodeCodeCrypto.hash_string(password)
119 119
120 120 def check_password(password, hashed):
121 121 return RhodeCodeCrypto.hash_check(password, hashed)
122 122
123 123 def authfunc(environ, username, password):
124 124 """
125 125 Dummy authentication function used in Mercurial/Git/ and access control,
126 126
127 127 :param environ: needed only for using in Basic auth
128 128 """
129 129 return authenticate(username, password)
130 130
131 131
132 132 def authenticate(username, password):
133 133 """
134 134 Authentication function used for access control,
135 135 firstly checks for db authentication then if ldap is enabled for ldap
136 136 authentication, also creates ldap user if not in database
137 137
138 138 :param username: username
139 139 :param password: password
140 140 """
141 141 user_model = UserModel()
142 142 user = user_model.get_by_username(username, cache=False)
143 143
144 144 log.debug('Authenticating user using RhodeCode account')
145 145 if user is not None and user.is_ldap is False:
146 146 if user.active:
147 147
148 148 if user.username == 'default' and user.active:
149 149 log.info('user %s authenticated correctly as anonymous user',
150 150 username)
151 151 return True
152 152
153 153 elif user.username == username and check_password(password, user.password):
154 154 log.info('user %s authenticated correctly', username)
155 155 return True
156 156 else:
157 157 log.warning('user %s is disabled', username)
158 158
159 159 else:
160 160 log.debug('Regular authentication failed')
161 161 user_obj = user_model.get_by_username(username, cache=False,
162 162 case_insensitive=True)
163 163
164 164 if user_obj is not None and user_obj.is_ldap is False:
165 165 log.debug('this user already exists as non ldap')
166 166 return False
167 167
168 168 from rhodecode.model.settings import SettingsModel
169 169 ldap_settings = SettingsModel().get_ldap_settings()
170 170
171 171 #======================================================================
172 172 # FALLBACK TO LDAP AUTH IN ENABLE
173 173 #======================================================================
174 174 if str2bool(ldap_settings.get('ldap_active')):
175 175 log.debug("Authenticating user using ldap")
176 176 kwargs = {
177 177 'server':ldap_settings.get('ldap_host', ''),
178 178 'base_dn':ldap_settings.get('ldap_base_dn', ''),
179 179 'port':ldap_settings.get('ldap_port'),
180 180 'bind_dn':ldap_settings.get('ldap_dn_user'),
181 181 'bind_pass':ldap_settings.get('ldap_dn_pass'),
182 182 'use_ldaps':str2bool(ldap_settings.get('ldap_ldaps')),
183 183 'ldap_version':3,
184 184 }
185 185 log.debug('Checking for ldap authentication')
186 186 try:
187 187 aldap = AuthLdap(**kwargs)
188 188 res = aldap.authenticate_ldap(username, password)
189 189 log.debug('Got ldap response %s', res)
190 190
191 191 if user_model.create_ldap(username, password):
192 192 log.info('created new ldap user')
193 193
194 194 return True
195 195 except (LdapUsernameError, LdapPasswordError,):
196 196 pass
197 197 except (Exception,):
198 198 log.error(traceback.format_exc())
199 199 pass
200 200 return False
201 201
202 202 class AuthUser(object):
203 203 """
204 204 A simple object that handles a mercurial username for authentication
205 205 """
206 206 def __init__(self):
207 207 self.username = 'None'
208 208 self.name = ''
209 209 self.lastname = ''
210 210 self.email = ''
211 211 self.user_id = None
212 212 self.is_authenticated = False
213 213 self.is_admin = False
214 214 self.permissions = {}
215 215
216 216 def __repr__(self):
217 217 return "<AuthUser('id:%s:%s')>" % (self.user_id, self.username)
218 218
219 219 def set_available_permissions(config):
220 220 """
221 221 This function will propagate pylons globals with all available defined
222 222 permission given in db. We don't wannt to check each time from db for new
223 223 permissions since adding a new permission also requires application restart
224 224 ie. to decorate new views with the newly created permission
225 225 :param config:
226 226 """
227 227 log.info('getting information about all available permissions')
228 228 try:
229 229 sa = meta.Session()
230 230 all_perms = sa.query(Permission).all()
231 231 except:
232 232 pass
233 233 finally:
234 234 meta.Session.remove()
235 235
236 236 config['available_permissions'] = [x.permission_name for x in all_perms]
237 237
238 238 def set_base_path(config):
239 239 config['base_path'] = config['pylons.app_globals'].base_path
240 240
241 241
242 242 def fill_perms(user):
243 243 """
244 244 Fills user permission attribute with permissions taken from database
245 245 :param user:
246 246 """
247 247
248 248 sa = meta.Session()
249 249 user.permissions['repositories'] = {}
250 250 user.permissions['global'] = set()
251 251
252 252 #===========================================================================
253 253 # fetch default permissions
254 254 #===========================================================================
255 255 default_user = UserModel().get_by_username('default', cache=True)
256 256
257 257 default_perms = sa.query(RepoToPerm, Repository, Permission)\
258 258 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
259 259 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
260 260 .filter(RepoToPerm.user == default_user).all()
261 261
262 262 if user.is_admin:
263 263 #=======================================================================
264 264 # #admin have all default rights set to admin
265 265 #=======================================================================
266 266 user.permissions['global'].add('hg.admin')
267 267
268 268 for perm in default_perms:
269 269 p = 'repository.admin'
270 270 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
271 271
272 272 else:
273 273 #=======================================================================
274 274 # set default permissions
275 275 #=======================================================================
276 276
277 277 #default global
278 278 default_global_perms = sa.query(UserToPerm)\
279 279 .filter(UserToPerm.user == sa.query(User)\
280 280 .filter(User.username == 'default').one())
281 281
282 282 for perm in default_global_perms:
283 283 user.permissions['global'].add(perm.permission.permission_name)
284 284
285 285 #default repositories
286 286 for perm in default_perms:
287 287 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
288 288 #disable defaults for private repos,
289 289 p = 'repository.none'
290 290 elif perm.Repository.user_id == user.user_id:
291 291 #set admin if owner
292 292 p = 'repository.admin'
293 293 else:
294 294 p = perm.Permission.permission_name
295 295
296 296 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
297 297
298 298 #=======================================================================
299 299 # #overwrite default with user permissions if any
300 300 #=======================================================================
301 301 user_perms = sa.query(RepoToPerm, Permission, Repository)\
302 302 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
303 303 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
304 304 .filter(RepoToPerm.user_id == user.user_id).all()
305 305
306 306 for perm in user_perms:
307 307 if perm.Repository.user_id == user.user_id:#set admin if owner
308 308 p = 'repository.admin'
309 309 else:
310 310 p = perm.Permission.permission_name
311 311 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
312 312 meta.Session.remove()
313 313 return user
314 314
315 315 def get_user(session):
316 316 """
317 317 Gets user from session, and wraps permissions into user
318 318 :param session:
319 319 """
320 320 user = session.get('rhodecode_user', AuthUser())
321 321 #if the user is not logged in we check for anonymous access
322 322 #if user is logged and it's a default user check if we still have anonymous
323 323 #access enabled
324 324 if user.user_id is None or user.username == 'default':
325 325 anonymous_user = UserModel().get_by_username('default', cache=True)
326 326 if anonymous_user.active is True:
327 327 #then we set this user is logged in
328 328 user.is_authenticated = True
329 329 user.user_id = anonymous_user.user_id
330 330 else:
331 331 user.is_authenticated = False
332 332
333 333 if user.is_authenticated:
334 334 user = UserModel().fill_data(user)
335 335
336 336 user = fill_perms(user)
337 337 session['rhodecode_user'] = user
338 338 session.save()
339 339 return user
340 340
341 341 #===============================================================================
342 342 # CHECK DECORATORS
343 343 #===============================================================================
344 344 class LoginRequired(object):
345 345 """Must be logged in to execute this function else
346 346 redirect to login page"""
347 347
348 348 def __call__(self, func):
349 349 return decorator(self.__wrapper, func)
350 350
351 351 def __wrapper(self, func, *fargs, **fkwargs):
352 352 user = session.get('rhodecode_user', AuthUser())
353 353 log.debug('Checking login required for user:%s', user.username)
354 354 if user.is_authenticated:
355 355 log.debug('user %s is authenticated', user.username)
356 356 return func(*fargs, **fkwargs)
357 357 else:
358 358 log.warn('user %s not authenticated', user.username)
359 359
360 360 p = ''
361 361 if request.environ.get('SCRIPT_NAME') != '/':
362 362 p += request.environ.get('SCRIPT_NAME')
363 363
364 364 p += request.environ.get('PATH_INFO')
365 365 if request.environ.get('QUERY_STRING'):
366 366 p += '?' + request.environ.get('QUERY_STRING')
367 367
368 368 log.debug('redirecting to login page with %s', p)
369 369 return redirect(url('login_home', came_from=p))
370 370
371 371 class NotAnonymous(object):
372 372 """Must be logged in to execute this function else
373 373 redirect to login page"""
374 374
375 375 def __call__(self, func):
376 376 return decorator(self.__wrapper, func)
377 377
378 378 def __wrapper(self, func, *fargs, **fkwargs):
379 379 user = session.get('rhodecode_user', AuthUser())
380 380 log.debug('Checking if user is not anonymous')
381 381
382 382 anonymous = user.username == 'default'
383 383
384 384 if anonymous:
385 385 p = ''
386 386 if request.environ.get('SCRIPT_NAME') != '/':
387 387 p += request.environ.get('SCRIPT_NAME')
388 388
389 389 p += request.environ.get('PATH_INFO')
390 390 if request.environ.get('QUERY_STRING'):
391 391 p += '?' + request.environ.get('QUERY_STRING')
392 392 return redirect(url('login_home', came_from=p))
393 393 else:
394 394 return func(*fargs, **fkwargs)
395 395
396 396 class PermsDecorator(object):
397 397 """Base class for decorators"""
398 398
399 399 def __init__(self, *required_perms):
400 400 available_perms = config['available_permissions']
401 401 for perm in required_perms:
402 402 if perm not in available_perms:
403 403 raise Exception("'%s' permission is not defined" % perm)
404 404 self.required_perms = set(required_perms)
405 405 self.user_perms = None
406 406
407 407 def __call__(self, func):
408 408 return decorator(self.__wrapper, func)
409 409
410 410
411 411 def __wrapper(self, func, *fargs, **fkwargs):
412 412 # _wrapper.__name__ = func.__name__
413 413 # _wrapper.__dict__.update(func.__dict__)
414 414 # _wrapper.__doc__ = func.__doc__
415 415 self.user = session.get('rhodecode_user', AuthUser())
416 416 self.user_perms = self.user.permissions
417 417 log.debug('checking %s permissions %s for %s %s',
418 418 self.__class__.__name__, self.required_perms, func.__name__,
419 419 self.user)
420 420
421 421 if self.check_permissions():
422 422 log.debug('Permission granted for %s %s', func.__name__, self.user)
423 423
424 424 return func(*fargs, **fkwargs)
425 425
426 426 else:
427 427 log.warning('Permission denied for %s %s', func.__name__, self.user)
428 428 #redirect with forbidden ret code
429 429 return abort(403)
430 430
431 431
432 432
433 433 def check_permissions(self):
434 434 """Dummy function for overriding"""
435 435 raise Exception('You have to write this function in child class')
436 436
437 437 class HasPermissionAllDecorator(PermsDecorator):
438 438 """Checks for access permission for all given predicates. All of them
439 439 have to be meet in order to fulfill the request
440 440 """
441 441
442 442 def check_permissions(self):
443 443 if self.required_perms.issubset(self.user_perms.get('global')):
444 444 return True
445 445 return False
446 446
447 447
448 448 class HasPermissionAnyDecorator(PermsDecorator):
449 449 """Checks for access permission for any of given predicates. In order to
450 450 fulfill the request any of predicates must be meet
451 451 """
452 452
453 453 def check_permissions(self):
454 454 if self.required_perms.intersection(self.user_perms.get('global')):
455 455 return True
456 456 return False
457 457
458 458 class HasRepoPermissionAllDecorator(PermsDecorator):
459 459 """Checks for access permission for all given predicates for specific
460 460 repository. All of them have to be meet in order to fulfill the request
461 461 """
462 462
463 463 def check_permissions(self):
464 464 repo_name = get_repo_slug(request)
465 465 try:
466 466 user_perms = set([self.user_perms['repositories'][repo_name]])
467 467 except KeyError:
468 468 return False
469 469 if self.required_perms.issubset(user_perms):
470 470 return True
471 471 return False
472 472
473 473
474 474 class HasRepoPermissionAnyDecorator(PermsDecorator):
475 475 """Checks for access permission for any of given predicates for specific
476 476 repository. In order to fulfill the request any of predicates must be meet
477 477 """
478 478
479 479 def check_permissions(self):
480 480 repo_name = get_repo_slug(request)
481 481
482 482 try:
483 483 user_perms = set([self.user_perms['repositories'][repo_name]])
484 484 except KeyError:
485 485 return False
486 486 if self.required_perms.intersection(user_perms):
487 487 return True
488 488 return False
489 489 #===============================================================================
490 490 # CHECK FUNCTIONS
491 491 #===============================================================================
492 492
493 493 class PermsFunction(object):
494 494 """Base function for other check functions"""
495 495
496 496 def __init__(self, *perms):
497 497 available_perms = config['available_permissions']
498 498
499 499 for perm in perms:
500 500 if perm not in available_perms:
501 501 raise Exception("'%s' permission in not defined" % perm)
502 502 self.required_perms = set(perms)
503 503 self.user_perms = None
504 504 self.granted_for = ''
505 505 self.repo_name = None
506 506
507 507 def __call__(self, check_Location=''):
508 508 user = session.get('rhodecode_user', False)
509 509 if not user:
510 510 return False
511 511 self.user_perms = user.permissions
512 512 self.granted_for = user.username
513 513 log.debug('checking %s %s %s', self.__class__.__name__,
514 514 self.required_perms, user)
515 515
516 516 if self.check_permissions():
517 517 log.debug('Permission granted for %s @ %s %s', self.granted_for,
518 518 check_Location, user)
519 519 return True
520 520
521 521 else:
522 522 log.warning('Permission denied for %s @ %s %s', self.granted_for,
523 523 check_Location, user)
524 524 return False
525 525
526 526 def check_permissions(self):
527 527 """Dummy function for overriding"""
528 528 raise Exception('You have to write this function in child class')
529 529
530 530 class HasPermissionAll(PermsFunction):
531 531 def check_permissions(self):
532 532 if self.required_perms.issubset(self.user_perms.get('global')):
533 533 return True
534 534 return False
535 535
536 536 class HasPermissionAny(PermsFunction):
537 537 def check_permissions(self):
538 538 if self.required_perms.intersection(self.user_perms.get('global')):
539 539 return True
540 540 return False
541 541
542 542 class HasRepoPermissionAll(PermsFunction):
543 543
544 544 def __call__(self, repo_name=None, check_Location=''):
545 545 self.repo_name = repo_name
546 546 return super(HasRepoPermissionAll, self).__call__(check_Location)
547 547
548 548 def check_permissions(self):
549 549 if not self.repo_name:
550 550 self.repo_name = get_repo_slug(request)
551 551
552 552 try:
553 553 self.user_perms = set([self.user_perms['repositories']\
554 554 [self.repo_name]])
555 555 except KeyError:
556 556 return False
557 557 self.granted_for = self.repo_name
558 558 if self.required_perms.issubset(self.user_perms):
559 559 return True
560 560 return False
561 561
562 562 class HasRepoPermissionAny(PermsFunction):
563 563
564 564 def __call__(self, repo_name=None, check_Location=''):
565 565 self.repo_name = repo_name
566 566 return super(HasRepoPermissionAny, self).__call__(check_Location)
567 567
568 568 def check_permissions(self):
569 569 if not self.repo_name:
570 570 self.repo_name = get_repo_slug(request)
571 571
572 572 try:
573 573 self.user_perms = set([self.user_perms['repositories']\
574 574 [self.repo_name]])
575 575 except KeyError:
576 576 return False
577 577 self.granted_for = self.repo_name
578 578 if self.required_perms.intersection(self.user_perms):
579 579 return True
580 580 return False
581 581
582 582 #===============================================================================
583 583 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
584 584 #===============================================================================
585 585
586 586 class HasPermissionAnyMiddleware(object):
587 587 def __init__(self, *perms):
588 588 self.required_perms = set(perms)
589 589
590 590 def __call__(self, user, repo_name):
591 591 usr = AuthUser()
592 592 usr.user_id = user.user_id
593 593 usr.username = user.username
594 594 usr.is_admin = user.admin
595 595
596 596 try:
597 597 self.user_perms = set([fill_perms(usr)\
598 598 .permissions['repositories'][repo_name]])
599 599 except:
600 600 self.user_perms = set()
601 601 self.granted_for = ''
602 602 self.username = user.username
603 603 self.repo_name = repo_name
604 604 return self.check_permissions()
605 605
606 606 def check_permissions(self):
607 607 log.debug('checking mercurial protocol '
608 608 'permissions for user:%s repository:%s',
609 609 self.username, self.repo_name)
610 610 if self.required_perms.intersection(self.user_perms):
611 611 log.debug('permission granted')
612 612 return True
613 613 log.debug('permission denied')
614 614 return False
General Comments 0
You need to be logged in to leave comments. Login now