##// END OF EJS Templates
added more info into __repr__ of auth user for better debugging and logging
marcink -
r3903:ddd05df2 beta
parent child Browse files
Show More
@@ -1,1113 +1,1113 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 from sqlalchemy.orm.exc import ObjectDeletedError
38 38
39 39 from rhodecode import __platform__, is_windows, is_unix
40 40 from rhodecode.model.meta import Session
41 41
42 42 from rhodecode.lib.utils2 import str2bool, safe_unicode, aslist
43 43 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError,\
44 44 LdapImportError
45 45 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug,\
46 46 get_user_group_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, RhodeCodeSetting, User, UserIpMap
52 52 from rhodecode.lib.caching_query import FromCache
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 class PasswordGenerator(object):
58 58 """
59 59 This is a simple class for generating password from different sets of
60 60 characters
61 61 usage::
62 62
63 63 passwd_gen = PasswordGenerator()
64 64 #print 8-letter password containing only big and small letters
65 65 of alphabet
66 66 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
67 67 """
68 68 ALPHABETS_NUM = r'''1234567890'''
69 69 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
70 70 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
71 71 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
72 72 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
73 73 + ALPHABETS_NUM + ALPHABETS_SPECIAL
74 74 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
75 75 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
76 76 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
77 77 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
78 78
79 79 def __init__(self, passwd=''):
80 80 self.passwd = passwd
81 81
82 82 def gen_password(self, length, type_=None):
83 83 if type_ is None:
84 84 type_ = self.ALPHABETS_FULL
85 85 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
86 86 return self.passwd
87 87
88 88
89 89 class RhodeCodeCrypto(object):
90 90
91 91 @classmethod
92 92 def hash_string(cls, str_):
93 93 """
94 94 Cryptographic function used for password hashing based on pybcrypt
95 95 or pycrypto in windows
96 96
97 97 :param password: password to hash
98 98 """
99 99 if is_windows:
100 100 from hashlib import sha256
101 101 return sha256(str_).hexdigest()
102 102 elif is_unix:
103 103 import bcrypt
104 104 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
105 105 else:
106 106 raise Exception('Unknown or unsupported platform %s' \
107 107 % __platform__)
108 108
109 109 @classmethod
110 110 def hash_check(cls, password, hashed):
111 111 """
112 112 Checks matching password with it's hashed value, runs different
113 113 implementation based on platform it runs on
114 114
115 115 :param password: password
116 116 :param hashed: password in hashed form
117 117 """
118 118
119 119 if is_windows:
120 120 from hashlib import sha256
121 121 return sha256(password).hexdigest() == hashed
122 122 elif is_unix:
123 123 import bcrypt
124 124 return bcrypt.hashpw(password, hashed) == hashed
125 125 else:
126 126 raise Exception('Unknown or unsupported platform %s' \
127 127 % __platform__)
128 128
129 129
130 130 def get_crypt_password(password):
131 131 return RhodeCodeCrypto.hash_string(password)
132 132
133 133
134 134 def check_password(password, hashed):
135 135 return RhodeCodeCrypto.hash_check(password, hashed)
136 136
137 137
138 138 def generate_api_key(str_, salt=None):
139 139 """
140 140 Generates API KEY from given string
141 141
142 142 :param str_:
143 143 :param salt:
144 144 """
145 145
146 146 if salt is None:
147 147 salt = _RandomNameSequence().next()
148 148
149 149 return hashlib.sha1(str_ + salt).hexdigest()
150 150
151 151
152 152 def authfunc(environ, username, password):
153 153 """
154 154 Dummy authentication wrapper function used in Mercurial and Git for
155 155 access control.
156 156
157 157 :param environ: needed only for using in Basic auth
158 158 """
159 159 return authenticate(username, password)
160 160
161 161
162 162 def authenticate(username, password):
163 163 """
164 164 Authentication function used for access control,
165 165 firstly checks for db authentication then if ldap is enabled for ldap
166 166 authentication, also creates ldap user if not in database
167 167
168 168 :param username: username
169 169 :param password: password
170 170 """
171 171
172 172 user_model = UserModel()
173 173 user = User.get_by_username(username)
174 174
175 175 log.debug('Authenticating user using RhodeCode account')
176 176 if user is not None and not user.ldap_dn:
177 177 if user.active:
178 178 if user.username == 'default' and user.active:
179 179 log.info('user %s authenticated correctly as anonymous user' %
180 180 username)
181 181 return True
182 182
183 183 elif user.username == username and check_password(password,
184 184 user.password):
185 185 log.info('user %s authenticated correctly' % username)
186 186 return True
187 187 else:
188 188 log.warning('user %s tried auth but is disabled' % username)
189 189
190 190 else:
191 191 log.debug('Regular authentication failed')
192 192 user_obj = User.get_by_username(username, case_insensitive=True)
193 193
194 194 if user_obj is not None and not user_obj.ldap_dn:
195 195 log.debug('this user already exists as non ldap')
196 196 return False
197 197
198 198 ldap_settings = RhodeCodeSetting.get_ldap_settings()
199 199 #======================================================================
200 200 # FALLBACK TO LDAP AUTH IF ENABLE
201 201 #======================================================================
202 202 if str2bool(ldap_settings.get('ldap_active')):
203 203 log.debug("Authenticating user using ldap")
204 204 kwargs = {
205 205 'server': ldap_settings.get('ldap_host', ''),
206 206 'base_dn': ldap_settings.get('ldap_base_dn', ''),
207 207 'port': ldap_settings.get('ldap_port'),
208 208 'bind_dn': ldap_settings.get('ldap_dn_user'),
209 209 'bind_pass': ldap_settings.get('ldap_dn_pass'),
210 210 'tls_kind': ldap_settings.get('ldap_tls_kind'),
211 211 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
212 212 'ldap_filter': ldap_settings.get('ldap_filter'),
213 213 'search_scope': ldap_settings.get('ldap_search_scope'),
214 214 'attr_login': ldap_settings.get('ldap_attr_login'),
215 215 'ldap_version': 3,
216 216 }
217 217 log.debug('Checking for ldap authentication')
218 218 try:
219 219 aldap = AuthLdap(**kwargs)
220 220 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
221 221 password)
222 222 log.debug('Got ldap DN response %s' % user_dn)
223 223
224 224 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
225 225 .get(k), [''])[0]
226 226
227 227 user_attrs = {
228 228 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
229 229 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
230 230 'email': get_ldap_attr('ldap_attr_email'),
231 231 'active': 'hg.extern_activate.auto' in User.get_default_user()\
232 232 .AuthUser.permissions['global']
233 233 }
234 234
235 235 # don't store LDAP password since we don't need it. Override
236 236 # with some random generated password
237 237 _password = PasswordGenerator().gen_password(length=8)
238 238 # create this user on the fly if it doesn't exist in rhodecode
239 239 # database
240 240 if user_model.create_ldap(username, _password, user_dn,
241 241 user_attrs):
242 242 log.info('created new ldap user %s' % username)
243 243
244 244 Session().commit()
245 245 return True
246 246 except (LdapUsernameError, LdapPasswordError, LdapImportError):
247 247 pass
248 248 except (Exception,):
249 249 log.error(traceback.format_exc())
250 250 pass
251 251 return False
252 252
253 253
254 254 def login_container_auth(username):
255 255 user = User.get_by_username(username)
256 256 if user is None:
257 257 user_attrs = {
258 258 'name': username,
259 259 'lastname': None,
260 260 'email': None,
261 261 'active': 'hg.extern_activate.auto' in User.get_default_user()\
262 262 .AuthUser.permissions['global']
263 263 }
264 264 user = UserModel().create_for_container_auth(username, user_attrs)
265 265 if not user:
266 266 return None
267 267 log.info('User %s was created by container authentication' % username)
268 268
269 269 if not user.active:
270 270 return None
271 271
272 272 user.update_lastlogin()
273 273 Session().commit()
274 274
275 275 log.debug('User %s is now logged in by container authentication',
276 276 user.username)
277 277 return user
278 278
279 279
280 280 def get_container_username(environ, config, clean_username=False):
281 281 """
282 282 Get's the container_auth username (or email). It tries to get username
283 283 from REMOTE_USER if container_auth_enabled is enabled, if that fails
284 284 it tries to get username from HTTP_X_FORWARDED_USER if proxypass_auth_enabled
285 285 is enabled. clean_username extracts the username from this data if it's
286 286 having @ in it.
287 287
288 288 :param environ:
289 289 :param config:
290 290 :param clean_username:
291 291 """
292 292 username = None
293 293
294 294 if str2bool(config.get('container_auth_enabled', False)):
295 295 from paste.httpheaders import REMOTE_USER
296 296 username = REMOTE_USER(environ)
297 297 log.debug('extracted REMOTE_USER:%s' % (username))
298 298
299 299 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
300 300 username = environ.get('HTTP_X_FORWARDED_USER')
301 301 log.debug('extracted HTTP_X_FORWARDED_USER:%s' % (username))
302 302
303 303 if username and clean_username:
304 304 # Removing realm and domain from username
305 305 username = username.partition('@')[0]
306 306 username = username.rpartition('\\')[2]
307 307 log.debug('Received username %s from container' % username)
308 308
309 309 return username
310 310
311 311
312 312 class CookieStoreWrapper(object):
313 313
314 314 def __init__(self, cookie_store):
315 315 self.cookie_store = cookie_store
316 316
317 317 def __repr__(self):
318 318 return 'CookieStore<%s>' % (self.cookie_store)
319 319
320 320 def get(self, key, other=None):
321 321 if isinstance(self.cookie_store, dict):
322 322 return self.cookie_store.get(key, other)
323 323 elif isinstance(self.cookie_store, AuthUser):
324 324 return self.cookie_store.__dict__.get(key, other)
325 325
326 326
327 327 class AuthUser(object):
328 328 """
329 329 A simple object that handles all attributes of user in RhodeCode
330 330
331 331 It does lookup based on API key,given user, or user present in session
332 332 Then it fills all required information for such user. It also checks if
333 333 anonymous access is enabled and if so, it returns default user as logged
334 334 in
335 335 """
336 336
337 337 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
338 338
339 339 self.user_id = user_id
340 340 self.api_key = None
341 341 self.username = username
342 342 self.ip_addr = ip_addr
343 343
344 344 self.name = ''
345 345 self.lastname = ''
346 346 self.email = ''
347 347 self.is_authenticated = False
348 348 self.admin = False
349 349 self.inherit_default_permissions = False
350 350 self.permissions = {}
351 351 self._api_key = api_key
352 352 self.propagate_data()
353 353 self._instance = None
354 354
355 355 def propagate_data(self):
356 356 user_model = UserModel()
357 357 self.anonymous_user = User.get_by_username('default', cache=True)
358 358 is_user_loaded = False
359 359
360 360 # try go get user by api key
361 361 if self._api_key and self._api_key != self.anonymous_user.api_key:
362 362 log.debug('Auth User lookup by API KEY %s' % self._api_key)
363 363 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
364 364 # lookup by userid
365 365 elif (self.user_id is not None and
366 366 self.user_id != self.anonymous_user.user_id):
367 367 log.debug('Auth User lookup by USER ID %s' % self.user_id)
368 368 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
369 369 # lookup by username
370 370 elif self.username and \
371 371 str2bool(config.get('container_auth_enabled', False)):
372 372
373 373 log.debug('Auth User lookup by USER NAME %s' % self.username)
374 374 dbuser = login_container_auth(self.username)
375 375 if dbuser is not None:
376 376 log.debug('filling all attributes to object')
377 377 for k, v in dbuser.get_dict().items():
378 378 setattr(self, k, v)
379 379 self.set_authenticated()
380 380 is_user_loaded = True
381 381 else:
382 382 log.debug('No data in %s that could been used to log in' % self)
383 383
384 384 if not is_user_loaded:
385 385 # if we cannot authenticate user try anonymous
386 386 if self.anonymous_user.active:
387 387 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
388 388 # then we set this user is logged in
389 389 self.is_authenticated = True
390 390 else:
391 391 self.user_id = None
392 392 self.username = None
393 393 self.is_authenticated = False
394 394
395 395 if not self.username:
396 396 self.username = 'None'
397 397
398 398 log.debug('Auth User is now %s' % self)
399 399 user_model.fill_perms(self)
400 400
401 401 @property
402 402 def is_admin(self):
403 403 return self.admin
404 404
405 405 @property
406 406 def repositories_admin(self):
407 407 """
408 408 Returns list of repositories you're an admin of
409 409 """
410 410 return [x[0] for x in self.permissions['repositories'].iteritems()
411 411 if x[1] == 'repository.admin']
412 412
413 413 @property
414 414 def repository_groups_admin(self):
415 415 """
416 416 Returns list of repository groups you're an admin of
417 417 """
418 418 return [x[0] for x in self.permissions['repositories_groups'].iteritems()
419 419 if x[1] == 'group.admin']
420 420
421 421 @property
422 422 def user_groups_admin(self):
423 423 """
424 424 Returns list of user groups you're an admin of
425 425 """
426 426 return [x[0] for x in self.permissions['user_groups'].iteritems()
427 427 if x[1] == 'usergroup.admin']
428 428
429 429 @property
430 430 def ip_allowed(self):
431 431 """
432 432 Checks if ip_addr used in constructor is allowed from defined list of
433 433 allowed ip_addresses for user
434 434
435 435 :returns: boolean, True if ip is in allowed ip range
436 436 """
437 437 #check IP
438 438 allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
439 439 if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
440 440 log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
441 441 return True
442 442 else:
443 443 log.info('Access for IP:%s forbidden, '
444 444 'not in %s' % (self.ip_addr, allowed_ips))
445 445 return False
446 446
447 447 def __repr__(self):
448 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
449 self.is_authenticated)
448 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
449 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
450 450
451 451 def set_authenticated(self, authenticated=True):
452 452 if self.user_id != self.anonymous_user.user_id:
453 453 self.is_authenticated = authenticated
454 454
455 455 def get_cookie_store(self):
456 456 return {'username': self.username,
457 457 'user_id': self.user_id,
458 458 'is_authenticated': self.is_authenticated}
459 459
460 460 @classmethod
461 461 def from_cookie_store(cls, cookie_store):
462 462 """
463 463 Creates AuthUser from a cookie store
464 464
465 465 :param cls:
466 466 :param cookie_store:
467 467 """
468 468 user_id = cookie_store.get('user_id')
469 469 username = cookie_store.get('username')
470 470 api_key = cookie_store.get('api_key')
471 471 return AuthUser(user_id, api_key, username)
472 472
473 473 @classmethod
474 474 def get_allowed_ips(cls, user_id, cache=False):
475 475 _set = set()
476 476 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
477 477 if cache:
478 478 user_ips = user_ips.options(FromCache("sql_cache_short",
479 479 "get_user_ips_%s" % user_id))
480 480 for ip in user_ips:
481 481 try:
482 482 _set.add(ip.ip_addr)
483 483 except ObjectDeletedError:
484 484 # since we use heavy caching sometimes it happens that we get
485 485 # deleted objects here, we just skip them
486 486 pass
487 487 return _set or set(['0.0.0.0/0', '::/0'])
488 488
489 489
490 490 def set_available_permissions(config):
491 491 """
492 492 This function will propagate pylons globals with all available defined
493 493 permission given in db. We don't want to check each time from db for new
494 494 permissions since adding a new permission also requires application restart
495 495 ie. to decorate new views with the newly created permission
496 496
497 497 :param config: current pylons config instance
498 498
499 499 """
500 500 log.info('getting information about all available permissions')
501 501 try:
502 502 sa = meta.Session
503 503 all_perms = sa.query(Permission).all()
504 504 except Exception:
505 505 pass
506 506 finally:
507 507 meta.Session.remove()
508 508
509 509 config['available_permissions'] = [x.permission_name for x in all_perms]
510 510
511 511
512 512 #==============================================================================
513 513 # CHECK DECORATORS
514 514 #==============================================================================
515 515 class LoginRequired(object):
516 516 """
517 517 Must be logged in to execute this function else
518 518 redirect to login page
519 519
520 520 :param api_access: if enabled this checks only for valid auth token
521 521 and grants access based on valid token
522 522 """
523 523
524 524 def __init__(self, api_access=False):
525 525 self.api_access = api_access
526 526
527 527 def __call__(self, func):
528 528 return decorator(self.__wrapper, func)
529 529
530 530 def __wrapper(self, func, *fargs, **fkwargs):
531 531 cls = fargs[0]
532 532 user = cls.rhodecode_user
533 533 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
534 534 # defined whitelist of controllers which API access will be enabled
535 535 whitelist = aslist(config.get('api_access_controllers_whitelist'),
536 536 sep=',')
537 537 api_access_whitelist = loc in whitelist
538 538 log.debug('loc:%s is in API whitelist:%s:%s' % (loc, whitelist,
539 539 api_access_whitelist))
540 540 #check IP
541 541 ip_access_ok = True
542 542 if not user.ip_allowed:
543 543 from rhodecode.lib import helpers as h
544 544 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
545 545 category='warning')
546 546 ip_access_ok = False
547 547
548 548 api_access_ok = False
549 549 if self.api_access or api_access_whitelist:
550 550 log.debug('Checking API KEY access for %s' % cls)
551 551 if user.api_key == request.GET.get('api_key'):
552 552 api_access_ok = True
553 553 else:
554 554 log.debug("API KEY token not valid")
555 555
556 556 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
557 557 if (user.is_authenticated or api_access_ok) and ip_access_ok:
558 558 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
559 559 log.info('user %s is authenticated and granted access to %s '
560 560 'using %s' % (user.username, loc, reason)
561 561 )
562 562 return func(*fargs, **fkwargs)
563 563 else:
564 564 log.warn('user %s NOT authenticated on func: %s' % (
565 565 user, loc)
566 566 )
567 567 p = url.current()
568 568
569 569 log.debug('redirecting to login page with %s' % p)
570 570 return redirect(url('login_home', came_from=p))
571 571
572 572
573 573 class NotAnonymous(object):
574 574 """
575 575 Must be logged in to execute this function else
576 576 redirect to login page"""
577 577
578 578 def __call__(self, func):
579 579 return decorator(self.__wrapper, func)
580 580
581 581 def __wrapper(self, func, *fargs, **fkwargs):
582 582 cls = fargs[0]
583 583 self.user = cls.rhodecode_user
584 584
585 585 log.debug('Checking if user is not anonymous @%s' % cls)
586 586
587 587 anonymous = self.user.username == 'default'
588 588
589 589 if anonymous:
590 590 p = url.current()
591 591
592 592 import rhodecode.lib.helpers as h
593 593 h.flash(_('You need to be a registered user to '
594 594 'perform this action'),
595 595 category='warning')
596 596 return redirect(url('login_home', came_from=p))
597 597 else:
598 598 return func(*fargs, **fkwargs)
599 599
600 600
601 601 class PermsDecorator(object):
602 602 """Base class for controller decorators"""
603 603
604 604 def __init__(self, *required_perms):
605 605 available_perms = config['available_permissions']
606 606 for perm in required_perms:
607 607 if perm not in available_perms:
608 608 raise Exception("'%s' permission is not defined" % perm)
609 609 self.required_perms = set(required_perms)
610 610 self.user_perms = None
611 611
612 612 def __call__(self, func):
613 613 return decorator(self.__wrapper, func)
614 614
615 615 def __wrapper(self, func, *fargs, **fkwargs):
616 616 cls = fargs[0]
617 617 self.user = cls.rhodecode_user
618 618 self.user_perms = self.user.permissions
619 619 log.debug('checking %s permissions %s for %s %s',
620 620 self.__class__.__name__, self.required_perms, cls, self.user)
621 621
622 622 if self.check_permissions():
623 623 log.debug('Permission granted for %s %s' % (cls, self.user))
624 624 return func(*fargs, **fkwargs)
625 625
626 626 else:
627 627 log.debug('Permission denied for %s %s' % (cls, self.user))
628 628 anonymous = self.user.username == 'default'
629 629
630 630 if anonymous:
631 631 p = url.current()
632 632
633 633 import rhodecode.lib.helpers as h
634 634 h.flash(_('You need to be a signed in to '
635 635 'view this page'),
636 636 category='warning')
637 637 return redirect(url('login_home', came_from=p))
638 638
639 639 else:
640 640 # redirect with forbidden ret code
641 641 return abort(403)
642 642
643 643 def check_permissions(self):
644 644 """Dummy function for overriding"""
645 645 raise Exception('You have to write this function in child class')
646 646
647 647
648 648 class HasPermissionAllDecorator(PermsDecorator):
649 649 """
650 650 Checks for access permission for all given predicates. All of them
651 651 have to be meet in order to fulfill the request
652 652 """
653 653
654 654 def check_permissions(self):
655 655 if self.required_perms.issubset(self.user_perms.get('global')):
656 656 return True
657 657 return False
658 658
659 659
660 660 class HasPermissionAnyDecorator(PermsDecorator):
661 661 """
662 662 Checks for access permission for any of given predicates. In order to
663 663 fulfill the request any of predicates must be meet
664 664 """
665 665
666 666 def check_permissions(self):
667 667 if self.required_perms.intersection(self.user_perms.get('global')):
668 668 return True
669 669 return False
670 670
671 671
672 672 class HasRepoPermissionAllDecorator(PermsDecorator):
673 673 """
674 674 Checks for access permission for all given predicates for specific
675 675 repository. All of them have to be meet in order to fulfill the request
676 676 """
677 677
678 678 def check_permissions(self):
679 679 repo_name = get_repo_slug(request)
680 680 try:
681 681 user_perms = set([self.user_perms['repositories'][repo_name]])
682 682 except KeyError:
683 683 return False
684 684 if self.required_perms.issubset(user_perms):
685 685 return True
686 686 return False
687 687
688 688
689 689 class HasRepoPermissionAnyDecorator(PermsDecorator):
690 690 """
691 691 Checks for access permission for any of given predicates for specific
692 692 repository. In order to fulfill the request any of predicates must be meet
693 693 """
694 694
695 695 def check_permissions(self):
696 696 repo_name = get_repo_slug(request)
697 697 try:
698 698 user_perms = set([self.user_perms['repositories'][repo_name]])
699 699 except KeyError:
700 700 return False
701 701
702 702 if self.required_perms.intersection(user_perms):
703 703 return True
704 704 return False
705 705
706 706
707 707 class HasReposGroupPermissionAllDecorator(PermsDecorator):
708 708 """
709 709 Checks for access permission for all given predicates for specific
710 710 repository group. All of them have to be meet in order to fulfill the request
711 711 """
712 712
713 713 def check_permissions(self):
714 714 group_name = get_repos_group_slug(request)
715 715 try:
716 716 user_perms = set([self.user_perms['repositories_groups'][group_name]])
717 717 except KeyError:
718 718 return False
719 719
720 720 if self.required_perms.issubset(user_perms):
721 721 return True
722 722 return False
723 723
724 724
725 725 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
726 726 """
727 727 Checks for access permission for any of given predicates for specific
728 728 repository group. In order to fulfill the request any of predicates must be meet
729 729 """
730 730
731 731 def check_permissions(self):
732 732 group_name = get_repos_group_slug(request)
733 733 try:
734 734 user_perms = set([self.user_perms['repositories_groups'][group_name]])
735 735 except KeyError:
736 736 return False
737 737
738 738 if self.required_perms.intersection(user_perms):
739 739 return True
740 740 return False
741 741
742 742
743 743 class HasUserGroupPermissionAllDecorator(PermsDecorator):
744 744 """
745 745 Checks for access permission for all given predicates for specific
746 746 user group. All of them have to be meet in order to fulfill the request
747 747 """
748 748
749 749 def check_permissions(self):
750 750 group_name = get_user_group_slug(request)
751 751 try:
752 752 user_perms = set([self.user_perms['user_groups'][group_name]])
753 753 except KeyError:
754 754 return False
755 755
756 756 if self.required_perms.issubset(user_perms):
757 757 return True
758 758 return False
759 759
760 760
761 761 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
762 762 """
763 763 Checks for access permission for any of given predicates for specific
764 764 user group. In order to fulfill the request any of predicates must be meet
765 765 """
766 766
767 767 def check_permissions(self):
768 768 group_name = get_user_group_slug(request)
769 769 try:
770 770 user_perms = set([self.user_perms['user_groups'][group_name]])
771 771 except KeyError:
772 772 return False
773 773
774 774 if self.required_perms.intersection(user_perms):
775 775 return True
776 776 return False
777 777
778 778
779 779 #==============================================================================
780 780 # CHECK FUNCTIONS
781 781 #==============================================================================
782 782 class PermsFunction(object):
783 783 """Base function for other check functions"""
784 784
785 785 def __init__(self, *perms):
786 786 available_perms = config['available_permissions']
787 787
788 788 for perm in perms:
789 789 if perm not in available_perms:
790 790 raise Exception("'%s' permission is not defined" % perm)
791 791 self.required_perms = set(perms)
792 792 self.user_perms = None
793 793 self.repo_name = None
794 794 self.group_name = None
795 795
796 796 def __call__(self, check_location=''):
797 797 #TODO: put user as attribute here
798 798 user = request.user
799 799 cls_name = self.__class__.__name__
800 800 check_scope = {
801 801 'HasPermissionAll': '',
802 802 'HasPermissionAny': '',
803 803 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
804 804 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
805 805 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
806 806 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
807 807 }.get(cls_name, '?')
808 808 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
809 809 self.required_perms, user, check_scope,
810 810 check_location or 'unspecified location')
811 811 if not user:
812 812 log.debug('Empty request user')
813 813 return False
814 814 self.user_perms = user.permissions
815 815 if self.check_permissions():
816 816 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
817 817 check_location or 'unspecified location')
818 818 return True
819 819
820 820 else:
821 821 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
822 822 check_location or 'unspecified location')
823 823 return False
824 824
825 825 def check_permissions(self):
826 826 """Dummy function for overriding"""
827 827 raise Exception('You have to write this function in child class')
828 828
829 829
830 830 class HasPermissionAll(PermsFunction):
831 831 def check_permissions(self):
832 832 if self.required_perms.issubset(self.user_perms.get('global')):
833 833 return True
834 834 return False
835 835
836 836
837 837 class HasPermissionAny(PermsFunction):
838 838 def check_permissions(self):
839 839 if self.required_perms.intersection(self.user_perms.get('global')):
840 840 return True
841 841 return False
842 842
843 843
844 844 class HasRepoPermissionAll(PermsFunction):
845 845 def __call__(self, repo_name=None, check_location=''):
846 846 self.repo_name = repo_name
847 847 return super(HasRepoPermissionAll, self).__call__(check_location)
848 848
849 849 def check_permissions(self):
850 850 if not self.repo_name:
851 851 self.repo_name = get_repo_slug(request)
852 852
853 853 try:
854 854 self._user_perms = set(
855 855 [self.user_perms['repositories'][self.repo_name]]
856 856 )
857 857 except KeyError:
858 858 return False
859 859 if self.required_perms.issubset(self._user_perms):
860 860 return True
861 861 return False
862 862
863 863
864 864 class HasRepoPermissionAny(PermsFunction):
865 865 def __call__(self, repo_name=None, check_location=''):
866 866 self.repo_name = repo_name
867 867 return super(HasRepoPermissionAny, self).__call__(check_location)
868 868
869 869 def check_permissions(self):
870 870 if not self.repo_name:
871 871 self.repo_name = get_repo_slug(request)
872 872
873 873 try:
874 874 self._user_perms = set(
875 875 [self.user_perms['repositories'][self.repo_name]]
876 876 )
877 877 except KeyError:
878 878 return False
879 879 if self.required_perms.intersection(self._user_perms):
880 880 return True
881 881 return False
882 882
883 883
884 884 class HasReposGroupPermissionAny(PermsFunction):
885 885 def __call__(self, group_name=None, check_location=''):
886 886 self.group_name = group_name
887 887 return super(HasReposGroupPermissionAny, self).__call__(check_location)
888 888
889 889 def check_permissions(self):
890 890 try:
891 891 self._user_perms = set(
892 892 [self.user_perms['repositories_groups'][self.group_name]]
893 893 )
894 894 except KeyError:
895 895 return False
896 896 if self.required_perms.intersection(self._user_perms):
897 897 return True
898 898 return False
899 899
900 900
901 901 class HasReposGroupPermissionAll(PermsFunction):
902 902 def __call__(self, group_name=None, check_location=''):
903 903 self.group_name = group_name
904 904 return super(HasReposGroupPermissionAll, self).__call__(check_location)
905 905
906 906 def check_permissions(self):
907 907 try:
908 908 self._user_perms = set(
909 909 [self.user_perms['repositories_groups'][self.group_name]]
910 910 )
911 911 except KeyError:
912 912 return False
913 913 if self.required_perms.issubset(self._user_perms):
914 914 return True
915 915 return False
916 916
917 917
918 918 class HasUserGroupPermissionAny(PermsFunction):
919 919 def __call__(self, user_group_name=None, check_location=''):
920 920 self.user_group_name = user_group_name
921 921 return super(HasUserGroupPermissionAny, self).__call__(check_location)
922 922
923 923 def check_permissions(self):
924 924 try:
925 925 self._user_perms = set(
926 926 [self.user_perms['user_groups'][self.user_group_name]]
927 927 )
928 928 except KeyError:
929 929 return False
930 930 if self.required_perms.intersection(self._user_perms):
931 931 return True
932 932 return False
933 933
934 934
935 935 class HasUserGroupPermissionAll(PermsFunction):
936 936 def __call__(self, user_group_name=None, check_location=''):
937 937 self.user_group_name = user_group_name
938 938 return super(HasUserGroupPermissionAll, self).__call__(check_location)
939 939
940 940 def check_permissions(self):
941 941 try:
942 942 self._user_perms = set(
943 943 [self.user_perms['user_groups'][self.user_group_name]]
944 944 )
945 945 except KeyError:
946 946 return False
947 947 if self.required_perms.issubset(self._user_perms):
948 948 return True
949 949 return False
950 950
951 951 #==============================================================================
952 952 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
953 953 #==============================================================================
954 954 class HasPermissionAnyMiddleware(object):
955 955 def __init__(self, *perms):
956 956 self.required_perms = set(perms)
957 957
958 958 def __call__(self, user, repo_name):
959 959 # repo_name MUST be unicode, since we handle keys in permission
960 960 # dict by unicode
961 961 repo_name = safe_unicode(repo_name)
962 962 usr = AuthUser(user.user_id)
963 963 try:
964 964 self.user_perms = set([usr.permissions['repositories'][repo_name]])
965 965 except Exception:
966 966 log.error('Exception while accessing permissions %s' %
967 967 traceback.format_exc())
968 968 self.user_perms = set()
969 969 self.username = user.username
970 970 self.repo_name = repo_name
971 971 return self.check_permissions()
972 972
973 973 def check_permissions(self):
974 974 log.debug('checking VCS protocol '
975 975 'permissions %s for user:%s repository:%s', self.user_perms,
976 976 self.username, self.repo_name)
977 977 if self.required_perms.intersection(self.user_perms):
978 978 log.debug('permission granted for user:%s on repo:%s' % (
979 979 self.username, self.repo_name
980 980 )
981 981 )
982 982 return True
983 983 log.debug('permission denied for user:%s on repo:%s' % (
984 984 self.username, self.repo_name
985 985 )
986 986 )
987 987 return False
988 988
989 989
990 990 #==============================================================================
991 991 # SPECIAL VERSION TO HANDLE API AUTH
992 992 #==============================================================================
993 993 class _BaseApiPerm(object):
994 994 def __init__(self, *perms):
995 995 self.required_perms = set(perms)
996 996
997 997 def __call__(self, check_location='unspecified', user=None, repo_name=None):
998 998 cls_name = self.__class__.__name__
999 999 check_scope = 'user:%s, repo:%s' % (user, repo_name)
1000 1000 log.debug('checking cls:%s %s %s @ %s', cls_name,
1001 1001 self.required_perms, check_scope, check_location)
1002 1002 if not user:
1003 1003 log.debug('Empty User passed into arguments')
1004 1004 return False
1005 1005
1006 1006 ## process user
1007 1007 if not isinstance(user, AuthUser):
1008 1008 user = AuthUser(user.user_id)
1009 1009
1010 1010 if self.check_permissions(user.permissions, repo_name):
1011 1011 log.debug('Permission to %s granted for user: %s @ %s', repo_name,
1012 1012 user, check_location)
1013 1013 return True
1014 1014
1015 1015 else:
1016 1016 log.debug('Permission to %s denied for user: %s @ %s', repo_name,
1017 1017 user, check_location)
1018 1018 return False
1019 1019
1020 1020 def check_permissions(self, perm_defs, repo_name):
1021 1021 """
1022 1022 implement in child class should return True if permissions are ok,
1023 1023 False otherwise
1024 1024
1025 1025 :param perm_defs: dict with permission definitions
1026 1026 :param repo_name: repo name
1027 1027 """
1028 1028 raise NotImplementedError()
1029 1029
1030 1030
1031 1031 class HasPermissionAllApi(_BaseApiPerm):
1032 1032 def __call__(self, user, check_location=''):
1033 1033 return super(HasPermissionAllApi, self)\
1034 1034 .__call__(check_location=check_location, user=user)
1035 1035
1036 1036 def check_permissions(self, perm_defs, repo):
1037 1037 if self.required_perms.issubset(perm_defs.get('global')):
1038 1038 return True
1039 1039 return False
1040 1040
1041 1041
1042 1042 class HasPermissionAnyApi(_BaseApiPerm):
1043 1043 def __call__(self, user, check_location=''):
1044 1044 return super(HasPermissionAnyApi, self)\
1045 1045 .__call__(check_location=check_location, user=user)
1046 1046
1047 1047 def check_permissions(self, perm_defs, repo):
1048 1048 if self.required_perms.intersection(perm_defs.get('global')):
1049 1049 return True
1050 1050 return False
1051 1051
1052 1052
1053 1053 class HasRepoPermissionAllApi(_BaseApiPerm):
1054 1054 def __call__(self, user, repo_name, check_location=''):
1055 1055 return super(HasRepoPermissionAllApi, self)\
1056 1056 .__call__(check_location=check_location, user=user,
1057 1057 repo_name=repo_name)
1058 1058
1059 1059 def check_permissions(self, perm_defs, repo_name):
1060 1060
1061 1061 try:
1062 1062 self._user_perms = set(
1063 1063 [perm_defs['repositories'][repo_name]]
1064 1064 )
1065 1065 except KeyError:
1066 1066 log.warning(traceback.format_exc())
1067 1067 return False
1068 1068 if self.required_perms.issubset(self._user_perms):
1069 1069 return True
1070 1070 return False
1071 1071
1072 1072
1073 1073 class HasRepoPermissionAnyApi(_BaseApiPerm):
1074 1074 def __call__(self, user, repo_name, check_location=''):
1075 1075 return super(HasRepoPermissionAnyApi, self)\
1076 1076 .__call__(check_location=check_location, user=user,
1077 1077 repo_name=repo_name)
1078 1078
1079 1079 def check_permissions(self, perm_defs, repo_name):
1080 1080
1081 1081 try:
1082 1082 _user_perms = set(
1083 1083 [perm_defs['repositories'][repo_name]]
1084 1084 )
1085 1085 except KeyError:
1086 1086 log.warning(traceback.format_exc())
1087 1087 return False
1088 1088 if self.required_perms.intersection(_user_perms):
1089 1089 return True
1090 1090 return False
1091 1091
1092 1092
1093 1093 def check_ip_access(source_ip, allowed_ips=None):
1094 1094 """
1095 1095 Checks if source_ip is a subnet of any of allowed_ips.
1096 1096
1097 1097 :param source_ip:
1098 1098 :param allowed_ips: list of allowed ips together with mask
1099 1099 """
1100 1100 from rhodecode.lib import ipaddr
1101 1101 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1102 1102 if isinstance(allowed_ips, (tuple, list, set)):
1103 1103 for ip in allowed_ips:
1104 1104 try:
1105 1105 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
1106 1106 return True
1107 1107 # for any case we cannot determine the IP, don't crash just
1108 1108 # skip it and log as error, we want to say forbidden still when
1109 1109 # sending bad IP
1110 1110 except Exception:
1111 1111 log.error(traceback.format_exc())
1112 1112 continue
1113 1113 return False
General Comments 0
You need to be logged in to leave comments. Login now