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