##// END OF EJS Templates
Added session wrapper, for rc 1.2.X compatibility. Adds backwards compatability...
marcink -
r2030:61f9aeb2 beta
parent child Browse files
Show More
@@ -1,785 +1,806 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 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import random
27 27 import logging
28 28 import traceback
29 29 import hashlib
30 30
31 31 from tempfile import _RandomNameSequence
32 32 from decorator import decorator
33 33
34 34 from pylons import config, url, request
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
39 39 from rhodecode.model.meta import Session
40 40
41 41 if __platform__ in PLATFORM_WIN:
42 42 from hashlib import sha256
43 43 if __platform__ in PLATFORM_OTHERS:
44 44 import bcrypt
45 45
46 46 from rhodecode.lib import str2bool, safe_unicode
47 47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
48 48 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
49 49 from rhodecode.lib.auth_ldap import AuthLdap
50 50
51 51 from rhodecode.model import meta
52 52 from rhodecode.model.user import UserModel
53 53 from rhodecode.model.db import Permission, RhodeCodeSetting, User
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class PasswordGenerator(object):
59 59 """
60 60 This is a simple class for generating password from different sets of
61 61 characters
62 62 usage::
63 63
64 64 passwd_gen = PasswordGenerator()
65 65 #print 8-letter password containing only big and small letters
66 66 of alphabet
67 67 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
68 68 """
69 69 ALPHABETS_NUM = r'''1234567890'''
70 70 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
71 71 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
72 72 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
73 73 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
74 74 + ALPHABETS_NUM + ALPHABETS_SPECIAL
75 75 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
76 76 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
77 77 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
78 78 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
79 79
80 80 def __init__(self, passwd=''):
81 81 self.passwd = passwd
82 82
83 83 def gen_password(self, length, type_=None):
84 84 if type_ is None:
85 85 type_ = self.ALPHABETS_FULL
86 86 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
87 87 return self.passwd
88 88
89 89
90 90 class RhodeCodeCrypto(object):
91 91
92 92 @classmethod
93 93 def hash_string(cls, str_):
94 94 """
95 95 Cryptographic function used for password hashing based on pybcrypt
96 96 or pycrypto in windows
97 97
98 98 :param password: password to hash
99 99 """
100 100 if __platform__ in PLATFORM_WIN:
101 101 return sha256(str_).hexdigest()
102 102 elif __platform__ in PLATFORM_OTHERS:
103 103 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
104 104 else:
105 105 raise Exception('Unknown or unsupported platform %s' \
106 106 % __platform__)
107 107
108 108 @classmethod
109 109 def hash_check(cls, password, hashed):
110 110 """
111 111 Checks matching password with it's hashed value, runs different
112 112 implementation based on platform it runs on
113 113
114 114 :param password: password
115 115 :param hashed: password in hashed form
116 116 """
117 117
118 118 if __platform__ in PLATFORM_WIN:
119 119 return sha256(password).hexdigest() == hashed
120 120 elif __platform__ in PLATFORM_OTHERS:
121 121 return bcrypt.hashpw(password, hashed) == hashed
122 122 else:
123 123 raise Exception('Unknown or unsupported platform %s' \
124 124 % __platform__)
125 125
126 126
127 127 def get_crypt_password(password):
128 128 return RhodeCodeCrypto.hash_string(password)
129 129
130 130
131 131 def check_password(password, hashed):
132 132 return RhodeCodeCrypto.hash_check(password, hashed)
133 133
134 134
135 135 def generate_api_key(str_, salt=None):
136 136 """
137 137 Generates API KEY from given string
138 138
139 139 :param str_:
140 140 :param salt:
141 141 """
142 142
143 143 if salt is None:
144 144 salt = _RandomNameSequence().next()
145 145
146 146 return hashlib.sha1(str_ + salt).hexdigest()
147 147
148 148
149 149 def authfunc(environ, username, password):
150 150 """
151 151 Dummy authentication wrapper function used in Mercurial and Git for
152 152 access control.
153 153
154 154 :param environ: needed only for using in Basic auth
155 155 """
156 156 return authenticate(username, password)
157 157
158 158
159 159 def authenticate(username, password):
160 160 """
161 161 Authentication function used for access control,
162 162 firstly checks for db authentication then if ldap is enabled for ldap
163 163 authentication, also creates ldap user if not in database
164 164
165 165 :param username: username
166 166 :param password: password
167 167 """
168 168
169 169 user_model = UserModel()
170 170 user = User.get_by_username(username)
171 171
172 172 log.debug('Authenticating user using RhodeCode account')
173 173 if user is not None and not user.ldap_dn:
174 174 if user.active:
175 175 if user.username == 'default' and user.active:
176 176 log.info('user %s authenticated correctly as anonymous user' %
177 177 username)
178 178 return True
179 179
180 180 elif user.username == username and check_password(password,
181 181 user.password):
182 182 log.info('user %s authenticated correctly' % username)
183 183 return True
184 184 else:
185 185 log.warning('user %s tried auth but is disabled' % username)
186 186
187 187 else:
188 188 log.debug('Regular authentication failed')
189 189 user_obj = User.get_by_username(username, case_insensitive=True)
190 190
191 191 if user_obj is not None and not user_obj.ldap_dn:
192 192 log.debug('this user already exists as non ldap')
193 193 return False
194 194
195 195 ldap_settings = RhodeCodeSetting.get_ldap_settings()
196 196 #======================================================================
197 197 # FALLBACK TO LDAP AUTH IF ENABLE
198 198 #======================================================================
199 199 if str2bool(ldap_settings.get('ldap_active')):
200 200 log.debug("Authenticating user using ldap")
201 201 kwargs = {
202 202 'server': ldap_settings.get('ldap_host', ''),
203 203 'base_dn': ldap_settings.get('ldap_base_dn', ''),
204 204 'port': ldap_settings.get('ldap_port'),
205 205 'bind_dn': ldap_settings.get('ldap_dn_user'),
206 206 'bind_pass': ldap_settings.get('ldap_dn_pass'),
207 207 'tls_kind': ldap_settings.get('ldap_tls_kind'),
208 208 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
209 209 'ldap_filter': ldap_settings.get('ldap_filter'),
210 210 'search_scope': ldap_settings.get('ldap_search_scope'),
211 211 'attr_login': ldap_settings.get('ldap_attr_login'),
212 212 'ldap_version': 3,
213 213 }
214 214 log.debug('Checking for ldap authentication')
215 215 try:
216 216 aldap = AuthLdap(**kwargs)
217 217 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
218 218 password)
219 219 log.debug('Got ldap DN response %s' % user_dn)
220 220
221 221 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
222 222 .get(k), [''])[0]
223 223
224 224 user_attrs = {
225 225 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
226 226 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
227 227 'email': get_ldap_attr('ldap_attr_email'),
228 228 }
229 229
230 230 # don't store LDAP password since we don't need it. Override
231 231 # with some random generated password
232 232 _password = PasswordGenerator().gen_password(length=8)
233 233 # create this user on the fly if it doesn't exist in rhodecode
234 234 # database
235 235 if user_model.create_ldap(username, _password, user_dn,
236 236 user_attrs):
237 237 log.info('created new ldap user %s' % username)
238 238
239 239 Session.commit()
240 240 return True
241 241 except (LdapUsernameError, LdapPasswordError,):
242 242 pass
243 243 except (Exception,):
244 244 log.error(traceback.format_exc())
245 245 pass
246 246 return False
247 247
248 248
249 249 def login_container_auth(username):
250 250 user = User.get_by_username(username)
251 251 if user is None:
252 252 user_attrs = {
253 253 'name': username,
254 254 'lastname': None,
255 255 'email': None,
256 256 }
257 257 user = UserModel().create_for_container_auth(username, user_attrs)
258 258 if not user:
259 259 return None
260 260 log.info('User %s was created by container authentication' % username)
261 261
262 262 if not user.active:
263 263 return None
264 264
265 265 user.update_lastlogin()
266 266 Session.commit()
267 267
268 268 log.debug('User %s is now logged in by container authentication',
269 269 user.username)
270 270 return user
271 271
272 272
273 273 def get_container_username(environ, config):
274 274 username = None
275 275
276 276 if str2bool(config.get('container_auth_enabled', False)):
277 277 from paste.httpheaders import REMOTE_USER
278 278 username = REMOTE_USER(environ)
279 279
280 280 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
281 281 username = environ.get('HTTP_X_FORWARDED_USER')
282 282
283 283 if username:
284 284 # Removing realm and domain from username
285 285 username = username.partition('@')[0]
286 286 username = username.rpartition('\\')[2]
287 287 log.debug('Received username %s from container' % username)
288 288
289 289 return username
290 290
291 291
292 class CookieStoreWrapper(object):
293
294 def __init__(self, cookie_store):
295 self.cookie_store = cookie_store
296
297 def __repr__(self):
298 return 'CookieStore<%s>' % (self.cookie_store)
299
300 def get(self, key, other=None):
301 if isinstance(self.cookie_store, dict):
302 return self.cookie_store.get(key, other)
303 elif isinstance(self.cookie_store, AuthUser):
304 return self.cookie_store.__dict__.get(key, other)
305
306
292 307 class AuthUser(object):
293 308 """
294 309 A simple object that handles all attributes of user in RhodeCode
295 310
296 311 It does lookup based on API key,given user, or user present in session
297 312 Then it fills all required information for such user. It also checks if
298 313 anonymous access is enabled and if so, it returns default user as logged
299 314 in
300 315 """
301 316
302 317 def __init__(self, user_id=None, api_key=None, username=None):
303 318
304 319 self.user_id = user_id
305 320 self.api_key = None
306 321 self.username = username
307 322
308 323 self.name = ''
309 324 self.lastname = ''
310 325 self.email = ''
311 326 self.is_authenticated = False
312 327 self.admin = False
313 328 self.permissions = {}
314 329 self._api_key = api_key
315 330 self.propagate_data()
316 331 self._instance = None
317 332
318 333 def propagate_data(self):
319 334 user_model = UserModel()
320 335 self.anonymous_user = User.get_by_username('default', cache=True)
321 336 is_user_loaded = False
322 337
323 338 # try go get user by api key
324 339 if self._api_key and self._api_key != self.anonymous_user.api_key:
325 340 log.debug('Auth User lookup by API KEY %s' % self._api_key)
326 341 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
327 342 # lookup by userid
328 343 elif (self.user_id is not None and
329 344 self.user_id != self.anonymous_user.user_id):
330 345 log.debug('Auth User lookup by USER ID %s' % self.user_id)
331 346 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
332 347 # lookup by username
333 348 elif self.username and \
334 349 str2bool(config.get('container_auth_enabled', False)):
335 350
336 351 log.debug('Auth User lookup by USER NAME %s' % self.username)
337 352 dbuser = login_container_auth(self.username)
338 353 if dbuser is not None:
339 354 for k, v in dbuser.get_dict().items():
340 355 setattr(self, k, v)
341 356 self.set_authenticated()
342 357 is_user_loaded = True
343 358
344 359 if not is_user_loaded:
345 360 # if we cannot authenticate user try anonymous
346 361 if self.anonymous_user.active is True:
347 362 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
348 363 # then we set this user is logged in
349 364 self.is_authenticated = True
350 365 else:
351 366 self.user_id = None
352 367 self.username = None
353 368 self.is_authenticated = False
354 369
355 370 if not self.username:
356 371 self.username = 'None'
357 372
358 373 log.debug('Auth User is now %s' % self)
359 374 user_model.fill_perms(self)
360 375
361 376 @property
362 377 def is_admin(self):
363 378 return self.admin
364 379
365 380 def __repr__(self):
366 381 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
367 382 self.is_authenticated)
368 383
369 384 def set_authenticated(self, authenticated=True):
370 385 if self.user_id != self.anonymous_user.user_id:
371 386 self.is_authenticated = authenticated
372 387
373 388 def get_cookie_store(self):
374 389 return {'username': self.username,
375 390 'user_id': self.user_id,
376 391 'is_authenticated': self.is_authenticated}
377 392
378 393 @classmethod
379 394 def from_cookie_store(cls, cookie_store):
395 """
396 Creates AuthUser from a cookie store
397
398 :param cls:
399 :param cookie_store:
400 """
380 401 user_id = cookie_store.get('user_id')
381 402 username = cookie_store.get('username')
382 403 api_key = cookie_store.get('api_key')
383 404 return AuthUser(user_id, api_key, username)
384 405
385 406
386 407 def set_available_permissions(config):
387 408 """
388 409 This function will propagate pylons globals with all available defined
389 410 permission given in db. We don't want to check each time from db for new
390 411 permissions since adding a new permission also requires application restart
391 412 ie. to decorate new views with the newly created permission
392 413
393 414 :param config: current pylons config instance
394 415
395 416 """
396 417 log.info('getting information about all available permissions')
397 418 try:
398 419 sa = meta.Session
399 420 all_perms = sa.query(Permission).all()
400 421 except Exception:
401 422 pass
402 423 finally:
403 424 meta.Session.remove()
404 425
405 426 config['available_permissions'] = [x.permission_name for x in all_perms]
406 427
407 428
408 429 #==============================================================================
409 430 # CHECK DECORATORS
410 431 #==============================================================================
411 432 class LoginRequired(object):
412 433 """
413 434 Must be logged in to execute this function else
414 435 redirect to login page
415 436
416 437 :param api_access: if enabled this checks only for valid auth token
417 438 and grants access based on valid token
418 439 """
419 440
420 441 def __init__(self, api_access=False):
421 442 self.api_access = api_access
422 443
423 444 def __call__(self, func):
424 445 return decorator(self.__wrapper, func)
425 446
426 447 def __wrapper(self, func, *fargs, **fkwargs):
427 448 cls = fargs[0]
428 449 user = cls.rhodecode_user
429 450
430 451 api_access_ok = False
431 452 if self.api_access:
432 453 log.debug('Checking API KEY access for %s' % cls)
433 454 if user.api_key == request.GET.get('api_key'):
434 455 api_access_ok = True
435 456 else:
436 457 log.debug("API KEY token not valid")
437 458 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
438 459 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
439 460 if user.is_authenticated or api_access_ok:
440 461 log.info('user %s is authenticated and granted access to %s' % (
441 462 user.username, loc)
442 463 )
443 464 return func(*fargs, **fkwargs)
444 465 else:
445 466 log.warn('user %s NOT authenticated on func: %s' % (
446 467 user, loc)
447 468 )
448 469 p = url.current()
449 470
450 471 log.debug('redirecting to login page with %s' % p)
451 472 return redirect(url('login_home', came_from=p))
452 473
453 474
454 475 class NotAnonymous(object):
455 476 """
456 477 Must be logged in to execute this function else
457 478 redirect to login page"""
458 479
459 480 def __call__(self, func):
460 481 return decorator(self.__wrapper, func)
461 482
462 483 def __wrapper(self, func, *fargs, **fkwargs):
463 484 cls = fargs[0]
464 485 self.user = cls.rhodecode_user
465 486
466 487 log.debug('Checking if user is not anonymous @%s' % cls)
467 488
468 489 anonymous = self.user.username == 'default'
469 490
470 491 if anonymous:
471 492 p = url.current()
472 493
473 494 import rhodecode.lib.helpers as h
474 495 h.flash(_('You need to be a registered user to '
475 496 'perform this action'),
476 497 category='warning')
477 498 return redirect(url('login_home', came_from=p))
478 499 else:
479 500 return func(*fargs, **fkwargs)
480 501
481 502
482 503 class PermsDecorator(object):
483 504 """Base class for controller decorators"""
484 505
485 506 def __init__(self, *required_perms):
486 507 available_perms = config['available_permissions']
487 508 for perm in required_perms:
488 509 if perm not in available_perms:
489 510 raise Exception("'%s' permission is not defined" % perm)
490 511 self.required_perms = set(required_perms)
491 512 self.user_perms = None
492 513
493 514 def __call__(self, func):
494 515 return decorator(self.__wrapper, func)
495 516
496 517 def __wrapper(self, func, *fargs, **fkwargs):
497 518 cls = fargs[0]
498 519 self.user = cls.rhodecode_user
499 520 self.user_perms = self.user.permissions
500 521 log.debug('checking %s permissions %s for %s %s',
501 522 self.__class__.__name__, self.required_perms, cls,
502 523 self.user)
503 524
504 525 if self.check_permissions():
505 526 log.debug('Permission granted for %s %s' % (cls, self.user))
506 527 return func(*fargs, **fkwargs)
507 528
508 529 else:
509 530 log.debug('Permission denied for %s %s' % (cls, self.user))
510 531 anonymous = self.user.username == 'default'
511 532
512 533 if anonymous:
513 534 p = url.current()
514 535
515 536 import rhodecode.lib.helpers as h
516 537 h.flash(_('You need to be a signed in to '
517 538 'view this page'),
518 539 category='warning')
519 540 return redirect(url('login_home', came_from=p))
520 541
521 542 else:
522 543 # redirect with forbidden ret code
523 544 return abort(403)
524 545
525 546 def check_permissions(self):
526 547 """Dummy function for overriding"""
527 548 raise Exception('You have to write this function in child class')
528 549
529 550
530 551 class HasPermissionAllDecorator(PermsDecorator):
531 552 """
532 553 Checks for access permission for all given predicates. All of them
533 554 have to be meet in order to fulfill the request
534 555 """
535 556
536 557 def check_permissions(self):
537 558 if self.required_perms.issubset(self.user_perms.get('global')):
538 559 return True
539 560 return False
540 561
541 562
542 563 class HasPermissionAnyDecorator(PermsDecorator):
543 564 """
544 565 Checks for access permission for any of given predicates. In order to
545 566 fulfill the request any of predicates must be meet
546 567 """
547 568
548 569 def check_permissions(self):
549 570 if self.required_perms.intersection(self.user_perms.get('global')):
550 571 return True
551 572 return False
552 573
553 574
554 575 class HasRepoPermissionAllDecorator(PermsDecorator):
555 576 """
556 577 Checks for access permission for all given predicates for specific
557 578 repository. All of them have to be meet in order to fulfill the request
558 579 """
559 580
560 581 def check_permissions(self):
561 582 repo_name = get_repo_slug(request)
562 583 try:
563 584 user_perms = set([self.user_perms['repositories'][repo_name]])
564 585 except KeyError:
565 586 return False
566 587 if self.required_perms.issubset(user_perms):
567 588 return True
568 589 return False
569 590
570 591
571 592 class HasRepoPermissionAnyDecorator(PermsDecorator):
572 593 """
573 594 Checks for access permission for any of given predicates for specific
574 595 repository. In order to fulfill the request any of predicates must be meet
575 596 """
576 597
577 598 def check_permissions(self):
578 599 repo_name = get_repo_slug(request)
579 600
580 601 try:
581 602 user_perms = set([self.user_perms['repositories'][repo_name]])
582 603 except KeyError:
583 604 return False
584 605 if self.required_perms.intersection(user_perms):
585 606 return True
586 607 return False
587 608
588 609
589 610 class HasReposGroupPermissionAllDecorator(PermsDecorator):
590 611 """
591 612 Checks for access permission for all given predicates for specific
592 613 repository. All of them have to be meet in order to fulfill the request
593 614 """
594 615
595 616 def check_permissions(self):
596 617 group_name = get_repos_group_slug(request)
597 618 try:
598 619 user_perms = set([self.user_perms['repositories_groups'][group_name]])
599 620 except KeyError:
600 621 return False
601 622 if self.required_perms.issubset(user_perms):
602 623 return True
603 624 return False
604 625
605 626
606 627 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
607 628 """
608 629 Checks for access permission for any of given predicates for specific
609 630 repository. In order to fulfill the request any of predicates must be meet
610 631 """
611 632
612 633 def check_permissions(self):
613 634 group_name = get_repos_group_slug(request)
614 635
615 636 try:
616 637 user_perms = set([self.user_perms['repositories_groups'][group_name]])
617 638 except KeyError:
618 639 return False
619 640 if self.required_perms.intersection(user_perms):
620 641 return True
621 642 return False
622 643
623 644
624 645 #==============================================================================
625 646 # CHECK FUNCTIONS
626 647 #==============================================================================
627 648 class PermsFunction(object):
628 649 """Base function for other check functions"""
629 650
630 651 def __init__(self, *perms):
631 652 available_perms = config['available_permissions']
632 653
633 654 for perm in perms:
634 655 if perm not in available_perms:
635 656 raise Exception("'%s' permission in not defined" % perm)
636 657 self.required_perms = set(perms)
637 658 self.user_perms = None
638 659 self.granted_for = ''
639 660 self.repo_name = None
640 661
641 662 def __call__(self, check_Location=''):
642 663 user = request.user
643 664 if not user:
644 665 return False
645 666 self.user_perms = user.permissions
646 667 self.granted_for = user
647 668 log.debug('checking %s %s %s', self.__class__.__name__,
648 669 self.required_perms, user)
649 670
650 671 if self.check_permissions():
651 672 log.debug('Permission granted %s @ %s', self.granted_for,
652 673 check_Location or 'unspecified location')
653 674 return True
654 675
655 676 else:
656 677 log.debug('Permission denied for %s @ %s', self.granted_for,
657 678 check_Location or 'unspecified location')
658 679 return False
659 680
660 681 def check_permissions(self):
661 682 """Dummy function for overriding"""
662 683 raise Exception('You have to write this function in child class')
663 684
664 685
665 686 class HasPermissionAll(PermsFunction):
666 687 def check_permissions(self):
667 688 if self.required_perms.issubset(self.user_perms.get('global')):
668 689 return True
669 690 return False
670 691
671 692
672 693 class HasPermissionAny(PermsFunction):
673 694 def check_permissions(self):
674 695 if self.required_perms.intersection(self.user_perms.get('global')):
675 696 return True
676 697 return False
677 698
678 699
679 700 class HasRepoPermissionAll(PermsFunction):
680 701
681 702 def __call__(self, repo_name=None, check_Location=''):
682 703 self.repo_name = repo_name
683 704 return super(HasRepoPermissionAll, self).__call__(check_Location)
684 705
685 706 def check_permissions(self):
686 707 if not self.repo_name:
687 708 self.repo_name = get_repo_slug(request)
688 709
689 710 try:
690 711 self.user_perms = set(
691 712 [self.user_perms['repositories'][self.repo_name]]
692 713 )
693 714 except KeyError:
694 715 return False
695 716 self.granted_for = self.repo_name
696 717 if self.required_perms.issubset(self.user_perms):
697 718 return True
698 719 return False
699 720
700 721
701 722 class HasRepoPermissionAny(PermsFunction):
702 723
703 724 def __call__(self, repo_name=None, check_Location=''):
704 725 self.repo_name = repo_name
705 726 return super(HasRepoPermissionAny, self).__call__(check_Location)
706 727
707 728 def check_permissions(self):
708 729 if not self.repo_name:
709 730 self.repo_name = get_repo_slug(request)
710 731
711 732 try:
712 733 self.user_perms = set(
713 734 [self.user_perms['repositories'][self.repo_name]]
714 735 )
715 736 except KeyError:
716 737 return False
717 738 self.granted_for = self.repo_name
718 739 if self.required_perms.intersection(self.user_perms):
719 740 return True
720 741 return False
721 742
722 743
723 744 class HasReposGroupPermissionAny(PermsFunction):
724 745 def __call__(self, group_name=None, check_Location=''):
725 746 self.group_name = group_name
726 747 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
727 748
728 749 def check_permissions(self):
729 750 try:
730 751 self.user_perms = set(
731 752 [self.user_perms['repositories_groups'][self.group_name]]
732 753 )
733 754 except KeyError:
734 755 return False
735 756 self.granted_for = self.repo_name
736 757 if self.required_perms.intersection(self.user_perms):
737 758 return True
738 759 return False
739 760
740 761
741 762 class HasReposGroupPermissionAll(PermsFunction):
742 763 def __call__(self, group_name=None, check_Location=''):
743 764 self.group_name = group_name
744 765 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
745 766
746 767 def check_permissions(self):
747 768 try:
748 769 self.user_perms = set(
749 770 [self.user_perms['repositories_groups'][self.group_name]]
750 771 )
751 772 except KeyError:
752 773 return False
753 774 self.granted_for = self.repo_name
754 775 if self.required_perms.issubset(self.user_perms):
755 776 return True
756 777 return False
757 778
758 779
759 780 #==============================================================================
760 781 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
761 782 #==============================================================================
762 783 class HasPermissionAnyMiddleware(object):
763 784 def __init__(self, *perms):
764 785 self.required_perms = set(perms)
765 786
766 787 def __call__(self, user, repo_name):
767 788 usr = AuthUser(user.user_id)
768 789 try:
769 790 self.user_perms = set([usr.permissions['repositories'][repo_name]])
770 791 except:
771 792 self.user_perms = set()
772 793 self.granted_for = ''
773 794 self.username = user.username
774 795 self.repo_name = repo_name
775 796 return self.check_permissions()
776 797
777 798 def check_permissions(self):
778 799 log.debug('checking mercurial protocol '
779 800 'permissions %s for user:%s repository:%s', self.user_perms,
780 801 self.username, self.repo_name)
781 802 if self.required_perms.intersection(self.user_perms):
782 803 log.debug('permission granted')
783 804 return True
784 805 log.debug('permission denied')
785 806 return False
@@ -1,186 +1,184 b''
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 import logging
6 6 import time
7 7 import traceback
8 8
9 9 from paste.auth.basic import AuthBasicAuthenticator
10 10
11 11 from pylons import config, tmpl_context as c, request, session, url
12 12 from pylons.controllers import WSGIController
13 13 from pylons.controllers.util import redirect
14 14 from pylons.templating import render_mako as render
15 15
16 16 from rhodecode import __version__, BACKENDS
17 17
18 18 from rhodecode.lib import str2bool, safe_unicode
19 19 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
20 HasPermissionAnyMiddleware
20 HasPermissionAnyMiddleware, CookieStoreWrapper
21 21 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
22 22 from rhodecode.model import meta
23 23
24 24 from rhodecode.model.db import Repository
25 25 from rhodecode.model.notification import NotificationModel
26 26 from rhodecode.model.scm import ScmModel
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 class BaseVCSController(object):
32 32
33 33 def __init__(self, application, config):
34 34 self.application = application
35 35 self.config = config
36 36 # base path of repo locations
37 37 self.basepath = self.config['base_path']
38 38 #authenticate this mercurial request using authfunc
39 39 self.authenticate = AuthBasicAuthenticator('', authfunc)
40 40 self.ipaddr = '0.0.0.0'
41 41
42 42 def _handle_request(self, environ, start_response):
43 43 raise NotImplementedError()
44 44
45 45 def _get_by_id(self, repo_name):
46 46 """
47 47 Get's a special pattern _<ID> from clone url and tries to replace it
48 48 with a repository_name for support of _<ID> non changable urls
49 49
50 50 :param repo_name:
51 51 """
52 52 try:
53 53 data = repo_name.split('/')
54 54 if len(data) >= 2:
55 55 by_id = data[1].split('_')
56 56 if len(by_id) == 2 and by_id[1].isdigit():
57 57 _repo_name = Repository.get(by_id[1]).repo_name
58 58 data[1] = _repo_name
59 59 except:
60 60 log.debug('Failed to extract repo_name from id %s' % (
61 61 traceback.format_exc()
62 62 )
63 63 )
64 64
65 65 return '/'.join(data)
66 66
67 67 def _invalidate_cache(self, repo_name):
68 68 """
69 69 Set's cache for this repository for invalidation on next access
70 70
71 71 :param repo_name: full repo name, also a cache key
72 72 """
73 73 invalidate_cache('get_repo_cached_%s' % repo_name)
74 74
75 75 def _check_permission(self, action, user, repo_name):
76 76 """
77 77 Checks permissions using action (push/pull) user and repository
78 78 name
79 79
80 80 :param action: push or pull action
81 81 :param user: user instance
82 82 :param repo_name: repository name
83 83 """
84 84 if action == 'push':
85 85 if not HasPermissionAnyMiddleware('repository.write',
86 86 'repository.admin')(user,
87 87 repo_name):
88 88 return False
89 89
90 90 else:
91 91 #any other action need at least read permission
92 92 if not HasPermissionAnyMiddleware('repository.read',
93 93 'repository.write',
94 94 'repository.admin')(user,
95 95 repo_name):
96 96 return False
97 97
98 98 return True
99 99
100 100 def __call__(self, environ, start_response):
101 101 start = time.time()
102 102 try:
103 103 return self._handle_request(environ, start_response)
104 104 finally:
105 105 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
106 106 log.debug('Request time: %.3fs' % (time.time() - start))
107 107 meta.Session.remove()
108 108
109 109
110 110 class BaseController(WSGIController):
111 111
112 112 def __before__(self):
113 113 c.rhodecode_version = __version__
114 114 c.rhodecode_instanceid = config.get('instance_id')
115 115 c.rhodecode_name = config.get('rhodecode_title')
116 116 c.use_gravatar = str2bool(config.get('use_gravatar'))
117 117 c.ga_code = config.get('rhodecode_ga_code')
118 118 c.repo_name = get_repo_slug(request)
119 119 c.backends = BACKENDS.keys()
120 120 c.unread_notifications = NotificationModel()\
121 121 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
122 122 self.cut_off_limit = int(config.get('cut_off_limit'))
123 123
124 124 self.sa = meta.Session
125 125 self.scm_model = ScmModel(self.sa)
126 126
127 127 def __call__(self, environ, start_response):
128 128 """Invoke the Controller"""
129 129 # WSGIController.__call__ dispatches to the Controller method
130 130 # the request is routed to. This routing information is
131 131 # available in environ['pylons.routes_dict']
132 132 start = time.time()
133 133 try:
134 134 # make sure that we update permissions each time we call controller
135 135 api_key = request.GET.get('api_key')
136 cookie_store = session.get('rhodecode_user') or {}
136 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
137 137 user_id = cookie_store.get('user_id', None)
138 138 username = get_container_username(environ, config)
139 139
140 140 auth_user = AuthUser(user_id, api_key, username)
141 141 request.user = auth_user
142 142 self.rhodecode_user = c.rhodecode_user = auth_user
143 143 if not self.rhodecode_user.is_authenticated and \
144 144 self.rhodecode_user.user_id is not None:
145 self.rhodecode_user\
146 .set_authenticated(cookie_store.get('is_authenticated'))
147
148 session['rhodecode_user'] = self.rhodecode_user.get_cookie_store()
149 session.save()
145 self.rhodecode_user.set_authenticated(
146 cookie_store.get('is_authenticated')
147 )
150 148 log.info('User: %s accessed %s' % (
151 149 auth_user, safe_unicode(environ.get('PATH_INFO')))
152 150 )
153 151 return WSGIController.__call__(self, environ, start_response)
154 152 finally:
155 153 log.info('Request to %s time: %.3fs' % (
156 154 safe_unicode(environ.get('PATH_INFO')), time.time() - start)
157 155 )
158 156 meta.Session.remove()
159 157
160 158
161 159 class BaseRepoController(BaseController):
162 160 """
163 161 Base class for controllers responsible for loading all needed data for
164 162 repository loaded items are
165 163
166 164 c.rhodecode_repo: instance of scm repository
167 165 c.rhodecode_db_repo: instance of db
168 166 c.repository_followers: number of followers
169 167 c.repository_forks: number of forks
170 168 """
171 169
172 170 def __before__(self):
173 171 super(BaseRepoController, self).__before__()
174 172 if c.repo_name:
175 173
176 174 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
177 175 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
178 176
179 177 if c.rhodecode_repo is None:
180 178 log.error('%s this repository is present in database but it '
181 179 'cannot be created as an scm instance', c.repo_name)
182 180
183 181 redirect(url('home'))
184 182
185 183 c.repository_followers = self.scm_model.get_followers(c.repo_name)
186 184 c.repository_forks = self.scm_model.get_forks(c.repo_name)
General Comments 0
You need to be logged in to leave comments. Login now