##// END OF EJS Templates
auth: update logging for permission calculation function
marcink -
r2813:fc1dc788 default
parent child Browse files
Show More
@@ -1,2196 +1,2198 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 authentication and permission libraries
23 23 """
24 24
25 25 import os
26 import time
26 27 import inspect
27 28 import collections
28 29 import fnmatch
29 30 import hashlib
30 31 import itertools
31 32 import logging
32 33 import random
33 34 import traceback
34 35 from functools import wraps
35 36
36 37 import ipaddress
37 38
38 39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
39 40 from sqlalchemy.orm.exc import ObjectDeletedError
40 41 from sqlalchemy.orm import joinedload
41 42 from zope.cachedescriptors.property import Lazy as LazyProperty
42 43
43 44 import rhodecode
44 45 from rhodecode.model import meta
45 46 from rhodecode.model.meta import Session
46 47 from rhodecode.model.user import UserModel
47 48 from rhodecode.model.db import (
48 49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
49 50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
50 51 from rhodecode.lib import caches
51 52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int
52 53 from rhodecode.lib.utils import (
53 54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
54 55 from rhodecode.lib.caching_query import FromCache
55 56
56 57
57 58 if rhodecode.is_unix:
58 59 import bcrypt
59 60
60 61 log = logging.getLogger(__name__)
61 62
62 63 csrf_token_key = "csrf_token"
63 64
64 65
65 66 class PasswordGenerator(object):
66 67 """
67 68 This is a simple class for generating password from different sets of
68 69 characters
69 70 usage::
70 71
71 72 passwd_gen = PasswordGenerator()
72 73 #print 8-letter password containing only big and small letters
73 74 of alphabet
74 75 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 76 """
76 77 ALPHABETS_NUM = r'''1234567890'''
77 78 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 79 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 80 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 81 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 82 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 83 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 84 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 85 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 86 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 87
87 88 def __init__(self, passwd=''):
88 89 self.passwd = passwd
89 90
90 91 def gen_password(self, length, type_=None):
91 92 if type_ is None:
92 93 type_ = self.ALPHABETS_FULL
93 94 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
94 95 return self.passwd
95 96
96 97
97 98 class _RhodeCodeCryptoBase(object):
98 99 ENC_PREF = None
99 100
100 101 def hash_create(self, str_):
101 102 """
102 103 hash the string using
103 104
104 105 :param str_: password to hash
105 106 """
106 107 raise NotImplementedError
107 108
108 109 def hash_check_with_upgrade(self, password, hashed):
109 110 """
110 111 Returns tuple in which first element is boolean that states that
111 112 given password matches it's hashed version, and the second is new hash
112 113 of the password, in case this password should be migrated to new
113 114 cipher.
114 115 """
115 116 checked_hash = self.hash_check(password, hashed)
116 117 return checked_hash, None
117 118
118 119 def hash_check(self, password, hashed):
119 120 """
120 121 Checks matching password with it's hashed value.
121 122
122 123 :param password: password
123 124 :param hashed: password in hashed form
124 125 """
125 126 raise NotImplementedError
126 127
127 128 def _assert_bytes(self, value):
128 129 """
129 130 Passing in an `unicode` object can lead to hard to detect issues
130 131 if passwords contain non-ascii characters. Doing a type check
131 132 during runtime, so that such mistakes are detected early on.
132 133 """
133 134 if not isinstance(value, str):
134 135 raise TypeError(
135 136 "Bytestring required as input, got %r." % (value, ))
136 137
137 138
138 139 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 140 ENC_PREF = ('$2a$10', '$2b$10')
140 141
141 142 def hash_create(self, str_):
142 143 self._assert_bytes(str_)
143 144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 145
145 146 def hash_check_with_upgrade(self, password, hashed):
146 147 """
147 148 Returns tuple in which first element is boolean that states that
148 149 given password matches it's hashed version, and the second is new hash
149 150 of the password, in case this password should be migrated to new
150 151 cipher.
151 152
152 153 This implements special upgrade logic which works like that:
153 154 - check if the given password == bcrypted hash, if yes then we
154 155 properly used password and it was already in bcrypt. Proceed
155 156 without any changes
156 157 - if bcrypt hash check is not working try with sha256. If hash compare
157 158 is ok, it means we using correct but old hashed password. indicate
158 159 hash change and proceed
159 160 """
160 161
161 162 new_hash = None
162 163
163 164 # regular pw check
164 165 password_match_bcrypt = self.hash_check(password, hashed)
165 166
166 167 # now we want to know if the password was maybe from sha256
167 168 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 169 if not password_match_bcrypt:
169 170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 171 new_hash = self.hash_create(password) # make new bcrypt hash
171 172 password_match_bcrypt = True
172 173
173 174 return password_match_bcrypt, new_hash
174 175
175 176 def hash_check(self, password, hashed):
176 177 """
177 178 Checks matching password with it's hashed value.
178 179
179 180 :param password: password
180 181 :param hashed: password in hashed form
181 182 """
182 183 self._assert_bytes(password)
183 184 try:
184 185 return bcrypt.hashpw(password, hashed) == hashed
185 186 except ValueError as e:
186 187 # we're having a invalid salt here probably, we should not crash
187 188 # just return with False as it would be a wrong password.
188 189 log.debug('Failed to check password hash using bcrypt %s',
189 190 safe_str(e))
190 191
191 192 return False
192 193
193 194
194 195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 196 ENC_PREF = '_'
196 197
197 198 def hash_create(self, str_):
198 199 self._assert_bytes(str_)
199 200 return hashlib.sha256(str_).hexdigest()
200 201
201 202 def hash_check(self, password, hashed):
202 203 """
203 204 Checks matching password with it's hashed value.
204 205
205 206 :param password: password
206 207 :param hashed: password in hashed form
207 208 """
208 209 self._assert_bytes(password)
209 210 return hashlib.sha256(password).hexdigest() == hashed
210 211
211 212
212 213 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
213 214 ENC_PREF = '_'
214 215
215 216 def hash_create(self, str_):
216 217 self._assert_bytes(str_)
217 218 return hashlib.md5(str_).hexdigest()
218 219
219 220 def hash_check(self, password, hashed):
220 221 """
221 222 Checks matching password with it's hashed value.
222 223
223 224 :param password: password
224 225 :param hashed: password in hashed form
225 226 """
226 227 self._assert_bytes(password)
227 228 return hashlib.md5(password).hexdigest() == hashed
228 229
229 230
230 231 def crypto_backend():
231 232 """
232 233 Return the matching crypto backend.
233 234
234 235 Selection is based on if we run tests or not, we pick md5 backend to run
235 236 tests faster since BCRYPT is expensive to calculate
236 237 """
237 238 if rhodecode.is_test:
238 239 RhodeCodeCrypto = _RhodeCodeCryptoMd5()
239 240 else:
240 241 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 242
242 243 return RhodeCodeCrypto
243 244
244 245
245 246 def get_crypt_password(password):
246 247 """
247 248 Create the hash of `password` with the active crypto backend.
248 249
249 250 :param password: The cleartext password.
250 251 :type password: unicode
251 252 """
252 253 password = safe_str(password)
253 254 return crypto_backend().hash_create(password)
254 255
255 256
256 257 def check_password(password, hashed):
257 258 """
258 259 Check if the value in `password` matches the hash in `hashed`.
259 260
260 261 :param password: The cleartext password.
261 262 :type password: unicode
262 263
263 264 :param hashed: The expected hashed version of the password.
264 265 :type hashed: The hash has to be passed in in text representation.
265 266 """
266 267 password = safe_str(password)
267 268 return crypto_backend().hash_check(password, hashed)
268 269
269 270
270 271 def generate_auth_token(data, salt=None):
271 272 """
272 273 Generates API KEY from given string
273 274 """
274 275
275 276 if salt is None:
276 277 salt = os.urandom(16)
277 278 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 279
279 280
280 281 def get_came_from(request):
281 282 """
282 283 get query_string+path from request sanitized after removing auth_token
283 284 """
284 285 _req = request
285 286
286 287 path = _req.path
287 288 if 'auth_token' in _req.GET:
288 289 # sanitize the request and remove auth_token for redirection
289 290 _req.GET.pop('auth_token')
290 291 qs = _req.query_string
291 292 if qs:
292 293 path += '?' + qs
293 294
294 295 return path
295 296
296 297
297 298 class CookieStoreWrapper(object):
298 299
299 300 def __init__(self, cookie_store):
300 301 self.cookie_store = cookie_store
301 302
302 303 def __repr__(self):
303 304 return 'CookieStore<%s>' % (self.cookie_store)
304 305
305 306 def get(self, key, other=None):
306 307 if isinstance(self.cookie_store, dict):
307 308 return self.cookie_store.get(key, other)
308 309 elif isinstance(self.cookie_store, AuthUser):
309 310 return self.cookie_store.__dict__.get(key, other)
310 311
311 312
312 313 def _cached_perms_data(user_id, scope, user_is_admin,
313 314 user_inherit_default_permissions, explicit, algo,
314 315 calculate_super_admin):
315 316
316 317 permissions = PermissionCalculator(
317 318 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 319 explicit, algo, calculate_super_admin)
319 320 return permissions.calculate()
320 321
321 322
322 323 class PermOrigin(object):
323 324 SUPER_ADMIN = 'superadmin'
324 325
325 326 REPO_USER = 'user:%s'
326 327 REPO_USERGROUP = 'usergroup:%s'
327 328 REPO_OWNER = 'repo.owner'
328 329 REPO_DEFAULT = 'repo.default'
329 330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
330 331 REPO_PRIVATE = 'repo.private'
331 332
332 333 REPOGROUP_USER = 'user:%s'
333 334 REPOGROUP_USERGROUP = 'usergroup:%s'
334 335 REPOGROUP_OWNER = 'group.owner'
335 336 REPOGROUP_DEFAULT = 'group.default'
336 337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
337 338
338 339 USERGROUP_USER = 'user:%s'
339 340 USERGROUP_USERGROUP = 'usergroup:%s'
340 341 USERGROUP_OWNER = 'usergroup.owner'
341 342 USERGROUP_DEFAULT = 'usergroup.default'
342 343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
343 344
344 345
345 346 class PermOriginDict(dict):
346 347 """
347 348 A special dict used for tracking permissions along with their origins.
348 349
349 350 `__setitem__` has been overridden to expect a tuple(perm, origin)
350 351 `__getitem__` will return only the perm
351 352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
352 353
353 354 >>> perms = PermOriginDict()
354 355 >>> perms['resource'] = 'read', 'default'
355 356 >>> perms['resource']
356 357 'read'
357 358 >>> perms['resource'] = 'write', 'admin'
358 359 >>> perms['resource']
359 360 'write'
360 361 >>> perms.perm_origin_stack
361 362 {'resource': [('read', 'default'), ('write', 'admin')]}
362 363 """
363 364
364 365 def __init__(self, *args, **kw):
365 366 dict.__init__(self, *args, **kw)
366 367 self.perm_origin_stack = collections.OrderedDict()
367 368
368 369 def __setitem__(self, key, (perm, origin)):
369 370 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
370 371 dict.__setitem__(self, key, perm)
371 372
372 373
373 374 class PermissionCalculator(object):
374 375
375 376 def __init__(
376 377 self, user_id, scope, user_is_admin,
377 378 user_inherit_default_permissions, explicit, algo,
378 379 calculate_super_admin=False):
379 380
380 381 self.user_id = user_id
381 382 self.user_is_admin = user_is_admin
382 383 self.inherit_default_permissions = user_inherit_default_permissions
383 384 self.explicit = explicit
384 385 self.algo = algo
385 386 self.calculate_super_admin = calculate_super_admin
386 387
387 388 scope = scope or {}
388 389 self.scope_repo_id = scope.get('repo_id')
389 390 self.scope_repo_group_id = scope.get('repo_group_id')
390 391 self.scope_user_group_id = scope.get('user_group_id')
391 392
392 393 self.default_user_id = User.get_default_user(cache=True).user_id
393 394
394 395 self.permissions_repositories = PermOriginDict()
395 396 self.permissions_repository_groups = PermOriginDict()
396 397 self.permissions_user_groups = PermOriginDict()
397 398 self.permissions_global = set()
398 399
399 400 self.default_repo_perms = Permission.get_default_repo_perms(
400 401 self.default_user_id, self.scope_repo_id)
401 402 self.default_repo_groups_perms = Permission.get_default_group_perms(
402 403 self.default_user_id, self.scope_repo_group_id)
403 404 self.default_user_group_perms = \
404 405 Permission.get_default_user_group_perms(
405 406 self.default_user_id, self.scope_user_group_id)
406 407
407 408 def calculate(self):
408 409 if self.user_is_admin and not self.calculate_super_admin:
409 410 return self._admin_permissions()
410 411
411 412 self._calculate_global_default_permissions()
412 413 self._calculate_global_permissions()
413 414 self._calculate_default_permissions()
414 415 self._calculate_repository_permissions()
415 416 self._calculate_repository_group_permissions()
416 417 self._calculate_user_group_permissions()
417 418 return self._permission_structure()
418 419
419 420 def _admin_permissions(self):
420 421 """
421 422 admin user have all default rights for repositories
422 423 and groups set to admin
423 424 """
424 425 self.permissions_global.add('hg.admin')
425 426 self.permissions_global.add('hg.create.write_on_repogroup.true')
426 427
427 428 # repositories
428 429 for perm in self.default_repo_perms:
429 430 r_k = perm.UserRepoToPerm.repository.repo_name
430 431 p = 'repository.admin'
431 432 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
432 433
433 434 # repository groups
434 435 for perm in self.default_repo_groups_perms:
435 436 rg_k = perm.UserRepoGroupToPerm.group.group_name
436 437 p = 'group.admin'
437 438 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
438 439
439 440 # user groups
440 441 for perm in self.default_user_group_perms:
441 442 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
442 443 p = 'usergroup.admin'
443 444 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
444 445
445 446 return self._permission_structure()
446 447
447 448 def _calculate_global_default_permissions(self):
448 449 """
449 450 global permissions taken from the default user
450 451 """
451 452 default_global_perms = UserToPerm.query()\
452 453 .filter(UserToPerm.user_id == self.default_user_id)\
453 454 .options(joinedload(UserToPerm.permission))
454 455
455 456 for perm in default_global_perms:
456 457 self.permissions_global.add(perm.permission.permission_name)
457 458
458 459 if self.user_is_admin:
459 460 self.permissions_global.add('hg.admin')
460 461 self.permissions_global.add('hg.create.write_on_repogroup.true')
461 462
462 463 def _calculate_global_permissions(self):
463 464 """
464 465 Set global system permissions with user permissions or permissions
465 466 taken from the user groups of the current user.
466 467
467 468 The permissions include repo creating, repo group creating, forking
468 469 etc.
469 470 """
470 471
471 472 # now we read the defined permissions and overwrite what we have set
472 473 # before those can be configured from groups or users explicitly.
473 474
474 475 # TODO: johbo: This seems to be out of sync, find out the reason
475 476 # for the comment below and update it.
476 477
477 478 # In case we want to extend this list we should be always in sync with
478 479 # User.DEFAULT_USER_PERMISSIONS definitions
479 480 _configurable = frozenset([
480 481 'hg.fork.none', 'hg.fork.repository',
481 482 'hg.create.none', 'hg.create.repository',
482 483 'hg.usergroup.create.false', 'hg.usergroup.create.true',
483 484 'hg.repogroup.create.false', 'hg.repogroup.create.true',
484 485 'hg.create.write_on_repogroup.false',
485 486 'hg.create.write_on_repogroup.true',
486 487 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
487 488 ])
488 489
489 490 # USER GROUPS comes first user group global permissions
490 491 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
491 492 .options(joinedload(UserGroupToPerm.permission))\
492 493 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
493 494 UserGroupMember.users_group_id))\
494 495 .filter(UserGroupMember.user_id == self.user_id)\
495 496 .order_by(UserGroupToPerm.users_group_id)\
496 497 .all()
497 498
498 499 # need to group here by groups since user can be in more than
499 500 # one group, so we get all groups
500 501 _explicit_grouped_perms = [
501 502 [x, list(y)] for x, y in
502 503 itertools.groupby(user_perms_from_users_groups,
503 504 lambda _x: _x.users_group)]
504 505
505 506 for gr, perms in _explicit_grouped_perms:
506 507 # since user can be in multiple groups iterate over them and
507 508 # select the lowest permissions first (more explicit)
508 509 # TODO: marcink: do this^^
509 510
510 511 # group doesn't inherit default permissions so we actually set them
511 512 if not gr.inherit_default_permissions:
512 513 # NEED TO IGNORE all previously set configurable permissions
513 514 # and replace them with explicitly set from this user
514 515 # group permissions
515 516 self.permissions_global = self.permissions_global.difference(
516 517 _configurable)
517 518 for perm in perms:
518 519 self.permissions_global.add(perm.permission.permission_name)
519 520
520 521 # user explicit global permissions
521 522 user_perms = Session().query(UserToPerm)\
522 523 .options(joinedload(UserToPerm.permission))\
523 524 .filter(UserToPerm.user_id == self.user_id).all()
524 525
525 526 if not self.inherit_default_permissions:
526 527 # NEED TO IGNORE all configurable permissions and
527 528 # replace them with explicitly set from this user permissions
528 529 self.permissions_global = self.permissions_global.difference(
529 530 _configurable)
530 531 for perm in user_perms:
531 532 self.permissions_global.add(perm.permission.permission_name)
532 533
533 534 def _calculate_default_permissions(self):
534 535 """
535 536 Set default user permissions for repositories, repository groups
536 537 taken from the default user.
537 538
538 539 Calculate inheritance of object permissions based on what we have now
539 540 in GLOBAL permissions. We check if .false is in GLOBAL since this is
540 541 explicitly set. Inherit is the opposite of .false being there.
541 542
542 543 .. note::
543 544
544 545 the syntax is little bit odd but what we need to check here is
545 546 the opposite of .false permission being in the list so even for
546 547 inconsistent state when both .true/.false is there
547 548 .false is more important
548 549
549 550 """
550 551 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
551 552 in self.permissions_global)
552 553
553 554 # defaults for repositories, taken from `default` user permissions
554 555 # on given repo
555 556 for perm in self.default_repo_perms:
556 557 r_k = perm.UserRepoToPerm.repository.repo_name
557 558 p = perm.Permission.permission_name
558 559 o = PermOrigin.REPO_DEFAULT
559 560 self.permissions_repositories[r_k] = p, o
560 561
561 562 # if we decide this user isn't inheriting permissions from
562 563 # default user we set him to .none so only explicit
563 564 # permissions work
564 565 if not user_inherit_object_permissions:
565 566 p = 'repository.none'
566 567 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
567 568 self.permissions_repositories[r_k] = p, o
568 569
569 570 if perm.Repository.private and not (
570 571 perm.Repository.user_id == self.user_id):
571 572 # disable defaults for private repos,
572 573 p = 'repository.none'
573 574 o = PermOrigin.REPO_PRIVATE
574 575 self.permissions_repositories[r_k] = p, o
575 576
576 577 elif perm.Repository.user_id == self.user_id:
577 578 # set admin if owner
578 579 p = 'repository.admin'
579 580 o = PermOrigin.REPO_OWNER
580 581 self.permissions_repositories[r_k] = p, o
581 582
582 583 if self.user_is_admin:
583 584 p = 'repository.admin'
584 585 o = PermOrigin.SUPER_ADMIN
585 586 self.permissions_repositories[r_k] = p, o
586 587
587 588 # defaults for repository groups taken from `default` user permission
588 589 # on given group
589 590 for perm in self.default_repo_groups_perms:
590 591 rg_k = perm.UserRepoGroupToPerm.group.group_name
591 592 p = perm.Permission.permission_name
592 593 o = PermOrigin.REPOGROUP_DEFAULT
593 594 self.permissions_repository_groups[rg_k] = p, o
594 595
595 596 # if we decide this user isn't inheriting permissions from default
596 597 # user we set him to .none so only explicit permissions work
597 598 if not user_inherit_object_permissions:
598 599 p = 'group.none'
599 600 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
600 601 self.permissions_repository_groups[rg_k] = p, o
601 602
602 603 if perm.RepoGroup.user_id == self.user_id:
603 604 # set admin if owner
604 605 p = 'group.admin'
605 606 o = PermOrigin.REPOGROUP_OWNER
606 607 self.permissions_repository_groups[rg_k] = p, o
607 608
608 609 if self.user_is_admin:
609 610 p = 'group.admin'
610 611 o = PermOrigin.SUPER_ADMIN
611 612 self.permissions_repository_groups[rg_k] = p, o
612 613
613 614 # defaults for user groups taken from `default` user permission
614 615 # on given user group
615 616 for perm in self.default_user_group_perms:
616 617 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
617 618 p = perm.Permission.permission_name
618 619 o = PermOrigin.USERGROUP_DEFAULT
619 620 self.permissions_user_groups[u_k] = p, o
620 621
621 622 # if we decide this user isn't inheriting permissions from default
622 623 # user we set him to .none so only explicit permissions work
623 624 if not user_inherit_object_permissions:
624 625 p = 'usergroup.none'
625 626 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
626 627 self.permissions_user_groups[u_k] = p, o
627 628
628 629 if perm.UserGroup.user_id == self.user_id:
629 630 # set admin if owner
630 631 p = 'usergroup.admin'
631 632 o = PermOrigin.USERGROUP_OWNER
632 633 self.permissions_user_groups[u_k] = p, o
633 634
634 635 if self.user_is_admin:
635 636 p = 'usergroup.admin'
636 637 o = PermOrigin.SUPER_ADMIN
637 638 self.permissions_user_groups[u_k] = p, o
638 639
639 640 def _calculate_repository_permissions(self):
640 641 """
641 642 Repository permissions for the current user.
642 643
643 644 Check if the user is part of user groups for this repository and
644 645 fill in the permission from it. `_choose_permission` decides of which
645 646 permission should be selected based on selected method.
646 647 """
647 648
648 649 # user group for repositories permissions
649 650 user_repo_perms_from_user_group = Permission\
650 651 .get_default_repo_perms_from_user_group(
651 652 self.user_id, self.scope_repo_id)
652 653
653 654 multiple_counter = collections.defaultdict(int)
654 655 for perm in user_repo_perms_from_user_group:
655 656 r_k = perm.UserGroupRepoToPerm.repository.repo_name
656 657 multiple_counter[r_k] += 1
657 658 p = perm.Permission.permission_name
658 659 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
659 660 .users_group.users_group_name
660 661
661 662 if multiple_counter[r_k] > 1:
662 663 cur_perm = self.permissions_repositories[r_k]
663 664 p = self._choose_permission(p, cur_perm)
664 665
665 666 self.permissions_repositories[r_k] = p, o
666 667
667 668 if perm.Repository.user_id == self.user_id:
668 669 # set admin if owner
669 670 p = 'repository.admin'
670 671 o = PermOrigin.REPO_OWNER
671 672 self.permissions_repositories[r_k] = p, o
672 673
673 674 if self.user_is_admin:
674 675 p = 'repository.admin'
675 676 o = PermOrigin.SUPER_ADMIN
676 677 self.permissions_repositories[r_k] = p, o
677 678
678 679 # user explicit permissions for repositories, overrides any specified
679 680 # by the group permission
680 681 user_repo_perms = Permission.get_default_repo_perms(
681 682 self.user_id, self.scope_repo_id)
682 683 for perm in user_repo_perms:
683 684 r_k = perm.UserRepoToPerm.repository.repo_name
684 685 p = perm.Permission.permission_name
685 686 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
686 687
687 688 if not self.explicit:
688 689 cur_perm = self.permissions_repositories.get(
689 690 r_k, 'repository.none')
690 691 p = self._choose_permission(p, cur_perm)
691 692
692 693 self.permissions_repositories[r_k] = p, o
693 694
694 695 if perm.Repository.user_id == self.user_id:
695 696 # set admin if owner
696 697 p = 'repository.admin'
697 698 o = PermOrigin.REPO_OWNER
698 699 self.permissions_repositories[r_k] = p, o
699 700
700 701 if self.user_is_admin:
701 702 p = 'repository.admin'
702 703 o = PermOrigin.SUPER_ADMIN
703 704 self.permissions_repositories[r_k] = p, o
704 705
705 706 def _calculate_repository_group_permissions(self):
706 707 """
707 708 Repository group permissions for the current user.
708 709
709 710 Check if the user is part of user groups for repository groups and
710 711 fill in the permissions from it. `_choose_permission` decides of which
711 712 permission should be selected based on selected method.
712 713 """
713 714 # user group for repo groups permissions
714 715 user_repo_group_perms_from_user_group = Permission\
715 716 .get_default_group_perms_from_user_group(
716 717 self.user_id, self.scope_repo_group_id)
717 718
718 719 multiple_counter = collections.defaultdict(int)
719 720 for perm in user_repo_group_perms_from_user_group:
720 721 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
721 722 multiple_counter[rg_k] += 1
722 723 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
723 724 .users_group.users_group_name
724 725 p = perm.Permission.permission_name
725 726
726 727 if multiple_counter[rg_k] > 1:
727 728 cur_perm = self.permissions_repository_groups[rg_k]
728 729 p = self._choose_permission(p, cur_perm)
729 730 self.permissions_repository_groups[rg_k] = p, o
730 731
731 732 if perm.RepoGroup.user_id == self.user_id:
732 733 # set admin if owner, even for member of other user group
733 734 p = 'group.admin'
734 735 o = PermOrigin.REPOGROUP_OWNER
735 736 self.permissions_repository_groups[rg_k] = p, o
736 737
737 738 if self.user_is_admin:
738 739 p = 'group.admin'
739 740 o = PermOrigin.SUPER_ADMIN
740 741 self.permissions_repository_groups[rg_k] = p, o
741 742
742 743 # user explicit permissions for repository groups
743 744 user_repo_groups_perms = Permission.get_default_group_perms(
744 745 self.user_id, self.scope_repo_group_id)
745 746 for perm in user_repo_groups_perms:
746 747 rg_k = perm.UserRepoGroupToPerm.group.group_name
747 748 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
748 749 .user.username
749 750 p = perm.Permission.permission_name
750 751
751 752 if not self.explicit:
752 753 cur_perm = self.permissions_repository_groups.get(
753 754 rg_k, 'group.none')
754 755 p = self._choose_permission(p, cur_perm)
755 756
756 757 self.permissions_repository_groups[rg_k] = p, o
757 758
758 759 if perm.RepoGroup.user_id == self.user_id:
759 760 # set admin if owner
760 761 p = 'group.admin'
761 762 o = PermOrigin.REPOGROUP_OWNER
762 763 self.permissions_repository_groups[rg_k] = p, o
763 764
764 765 if self.user_is_admin:
765 766 p = 'group.admin'
766 767 o = PermOrigin.SUPER_ADMIN
767 768 self.permissions_repository_groups[rg_k] = p, o
768 769
769 770 def _calculate_user_group_permissions(self):
770 771 """
771 772 User group permissions for the current user.
772 773 """
773 774 # user group for user group permissions
774 775 user_group_from_user_group = Permission\
775 776 .get_default_user_group_perms_from_user_group(
776 777 self.user_id, self.scope_user_group_id)
777 778
778 779 multiple_counter = collections.defaultdict(int)
779 780 for perm in user_group_from_user_group:
780 781 ug_k = perm.UserGroupUserGroupToPerm\
781 782 .target_user_group.users_group_name
782 783 multiple_counter[ug_k] += 1
783 784 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
784 785 .user_group.users_group_name
785 786 p = perm.Permission.permission_name
786 787
787 788 if multiple_counter[ug_k] > 1:
788 789 cur_perm = self.permissions_user_groups[ug_k]
789 790 p = self._choose_permission(p, cur_perm)
790 791
791 792 self.permissions_user_groups[ug_k] = p, o
792 793
793 794 if perm.UserGroup.user_id == self.user_id:
794 795 # set admin if owner, even for member of other user group
795 796 p = 'usergroup.admin'
796 797 o = PermOrigin.USERGROUP_OWNER
797 798 self.permissions_user_groups[ug_k] = p, o
798 799
799 800 if self.user_is_admin:
800 801 p = 'usergroup.admin'
801 802 o = PermOrigin.SUPER_ADMIN
802 803 self.permissions_user_groups[ug_k] = p, o
803 804
804 805 # user explicit permission for user groups
805 806 user_user_groups_perms = Permission.get_default_user_group_perms(
806 807 self.user_id, self.scope_user_group_id)
807 808 for perm in user_user_groups_perms:
808 809 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
809 810 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
810 811 .user.username
811 812 p = perm.Permission.permission_name
812 813
813 814 if not self.explicit:
814 815 cur_perm = self.permissions_user_groups.get(
815 816 ug_k, 'usergroup.none')
816 817 p = self._choose_permission(p, cur_perm)
817 818
818 819 self.permissions_user_groups[ug_k] = p, o
819 820
820 821 if perm.UserGroup.user_id == self.user_id:
821 822 # set admin if owner
822 823 p = 'usergroup.admin'
823 824 o = PermOrigin.USERGROUP_OWNER
824 825 self.permissions_user_groups[ug_k] = p, o
825 826
826 827 if self.user_is_admin:
827 828 p = 'usergroup.admin'
828 829 o = PermOrigin.SUPER_ADMIN
829 830 self.permissions_user_groups[ug_k] = p, o
830 831
831 832 def _choose_permission(self, new_perm, cur_perm):
832 833 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
833 834 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
834 835 if self.algo == 'higherwin':
835 836 if new_perm_val > cur_perm_val:
836 837 return new_perm
837 838 return cur_perm
838 839 elif self.algo == 'lowerwin':
839 840 if new_perm_val < cur_perm_val:
840 841 return new_perm
841 842 return cur_perm
842 843
843 844 def _permission_structure(self):
844 845 return {
845 846 'global': self.permissions_global,
846 847 'repositories': self.permissions_repositories,
847 848 'repositories_groups': self.permissions_repository_groups,
848 849 'user_groups': self.permissions_user_groups,
849 850 }
850 851
851 852
852 853 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
853 854 """
854 855 Check if given controller_name is in whitelist of auth token access
855 856 """
856 857 if not whitelist:
857 858 from rhodecode import CONFIG
858 859 whitelist = aslist(
859 860 CONFIG.get('api_access_controllers_whitelist'), sep=',')
860 861 # backward compat translation
861 862 compat = {
862 863 # old controller, new VIEW
863 864 'ChangesetController:*': 'RepoCommitsView:*',
864 865 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
865 866 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
866 867 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
867 868 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
868 869 'GistsController:*': 'GistView:*',
869 870 }
870 871
871 872 log.debug(
872 873 'Allowed views for AUTH TOKEN access: %s' % (whitelist,))
873 874 auth_token_access_valid = False
874 875
875 876 for entry in whitelist:
876 877 token_match = True
877 878 if entry in compat:
878 879 # translate from old Controllers to Pyramid Views
879 880 entry = compat[entry]
880 881
881 882 if '@' in entry:
882 883 # specific AuthToken
883 884 entry, allowed_token = entry.split('@', 1)
884 885 token_match = auth_token == allowed_token
885 886
886 887 if fnmatch.fnmatch(view_name, entry) and token_match:
887 888 auth_token_access_valid = True
888 889 break
889 890
890 891 if auth_token_access_valid:
891 892 log.debug('view: `%s` matches entry in whitelist: %s'
892 893 % (view_name, whitelist))
893 894 else:
894 895 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
895 896 % (view_name, whitelist))
896 897 if auth_token:
897 898 # if we use auth token key and don't have access it's a warning
898 899 log.warning(msg)
899 900 else:
900 901 log.debug(msg)
901 902
902 903 return auth_token_access_valid
903 904
904 905
905 906 class AuthUser(object):
906 907 """
907 908 A simple object that handles all attributes of user in RhodeCode
908 909
909 910 It does lookup based on API key,given user, or user present in session
910 911 Then it fills all required information for such user. It also checks if
911 912 anonymous access is enabled and if so, it returns default user as logged in
912 913 """
913 914 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
914 915
915 916 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
916 917
917 918 self.user_id = user_id
918 919 self._api_key = api_key
919 920
920 921 self.api_key = None
921 922 self.username = username
922 923 self.ip_addr = ip_addr
923 924 self.name = ''
924 925 self.lastname = ''
925 926 self.first_name = ''
926 927 self.last_name = ''
927 928 self.email = ''
928 929 self.is_authenticated = False
929 930 self.admin = False
930 931 self.inherit_default_permissions = False
931 932 self.password = ''
932 933
933 934 self.anonymous_user = None # propagated on propagate_data
934 935 self.propagate_data()
935 936 self._instance = None
936 937 self._permissions_scoped_cache = {} # used to bind scoped calculation
937 938
938 939 @LazyProperty
939 940 def permissions(self):
940 941 return self.get_perms(user=self, cache=False)
941 942
942 943 @LazyProperty
943 944 def permissions_safe(self):
944 945 """
945 946 Filtered permissions excluding not allowed repositories
946 947 """
947 948 perms = self.get_perms(user=self, cache=False)
948 949
949 950 perms['repositories'] = {
950 951 k: v for k, v in perms['repositories'].iteritems()
951 952 if v != 'repository.none'}
952 953 perms['repositories_groups'] = {
953 954 k: v for k, v in perms['repositories_groups'].iteritems()
954 955 if v != 'group.none'}
955 956 perms['user_groups'] = {
956 957 k: v for k, v in perms['user_groups'].iteritems()
957 958 if v != 'usergroup.none'}
958 959 return perms
959 960
960 961 @LazyProperty
961 962 def permissions_full_details(self):
962 963 return self.get_perms(
963 964 user=self, cache=False, calculate_super_admin=True)
964 965
965 966 def permissions_with_scope(self, scope):
966 967 """
967 968 Call the get_perms function with scoped data. The scope in that function
968 969 narrows the SQL calls to the given ID of objects resulting in fetching
969 970 Just particular permission we want to obtain. If scope is an empty dict
970 971 then it basically narrows the scope to GLOBAL permissions only.
971 972
972 973 :param scope: dict
973 974 """
974 975 if 'repo_name' in scope:
975 976 obj = Repository.get_by_repo_name(scope['repo_name'])
976 977 if obj:
977 978 scope['repo_id'] = obj.repo_id
978 979 _scope = {
979 980 'repo_id': -1,
980 981 'user_group_id': -1,
981 982 'repo_group_id': -1,
982 983 }
983 984 _scope.update(scope)
984 985 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
985 986 _scope.items())))
986 987 if cache_key not in self._permissions_scoped_cache:
987 988 # store in cache to mimic how the @LazyProperty works,
988 989 # the difference here is that we use the unique key calculated
989 990 # from params and values
990 991 res = self.get_perms(user=self, cache=False, scope=_scope)
991 992 self._permissions_scoped_cache[cache_key] = res
992 993 return self._permissions_scoped_cache[cache_key]
993 994
994 995 def get_instance(self):
995 996 return User.get(self.user_id)
996 997
997 998 def update_lastactivity(self):
998 999 if self.user_id:
999 1000 User.get(self.user_id).update_lastactivity()
1000 1001
1001 1002 def propagate_data(self):
1002 1003 """
1003 1004 Fills in user data and propagates values to this instance. Maps fetched
1004 1005 user attributes to this class instance attributes
1005 1006 """
1006 1007 log.debug('AuthUser: starting data propagation for new potential user')
1007 1008 user_model = UserModel()
1008 1009 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1009 1010 is_user_loaded = False
1010 1011
1011 1012 # lookup by userid
1012 1013 if self.user_id is not None and self.user_id != anon_user.user_id:
1013 1014 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1014 1015 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1015 1016
1016 1017 # try go get user by api key
1017 1018 elif self._api_key and self._api_key != anon_user.api_key:
1018 1019 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1019 1020 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1020 1021
1021 1022 # lookup by username
1022 1023 elif self.username:
1023 1024 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1024 1025 is_user_loaded = user_model.fill_data(self, username=self.username)
1025 1026 else:
1026 1027 log.debug('No data in %s that could been used to log in', self)
1027 1028
1028 1029 if not is_user_loaded:
1029 1030 log.debug(
1030 1031 'Failed to load user. Fallback to default user %s', anon_user)
1031 1032 # if we cannot authenticate user try anonymous
1032 1033 if anon_user.active:
1033 1034 log.debug('default user is active, using it as a session user')
1034 1035 user_model.fill_data(self, user_id=anon_user.user_id)
1035 1036 # then we set this user is logged in
1036 1037 self.is_authenticated = True
1037 1038 else:
1038 1039 log.debug('default user is NOT active')
1039 1040 # in case of disabled anonymous user we reset some of the
1040 1041 # parameters so such user is "corrupted", skipping the fill_data
1041 1042 for attr in ['user_id', 'username', 'admin', 'active']:
1042 1043 setattr(self, attr, None)
1043 1044 self.is_authenticated = False
1044 1045
1045 1046 if not self.username:
1046 1047 self.username = 'None'
1047 1048
1048 1049 log.debug('AuthUser: propagated user is now %s', self)
1049 1050
1050 1051 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1051 1052 calculate_super_admin=False, cache=False):
1052 1053 """
1053 1054 Fills user permission attribute with permissions taken from database
1054 1055 works for permissions given for repositories, and for permissions that
1055 1056 are granted to groups
1056 1057
1057 1058 :param user: instance of User object from database
1058 1059 :param explicit: In case there are permissions both for user and a group
1059 1060 that user is part of, explicit flag will defiine if user will
1060 1061 explicitly override permissions from group, if it's False it will
1061 1062 make decision based on the algo
1062 1063 :param algo: algorithm to decide what permission should be choose if
1063 1064 it's multiple defined, eg user in two different groups. It also
1064 1065 decides if explicit flag is turned off how to specify the permission
1065 1066 for case when user is in a group + have defined separate permission
1066 1067 """
1067 1068 user_id = user.user_id
1068 1069 user_is_admin = user.is_admin
1069 1070
1070 1071 # inheritance of global permissions like create repo/fork repo etc
1071 1072 user_inherit_default_permissions = user.inherit_default_permissions
1072 1073
1073 1074 cache_seconds = safe_int(
1074 1075 rhodecode.CONFIG.get('beaker.cache.short_term.expire'))
1075 1076 cache_on = cache or cache_seconds > 0
1076 1077 log.debug(
1077 'Computing PERMISSION tree for scope `%s` with caching: %s' % (
1078 scope, cache_on))
1079
1078 'Computing PERMISSION tree for user %s scope `%s` '
1079 'with caching: %s[%ss]' % (user, scope, cache_on, cache_seconds))
1080 start = time.time()
1080 1081 compute = caches.conditional_cache(
1081 1082 'short_term', 'cache_desc',
1082 1083 condition=cache_on, func=_cached_perms_data)
1083 1084 result = compute(user_id, scope, user_is_admin,
1084 1085 user_inherit_default_permissions, explicit, algo,
1085 1086 calculate_super_admin)
1086 1087
1087 1088 result_repr = []
1088 1089 for k in result:
1089 1090 result_repr.append((k, len(result[k])))
1090
1091 log.debug('PERMISSION tree computed %s' % (result_repr,))
1091 total = time.time() - start
1092 log.debug('PERMISSION tree for user %s computed in %.3fs: %s' % (
1093 user, total, result_repr))
1092 1094 return result
1093 1095
1094 1096 @property
1095 1097 def is_default(self):
1096 1098 return self.username == User.DEFAULT_USER
1097 1099
1098 1100 @property
1099 1101 def is_admin(self):
1100 1102 return self.admin
1101 1103
1102 1104 @property
1103 1105 def is_user_object(self):
1104 1106 return self.user_id is not None
1105 1107
1106 1108 @property
1107 1109 def repositories_admin(self):
1108 1110 """
1109 1111 Returns list of repositories you're an admin of
1110 1112 """
1111 1113 return [
1112 1114 x[0] for x in self.permissions['repositories'].iteritems()
1113 1115 if x[1] == 'repository.admin']
1114 1116
1115 1117 @property
1116 1118 def repository_groups_admin(self):
1117 1119 """
1118 1120 Returns list of repository groups you're an admin of
1119 1121 """
1120 1122 return [
1121 1123 x[0] for x in self.permissions['repositories_groups'].iteritems()
1122 1124 if x[1] == 'group.admin']
1123 1125
1124 1126 @property
1125 1127 def user_groups_admin(self):
1126 1128 """
1127 1129 Returns list of user groups you're an admin of
1128 1130 """
1129 1131 return [
1130 1132 x[0] for x in self.permissions['user_groups'].iteritems()
1131 1133 if x[1] == 'usergroup.admin']
1132 1134
1133 1135 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1134 1136 """
1135 1137 Returns list of repository ids that user have access to based on given
1136 1138 perms. The cache flag should be only used in cases that are used for
1137 1139 display purposes, NOT IN ANY CASE for permission checks.
1138 1140 """
1139 1141 from rhodecode.model.scm import RepoList
1140 1142 if not perms:
1141 1143 perms = [
1142 1144 'repository.read', 'repository.write', 'repository.admin']
1143 1145
1144 1146 def _cached_repo_acl(user_id, perm_def, name_filter):
1145 1147 qry = Repository.query()
1146 1148 if name_filter:
1147 1149 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1148 1150 qry = qry.filter(
1149 1151 Repository.repo_name.ilike(ilike_expression))
1150 1152
1151 1153 return [x.repo_id for x in
1152 1154 RepoList(qry, perm_set=perm_def)]
1153 1155
1154 1156 compute = caches.conditional_cache(
1155 1157 'long_term', 'repo_acl_ids',
1156 1158 condition=cache, func=_cached_repo_acl)
1157 1159 return compute(self.user_id, perms, name_filter)
1158 1160
1159 1161 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1160 1162 """
1161 1163 Returns list of repository group ids that user have access to based on given
1162 1164 perms. The cache flag should be only used in cases that are used for
1163 1165 display purposes, NOT IN ANY CASE for permission checks.
1164 1166 """
1165 1167 from rhodecode.model.scm import RepoGroupList
1166 1168 if not perms:
1167 1169 perms = [
1168 1170 'group.read', 'group.write', 'group.admin']
1169 1171
1170 1172 def _cached_repo_group_acl(user_id, perm_def, name_filter):
1171 1173 qry = RepoGroup.query()
1172 1174 if name_filter:
1173 1175 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1174 1176 qry = qry.filter(
1175 1177 RepoGroup.group_name.ilike(ilike_expression))
1176 1178
1177 1179 return [x.group_id for x in
1178 1180 RepoGroupList(qry, perm_set=perm_def)]
1179 1181
1180 1182 compute = caches.conditional_cache(
1181 1183 'long_term', 'repo_group_acl_ids',
1182 1184 condition=cache, func=_cached_repo_group_acl)
1183 1185 return compute(self.user_id, perms, name_filter)
1184 1186
1185 1187 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1186 1188 """
1187 1189 Returns list of user group ids that user have access to based on given
1188 1190 perms. The cache flag should be only used in cases that are used for
1189 1191 display purposes, NOT IN ANY CASE for permission checks.
1190 1192 """
1191 1193 from rhodecode.model.scm import UserGroupList
1192 1194 if not perms:
1193 1195 perms = [
1194 1196 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1195 1197
1196 1198 def _cached_user_group_acl(user_id, perm_def, name_filter):
1197 1199 qry = UserGroup.query()
1198 1200 if name_filter:
1199 1201 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1200 1202 qry = qry.filter(
1201 1203 UserGroup.users_group_name.ilike(ilike_expression))
1202 1204
1203 1205 return [x.users_group_id for x in
1204 1206 UserGroupList(qry, perm_set=perm_def)]
1205 1207
1206 1208 compute = caches.conditional_cache(
1207 1209 'long_term', 'user_group_acl_ids',
1208 1210 condition=cache, func=_cached_user_group_acl)
1209 1211 return compute(self.user_id, perms, name_filter)
1210 1212
1211 1213 @property
1212 1214 def ip_allowed(self):
1213 1215 """
1214 1216 Checks if ip_addr used in constructor is allowed from defined list of
1215 1217 allowed ip_addresses for user
1216 1218
1217 1219 :returns: boolean, True if ip is in allowed ip range
1218 1220 """
1219 1221 # check IP
1220 1222 inherit = self.inherit_default_permissions
1221 1223 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1222 1224 inherit_from_default=inherit)
1223 1225 @property
1224 1226 def personal_repo_group(self):
1225 1227 return RepoGroup.get_user_personal_repo_group(self.user_id)
1226 1228
1227 1229 @LazyProperty
1228 1230 def feed_token(self):
1229 1231 return self.get_instance().feed_token
1230 1232
1231 1233 @classmethod
1232 1234 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1233 1235 allowed_ips = AuthUser.get_allowed_ips(
1234 1236 user_id, cache=True, inherit_from_default=inherit_from_default)
1235 1237 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1236 1238 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
1237 1239 return True
1238 1240 else:
1239 1241 log.info('Access for IP:%s forbidden, '
1240 1242 'not in %s' % (ip_addr, allowed_ips))
1241 1243 return False
1242 1244
1243 1245 def __repr__(self):
1244 1246 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1245 1247 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1246 1248
1247 1249 def set_authenticated(self, authenticated=True):
1248 1250 if self.user_id != self.anonymous_user.user_id:
1249 1251 self.is_authenticated = authenticated
1250 1252
1251 1253 def get_cookie_store(self):
1252 1254 return {
1253 1255 'username': self.username,
1254 1256 'password': md5(self.password or ''),
1255 1257 'user_id': self.user_id,
1256 1258 'is_authenticated': self.is_authenticated
1257 1259 }
1258 1260
1259 1261 @classmethod
1260 1262 def from_cookie_store(cls, cookie_store):
1261 1263 """
1262 1264 Creates AuthUser from a cookie store
1263 1265
1264 1266 :param cls:
1265 1267 :param cookie_store:
1266 1268 """
1267 1269 user_id = cookie_store.get('user_id')
1268 1270 username = cookie_store.get('username')
1269 1271 api_key = cookie_store.get('api_key')
1270 1272 return AuthUser(user_id, api_key, username)
1271 1273
1272 1274 @classmethod
1273 1275 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1274 1276 _set = set()
1275 1277
1276 1278 if inherit_from_default:
1277 1279 default_ips = UserIpMap.query().filter(
1278 1280 UserIpMap.user == User.get_default_user(cache=True))
1279 1281 if cache:
1280 1282 default_ips = default_ips.options(
1281 1283 FromCache("sql_cache_short", "get_user_ips_default"))
1282 1284
1283 1285 # populate from default user
1284 1286 for ip in default_ips:
1285 1287 try:
1286 1288 _set.add(ip.ip_addr)
1287 1289 except ObjectDeletedError:
1288 1290 # since we use heavy caching sometimes it happens that
1289 1291 # we get deleted objects here, we just skip them
1290 1292 pass
1291 1293
1292 1294 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1293 1295 if cache:
1294 1296 user_ips = user_ips.options(
1295 1297 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1296 1298
1297 1299 for ip in user_ips:
1298 1300 try:
1299 1301 _set.add(ip.ip_addr)
1300 1302 except ObjectDeletedError:
1301 1303 # since we use heavy caching sometimes it happens that we get
1302 1304 # deleted objects here, we just skip them
1303 1305 pass
1304 1306 return _set or set(['0.0.0.0/0', '::/0'])
1305 1307
1306 1308
1307 1309 def set_available_permissions(settings):
1308 1310 """
1309 1311 This function will propagate pyramid settings with all available defined
1310 1312 permission given in db. We don't want to check each time from db for new
1311 1313 permissions since adding a new permission also requires application restart
1312 1314 ie. to decorate new views with the newly created permission
1313 1315
1314 1316 :param settings: current pyramid registry.settings
1315 1317
1316 1318 """
1317 1319 log.debug('auth: getting information about all available permissions')
1318 1320 try:
1319 1321 sa = meta.Session
1320 1322 all_perms = sa.query(Permission).all()
1321 1323 settings.setdefault('available_permissions',
1322 1324 [x.permission_name for x in all_perms])
1323 1325 log.debug('auth: set available permissions')
1324 1326 except Exception:
1325 1327 log.exception('Failed to fetch permissions from the database.')
1326 1328 raise
1327 1329
1328 1330
1329 1331 def get_csrf_token(session, force_new=False, save_if_missing=True):
1330 1332 """
1331 1333 Return the current authentication token, creating one if one doesn't
1332 1334 already exist and the save_if_missing flag is present.
1333 1335
1334 1336 :param session: pass in the pyramid session, else we use the global ones
1335 1337 :param force_new: force to re-generate the token and store it in session
1336 1338 :param save_if_missing: save the newly generated token if it's missing in
1337 1339 session
1338 1340 """
1339 1341 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1340 1342 # from pyramid.csrf import get_csrf_token
1341 1343
1342 1344 if (csrf_token_key not in session and save_if_missing) or force_new:
1343 1345 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1344 1346 session[csrf_token_key] = token
1345 1347 if hasattr(session, 'save'):
1346 1348 session.save()
1347 1349 return session.get(csrf_token_key)
1348 1350
1349 1351
1350 1352 def get_request(perm_class_instance):
1351 1353 from pyramid.threadlocal import get_current_request
1352 1354 pyramid_request = get_current_request()
1353 1355 return pyramid_request
1354 1356
1355 1357
1356 1358 # CHECK DECORATORS
1357 1359 class CSRFRequired(object):
1358 1360 """
1359 1361 Decorator for authenticating a form
1360 1362
1361 1363 This decorator uses an authorization token stored in the client's
1362 1364 session for prevention of certain Cross-site request forgery (CSRF)
1363 1365 attacks (See
1364 1366 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1365 1367 information).
1366 1368
1367 1369 For use with the ``webhelpers.secure_form`` helper functions.
1368 1370
1369 1371 """
1370 1372 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1371 1373 except_methods=None):
1372 1374 self.token = token
1373 1375 self.header = header
1374 1376 self.except_methods = except_methods or []
1375 1377
1376 1378 def __call__(self, func):
1377 1379 return get_cython_compat_decorator(self.__wrapper, func)
1378 1380
1379 1381 def _get_csrf(self, _request):
1380 1382 return _request.POST.get(self.token, _request.headers.get(self.header))
1381 1383
1382 1384 def check_csrf(self, _request, cur_token):
1383 1385 supplied_token = self._get_csrf(_request)
1384 1386 return supplied_token and supplied_token == cur_token
1385 1387
1386 1388 def _get_request(self):
1387 1389 return get_request(self)
1388 1390
1389 1391 def __wrapper(self, func, *fargs, **fkwargs):
1390 1392 request = self._get_request()
1391 1393
1392 1394 if request.method in self.except_methods:
1393 1395 return func(*fargs, **fkwargs)
1394 1396
1395 1397 cur_token = get_csrf_token(request.session, save_if_missing=False)
1396 1398 if self.check_csrf(request, cur_token):
1397 1399 if request.POST.get(self.token):
1398 1400 del request.POST[self.token]
1399 1401 return func(*fargs, **fkwargs)
1400 1402 else:
1401 1403 reason = 'token-missing'
1402 1404 supplied_token = self._get_csrf(request)
1403 1405 if supplied_token and cur_token != supplied_token:
1404 1406 reason = 'token-mismatch [%s:%s]' % (
1405 1407 cur_token or ''[:6], supplied_token or ''[:6])
1406 1408
1407 1409 csrf_message = \
1408 1410 ("Cross-site request forgery detected, request denied. See "
1409 1411 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1410 1412 "more information.")
1411 1413 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1412 1414 'REMOTE_ADDR:%s, HEADERS:%s' % (
1413 1415 request, reason, request.remote_addr, request.headers))
1414 1416
1415 1417 raise HTTPForbidden(explanation=csrf_message)
1416 1418
1417 1419
1418 1420 class LoginRequired(object):
1419 1421 """
1420 1422 Must be logged in to execute this function else
1421 1423 redirect to login page
1422 1424
1423 1425 :param api_access: if enabled this checks only for valid auth token
1424 1426 and grants access based on valid token
1425 1427 """
1426 1428 def __init__(self, auth_token_access=None):
1427 1429 self.auth_token_access = auth_token_access
1428 1430
1429 1431 def __call__(self, func):
1430 1432 return get_cython_compat_decorator(self.__wrapper, func)
1431 1433
1432 1434 def _get_request(self):
1433 1435 return get_request(self)
1434 1436
1435 1437 def __wrapper(self, func, *fargs, **fkwargs):
1436 1438 from rhodecode.lib import helpers as h
1437 1439 cls = fargs[0]
1438 1440 user = cls._rhodecode_user
1439 1441 request = self._get_request()
1440 1442 _ = request.translate
1441 1443
1442 1444 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1443 1445 log.debug('Starting login restriction checks for user: %s' % (user,))
1444 1446 # check if our IP is allowed
1445 1447 ip_access_valid = True
1446 1448 if not user.ip_allowed:
1447 1449 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1448 1450 category='warning')
1449 1451 ip_access_valid = False
1450 1452
1451 1453 # check if we used an APIKEY and it's a valid one
1452 1454 # defined white-list of controllers which API access will be enabled
1453 1455 _auth_token = request.GET.get(
1454 1456 'auth_token', '') or request.GET.get('api_key', '')
1455 1457 auth_token_access_valid = allowed_auth_token_access(
1456 1458 loc, auth_token=_auth_token)
1457 1459
1458 1460 # explicit controller is enabled or API is in our whitelist
1459 1461 if self.auth_token_access or auth_token_access_valid:
1460 1462 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1461 1463 db_user = user.get_instance()
1462 1464
1463 1465 if db_user:
1464 1466 if self.auth_token_access:
1465 1467 roles = self.auth_token_access
1466 1468 else:
1467 1469 roles = [UserApiKeys.ROLE_HTTP]
1468 1470 token_match = db_user.authenticate_by_token(
1469 1471 _auth_token, roles=roles)
1470 1472 else:
1471 1473 log.debug('Unable to fetch db instance for auth user: %s', user)
1472 1474 token_match = False
1473 1475
1474 1476 if _auth_token and token_match:
1475 1477 auth_token_access_valid = True
1476 1478 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1477 1479 else:
1478 1480 auth_token_access_valid = False
1479 1481 if not _auth_token:
1480 1482 log.debug("AUTH TOKEN *NOT* present in request")
1481 1483 else:
1482 1484 log.warning(
1483 1485 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1484 1486
1485 1487 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1486 1488 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1487 1489 else 'AUTH_TOKEN_AUTH'
1488 1490
1489 1491 if ip_access_valid and (
1490 1492 user.is_authenticated or auth_token_access_valid):
1491 1493 log.info(
1492 1494 'user %s authenticating with:%s IS authenticated on func %s'
1493 1495 % (user, reason, loc))
1494 1496
1495 1497 # update user data to check last activity
1496 1498 user.update_lastactivity()
1497 1499 Session().commit()
1498 1500 return func(*fargs, **fkwargs)
1499 1501 else:
1500 1502 log.warning(
1501 1503 'user %s authenticating with:%s NOT authenticated on '
1502 1504 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1503 1505 % (user, reason, loc, ip_access_valid,
1504 1506 auth_token_access_valid))
1505 1507 # we preserve the get PARAM
1506 1508 came_from = get_came_from(request)
1507 1509
1508 1510 log.debug('redirecting to login page with %s' % (came_from,))
1509 1511 raise HTTPFound(
1510 1512 h.route_path('login', _query={'came_from': came_from}))
1511 1513
1512 1514
1513 1515 class NotAnonymous(object):
1514 1516 """
1515 1517 Must be logged in to execute this function else
1516 1518 redirect to login page
1517 1519 """
1518 1520
1519 1521 def __call__(self, func):
1520 1522 return get_cython_compat_decorator(self.__wrapper, func)
1521 1523
1522 1524 def _get_request(self):
1523 1525 return get_request(self)
1524 1526
1525 1527 def __wrapper(self, func, *fargs, **fkwargs):
1526 1528 import rhodecode.lib.helpers as h
1527 1529 cls = fargs[0]
1528 1530 self.user = cls._rhodecode_user
1529 1531 request = self._get_request()
1530 1532 _ = request.translate
1531 1533 log.debug('Checking if user is not anonymous @%s' % cls)
1532 1534
1533 1535 anonymous = self.user.username == User.DEFAULT_USER
1534 1536
1535 1537 if anonymous:
1536 1538 came_from = get_came_from(request)
1537 1539 h.flash(_('You need to be a registered user to '
1538 1540 'perform this action'),
1539 1541 category='warning')
1540 1542 raise HTTPFound(
1541 1543 h.route_path('login', _query={'came_from': came_from}))
1542 1544 else:
1543 1545 return func(*fargs, **fkwargs)
1544 1546
1545 1547
1546 1548 class PermsDecorator(object):
1547 1549 """
1548 1550 Base class for controller decorators, we extract the current user from
1549 1551 the class itself, which has it stored in base controllers
1550 1552 """
1551 1553
1552 1554 def __init__(self, *required_perms):
1553 1555 self.required_perms = set(required_perms)
1554 1556
1555 1557 def __call__(self, func):
1556 1558 return get_cython_compat_decorator(self.__wrapper, func)
1557 1559
1558 1560 def _get_request(self):
1559 1561 return get_request(self)
1560 1562
1561 1563 def __wrapper(self, func, *fargs, **fkwargs):
1562 1564 import rhodecode.lib.helpers as h
1563 1565 cls = fargs[0]
1564 1566 _user = cls._rhodecode_user
1565 1567 request = self._get_request()
1566 1568 _ = request.translate
1567 1569
1568 1570 log.debug('checking %s permissions %s for %s %s',
1569 1571 self.__class__.__name__, self.required_perms, cls, _user)
1570 1572
1571 1573 if self.check_permissions(_user):
1572 1574 log.debug('Permission granted for %s %s', cls, _user)
1573 1575 return func(*fargs, **fkwargs)
1574 1576
1575 1577 else:
1576 1578 log.debug('Permission denied for %s %s', cls, _user)
1577 1579 anonymous = _user.username == User.DEFAULT_USER
1578 1580
1579 1581 if anonymous:
1580 1582 came_from = get_came_from(self._get_request())
1581 1583 h.flash(_('You need to be signed in to view this page'),
1582 1584 category='warning')
1583 1585 raise HTTPFound(
1584 1586 h.route_path('login', _query={'came_from': came_from}))
1585 1587
1586 1588 else:
1587 1589 # redirect with 404 to prevent resource discovery
1588 1590 raise HTTPNotFound()
1589 1591
1590 1592 def check_permissions(self, user):
1591 1593 """Dummy function for overriding"""
1592 1594 raise NotImplementedError(
1593 1595 'You have to write this function in child class')
1594 1596
1595 1597
1596 1598 class HasPermissionAllDecorator(PermsDecorator):
1597 1599 """
1598 1600 Checks for access permission for all given predicates. All of them
1599 1601 have to be meet in order to fulfill the request
1600 1602 """
1601 1603
1602 1604 def check_permissions(self, user):
1603 1605 perms = user.permissions_with_scope({})
1604 1606 if self.required_perms.issubset(perms['global']):
1605 1607 return True
1606 1608 return False
1607 1609
1608 1610
1609 1611 class HasPermissionAnyDecorator(PermsDecorator):
1610 1612 """
1611 1613 Checks for access permission for any of given predicates. In order to
1612 1614 fulfill the request any of predicates must be meet
1613 1615 """
1614 1616
1615 1617 def check_permissions(self, user):
1616 1618 perms = user.permissions_with_scope({})
1617 1619 if self.required_perms.intersection(perms['global']):
1618 1620 return True
1619 1621 return False
1620 1622
1621 1623
1622 1624 class HasRepoPermissionAllDecorator(PermsDecorator):
1623 1625 """
1624 1626 Checks for access permission for all given predicates for specific
1625 1627 repository. All of them have to be meet in order to fulfill the request
1626 1628 """
1627 1629 def _get_repo_name(self):
1628 1630 _request = self._get_request()
1629 1631 return get_repo_slug(_request)
1630 1632
1631 1633 def check_permissions(self, user):
1632 1634 perms = user.permissions
1633 1635 repo_name = self._get_repo_name()
1634 1636
1635 1637 try:
1636 1638 user_perms = set([perms['repositories'][repo_name]])
1637 1639 except KeyError:
1638 1640 log.debug('cannot locate repo with name: `%s` in permissions defs',
1639 1641 repo_name)
1640 1642 return False
1641 1643
1642 1644 log.debug('checking `%s` permissions for repo `%s`',
1643 1645 user_perms, repo_name)
1644 1646 if self.required_perms.issubset(user_perms):
1645 1647 return True
1646 1648 return False
1647 1649
1648 1650
1649 1651 class HasRepoPermissionAnyDecorator(PermsDecorator):
1650 1652 """
1651 1653 Checks for access permission for any of given predicates for specific
1652 1654 repository. In order to fulfill the request any of predicates must be meet
1653 1655 """
1654 1656 def _get_repo_name(self):
1655 1657 _request = self._get_request()
1656 1658 return get_repo_slug(_request)
1657 1659
1658 1660 def check_permissions(self, user):
1659 1661 perms = user.permissions
1660 1662 repo_name = self._get_repo_name()
1661 1663
1662 1664 try:
1663 1665 user_perms = set([perms['repositories'][repo_name]])
1664 1666 except KeyError:
1665 1667 log.debug(
1666 1668 'cannot locate repo with name: `%s` in permissions defs',
1667 1669 repo_name)
1668 1670 return False
1669 1671
1670 1672 log.debug('checking `%s` permissions for repo `%s`',
1671 1673 user_perms, repo_name)
1672 1674 if self.required_perms.intersection(user_perms):
1673 1675 return True
1674 1676 return False
1675 1677
1676 1678
1677 1679 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1678 1680 """
1679 1681 Checks for access permission for all given predicates for specific
1680 1682 repository group. All of them have to be meet in order to
1681 1683 fulfill the request
1682 1684 """
1683 1685 def _get_repo_group_name(self):
1684 1686 _request = self._get_request()
1685 1687 return get_repo_group_slug(_request)
1686 1688
1687 1689 def check_permissions(self, user):
1688 1690 perms = user.permissions
1689 1691 group_name = self._get_repo_group_name()
1690 1692 try:
1691 1693 user_perms = set([perms['repositories_groups'][group_name]])
1692 1694 except KeyError:
1693 1695 log.debug(
1694 1696 'cannot locate repo group with name: `%s` in permissions defs',
1695 1697 group_name)
1696 1698 return False
1697 1699
1698 1700 log.debug('checking `%s` permissions for repo group `%s`',
1699 1701 user_perms, group_name)
1700 1702 if self.required_perms.issubset(user_perms):
1701 1703 return True
1702 1704 return False
1703 1705
1704 1706
1705 1707 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1706 1708 """
1707 1709 Checks for access permission for any of given predicates for specific
1708 1710 repository group. In order to fulfill the request any
1709 1711 of predicates must be met
1710 1712 """
1711 1713 def _get_repo_group_name(self):
1712 1714 _request = self._get_request()
1713 1715 return get_repo_group_slug(_request)
1714 1716
1715 1717 def check_permissions(self, user):
1716 1718 perms = user.permissions
1717 1719 group_name = self._get_repo_group_name()
1718 1720
1719 1721 try:
1720 1722 user_perms = set([perms['repositories_groups'][group_name]])
1721 1723 except KeyError:
1722 1724 log.debug(
1723 1725 'cannot locate repo group with name: `%s` in permissions defs',
1724 1726 group_name)
1725 1727 return False
1726 1728
1727 1729 log.debug('checking `%s` permissions for repo group `%s`',
1728 1730 user_perms, group_name)
1729 1731 if self.required_perms.intersection(user_perms):
1730 1732 return True
1731 1733 return False
1732 1734
1733 1735
1734 1736 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1735 1737 """
1736 1738 Checks for access permission for all given predicates for specific
1737 1739 user group. All of them have to be meet in order to fulfill the request
1738 1740 """
1739 1741 def _get_user_group_name(self):
1740 1742 _request = self._get_request()
1741 1743 return get_user_group_slug(_request)
1742 1744
1743 1745 def check_permissions(self, user):
1744 1746 perms = user.permissions
1745 1747 group_name = self._get_user_group_name()
1746 1748 try:
1747 1749 user_perms = set([perms['user_groups'][group_name]])
1748 1750 except KeyError:
1749 1751 return False
1750 1752
1751 1753 if self.required_perms.issubset(user_perms):
1752 1754 return True
1753 1755 return False
1754 1756
1755 1757
1756 1758 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1757 1759 """
1758 1760 Checks for access permission for any of given predicates for specific
1759 1761 user group. In order to fulfill the request any of predicates must be meet
1760 1762 """
1761 1763 def _get_user_group_name(self):
1762 1764 _request = self._get_request()
1763 1765 return get_user_group_slug(_request)
1764 1766
1765 1767 def check_permissions(self, user):
1766 1768 perms = user.permissions
1767 1769 group_name = self._get_user_group_name()
1768 1770 try:
1769 1771 user_perms = set([perms['user_groups'][group_name]])
1770 1772 except KeyError:
1771 1773 return False
1772 1774
1773 1775 if self.required_perms.intersection(user_perms):
1774 1776 return True
1775 1777 return False
1776 1778
1777 1779
1778 1780 # CHECK FUNCTIONS
1779 1781 class PermsFunction(object):
1780 1782 """Base function for other check functions"""
1781 1783
1782 1784 def __init__(self, *perms):
1783 1785 self.required_perms = set(perms)
1784 1786 self.repo_name = None
1785 1787 self.repo_group_name = None
1786 1788 self.user_group_name = None
1787 1789
1788 1790 def __bool__(self):
1789 1791 frame = inspect.currentframe()
1790 1792 stack_trace = traceback.format_stack(frame)
1791 1793 log.error('Checking bool value on a class instance of perm '
1792 1794 'function is not allowed: %s' % ''.join(stack_trace))
1793 1795 # rather than throwing errors, here we always return False so if by
1794 1796 # accident someone checks truth for just an instance it will always end
1795 1797 # up in returning False
1796 1798 return False
1797 1799 __nonzero__ = __bool__
1798 1800
1799 1801 def __call__(self, check_location='', user=None):
1800 1802 if not user:
1801 1803 log.debug('Using user attribute from global request')
1802 1804 # TODO: remove this someday,put as user as attribute here
1803 1805 request = self._get_request()
1804 1806 user = request.user
1805 1807
1806 1808 # init auth user if not already given
1807 1809 if not isinstance(user, AuthUser):
1808 1810 log.debug('Wrapping user %s into AuthUser', user)
1809 1811 user = AuthUser(user.user_id)
1810 1812
1811 1813 cls_name = self.__class__.__name__
1812 1814 check_scope = self._get_check_scope(cls_name)
1813 1815 check_location = check_location or 'unspecified location'
1814 1816
1815 1817 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1816 1818 self.required_perms, user, check_scope, check_location)
1817 1819 if not user:
1818 1820 log.warning('Empty user given for permission check')
1819 1821 return False
1820 1822
1821 1823 if self.check_permissions(user):
1822 1824 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1823 1825 check_scope, user, check_location)
1824 1826 return True
1825 1827
1826 1828 else:
1827 1829 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1828 1830 check_scope, user, check_location)
1829 1831 return False
1830 1832
1831 1833 def _get_request(self):
1832 1834 return get_request(self)
1833 1835
1834 1836 def _get_check_scope(self, cls_name):
1835 1837 return {
1836 1838 'HasPermissionAll': 'GLOBAL',
1837 1839 'HasPermissionAny': 'GLOBAL',
1838 1840 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1839 1841 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1840 1842 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1841 1843 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1842 1844 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1843 1845 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1844 1846 }.get(cls_name, '?:%s' % cls_name)
1845 1847
1846 1848 def check_permissions(self, user):
1847 1849 """Dummy function for overriding"""
1848 1850 raise Exception('You have to write this function in child class')
1849 1851
1850 1852
1851 1853 class HasPermissionAll(PermsFunction):
1852 1854 def check_permissions(self, user):
1853 1855 perms = user.permissions_with_scope({})
1854 1856 if self.required_perms.issubset(perms.get('global')):
1855 1857 return True
1856 1858 return False
1857 1859
1858 1860
1859 1861 class HasPermissionAny(PermsFunction):
1860 1862 def check_permissions(self, user):
1861 1863 perms = user.permissions_with_scope({})
1862 1864 if self.required_perms.intersection(perms.get('global')):
1863 1865 return True
1864 1866 return False
1865 1867
1866 1868
1867 1869 class HasRepoPermissionAll(PermsFunction):
1868 1870 def __call__(self, repo_name=None, check_location='', user=None):
1869 1871 self.repo_name = repo_name
1870 1872 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1871 1873
1872 1874 def _get_repo_name(self):
1873 1875 if not self.repo_name:
1874 1876 _request = self._get_request()
1875 1877 self.repo_name = get_repo_slug(_request)
1876 1878 return self.repo_name
1877 1879
1878 1880 def check_permissions(self, user):
1879 1881 self.repo_name = self._get_repo_name()
1880 1882 perms = user.permissions
1881 1883 try:
1882 1884 user_perms = set([perms['repositories'][self.repo_name]])
1883 1885 except KeyError:
1884 1886 return False
1885 1887 if self.required_perms.issubset(user_perms):
1886 1888 return True
1887 1889 return False
1888 1890
1889 1891
1890 1892 class HasRepoPermissionAny(PermsFunction):
1891 1893 def __call__(self, repo_name=None, check_location='', user=None):
1892 1894 self.repo_name = repo_name
1893 1895 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1894 1896
1895 1897 def _get_repo_name(self):
1896 1898 if not self.repo_name:
1897 1899 _request = self._get_request()
1898 1900 self.repo_name = get_repo_slug(_request)
1899 1901 return self.repo_name
1900 1902
1901 1903 def check_permissions(self, user):
1902 1904 self.repo_name = self._get_repo_name()
1903 1905 perms = user.permissions
1904 1906 try:
1905 1907 user_perms = set([perms['repositories'][self.repo_name]])
1906 1908 except KeyError:
1907 1909 return False
1908 1910 if self.required_perms.intersection(user_perms):
1909 1911 return True
1910 1912 return False
1911 1913
1912 1914
1913 1915 class HasRepoGroupPermissionAny(PermsFunction):
1914 1916 def __call__(self, group_name=None, check_location='', user=None):
1915 1917 self.repo_group_name = group_name
1916 1918 return super(HasRepoGroupPermissionAny, self).__call__(
1917 1919 check_location, user)
1918 1920
1919 1921 def check_permissions(self, user):
1920 1922 perms = user.permissions
1921 1923 try:
1922 1924 user_perms = set(
1923 1925 [perms['repositories_groups'][self.repo_group_name]])
1924 1926 except KeyError:
1925 1927 return False
1926 1928 if self.required_perms.intersection(user_perms):
1927 1929 return True
1928 1930 return False
1929 1931
1930 1932
1931 1933 class HasRepoGroupPermissionAll(PermsFunction):
1932 1934 def __call__(self, group_name=None, check_location='', user=None):
1933 1935 self.repo_group_name = group_name
1934 1936 return super(HasRepoGroupPermissionAll, self).__call__(
1935 1937 check_location, user)
1936 1938
1937 1939 def check_permissions(self, user):
1938 1940 perms = user.permissions
1939 1941 try:
1940 1942 user_perms = set(
1941 1943 [perms['repositories_groups'][self.repo_group_name]])
1942 1944 except KeyError:
1943 1945 return False
1944 1946 if self.required_perms.issubset(user_perms):
1945 1947 return True
1946 1948 return False
1947 1949
1948 1950
1949 1951 class HasUserGroupPermissionAny(PermsFunction):
1950 1952 def __call__(self, user_group_name=None, check_location='', user=None):
1951 1953 self.user_group_name = user_group_name
1952 1954 return super(HasUserGroupPermissionAny, self).__call__(
1953 1955 check_location, user)
1954 1956
1955 1957 def check_permissions(self, user):
1956 1958 perms = user.permissions
1957 1959 try:
1958 1960 user_perms = set([perms['user_groups'][self.user_group_name]])
1959 1961 except KeyError:
1960 1962 return False
1961 1963 if self.required_perms.intersection(user_perms):
1962 1964 return True
1963 1965 return False
1964 1966
1965 1967
1966 1968 class HasUserGroupPermissionAll(PermsFunction):
1967 1969 def __call__(self, user_group_name=None, check_location='', user=None):
1968 1970 self.user_group_name = user_group_name
1969 1971 return super(HasUserGroupPermissionAll, self).__call__(
1970 1972 check_location, user)
1971 1973
1972 1974 def check_permissions(self, user):
1973 1975 perms = user.permissions
1974 1976 try:
1975 1977 user_perms = set([perms['user_groups'][self.user_group_name]])
1976 1978 except KeyError:
1977 1979 return False
1978 1980 if self.required_perms.issubset(user_perms):
1979 1981 return True
1980 1982 return False
1981 1983
1982 1984
1983 1985 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1984 1986 class HasPermissionAnyMiddleware(object):
1985 1987 def __init__(self, *perms):
1986 1988 self.required_perms = set(perms)
1987 1989
1988 1990 def __call__(self, user, repo_name):
1989 1991 # repo_name MUST be unicode, since we handle keys in permission
1990 1992 # dict by unicode
1991 1993 repo_name = safe_unicode(repo_name)
1992 1994 user = AuthUser(user.user_id)
1993 1995 log.debug(
1994 1996 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1995 1997 self.required_perms, user, repo_name)
1996 1998
1997 1999 if self.check_permissions(user, repo_name):
1998 2000 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1999 2001 repo_name, user, 'PermissionMiddleware')
2000 2002 return True
2001 2003
2002 2004 else:
2003 2005 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2004 2006 repo_name, user, 'PermissionMiddleware')
2005 2007 return False
2006 2008
2007 2009 def check_permissions(self, user, repo_name):
2008 2010 perms = user.permissions_with_scope({'repo_name': repo_name})
2009 2011
2010 2012 try:
2011 2013 user_perms = set([perms['repositories'][repo_name]])
2012 2014 except Exception:
2013 2015 log.exception('Error while accessing user permissions')
2014 2016 return False
2015 2017
2016 2018 if self.required_perms.intersection(user_perms):
2017 2019 return True
2018 2020 return False
2019 2021
2020 2022
2021 2023 # SPECIAL VERSION TO HANDLE API AUTH
2022 2024 class _BaseApiPerm(object):
2023 2025 def __init__(self, *perms):
2024 2026 self.required_perms = set(perms)
2025 2027
2026 2028 def __call__(self, check_location=None, user=None, repo_name=None,
2027 2029 group_name=None, user_group_name=None):
2028 2030 cls_name = self.__class__.__name__
2029 2031 check_scope = 'global:%s' % (self.required_perms,)
2030 2032 if repo_name:
2031 2033 check_scope += ', repo_name:%s' % (repo_name,)
2032 2034
2033 2035 if group_name:
2034 2036 check_scope += ', repo_group_name:%s' % (group_name,)
2035 2037
2036 2038 if user_group_name:
2037 2039 check_scope += ', user_group_name:%s' % (user_group_name,)
2038 2040
2039 2041 log.debug(
2040 2042 'checking cls:%s %s %s @ %s'
2041 2043 % (cls_name, self.required_perms, check_scope, check_location))
2042 2044 if not user:
2043 2045 log.debug('Empty User passed into arguments')
2044 2046 return False
2045 2047
2046 2048 # process user
2047 2049 if not isinstance(user, AuthUser):
2048 2050 user = AuthUser(user.user_id)
2049 2051 if not check_location:
2050 2052 check_location = 'unspecified'
2051 2053 if self.check_permissions(user.permissions, repo_name, group_name,
2052 2054 user_group_name):
2053 2055 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2054 2056 check_scope, user, check_location)
2055 2057 return True
2056 2058
2057 2059 else:
2058 2060 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2059 2061 check_scope, user, check_location)
2060 2062 return False
2061 2063
2062 2064 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2063 2065 user_group_name=None):
2064 2066 """
2065 2067 implement in child class should return True if permissions are ok,
2066 2068 False otherwise
2067 2069
2068 2070 :param perm_defs: dict with permission definitions
2069 2071 :param repo_name: repo name
2070 2072 """
2071 2073 raise NotImplementedError()
2072 2074
2073 2075
2074 2076 class HasPermissionAllApi(_BaseApiPerm):
2075 2077 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2076 2078 user_group_name=None):
2077 2079 if self.required_perms.issubset(perm_defs.get('global')):
2078 2080 return True
2079 2081 return False
2080 2082
2081 2083
2082 2084 class HasPermissionAnyApi(_BaseApiPerm):
2083 2085 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2084 2086 user_group_name=None):
2085 2087 if self.required_perms.intersection(perm_defs.get('global')):
2086 2088 return True
2087 2089 return False
2088 2090
2089 2091
2090 2092 class HasRepoPermissionAllApi(_BaseApiPerm):
2091 2093 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2092 2094 user_group_name=None):
2093 2095 try:
2094 2096 _user_perms = set([perm_defs['repositories'][repo_name]])
2095 2097 except KeyError:
2096 2098 log.warning(traceback.format_exc())
2097 2099 return False
2098 2100 if self.required_perms.issubset(_user_perms):
2099 2101 return True
2100 2102 return False
2101 2103
2102 2104
2103 2105 class HasRepoPermissionAnyApi(_BaseApiPerm):
2104 2106 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2105 2107 user_group_name=None):
2106 2108 try:
2107 2109 _user_perms = set([perm_defs['repositories'][repo_name]])
2108 2110 except KeyError:
2109 2111 log.warning(traceback.format_exc())
2110 2112 return False
2111 2113 if self.required_perms.intersection(_user_perms):
2112 2114 return True
2113 2115 return False
2114 2116
2115 2117
2116 2118 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2117 2119 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2118 2120 user_group_name=None):
2119 2121 try:
2120 2122 _user_perms = set([perm_defs['repositories_groups'][group_name]])
2121 2123 except KeyError:
2122 2124 log.warning(traceback.format_exc())
2123 2125 return False
2124 2126 if self.required_perms.intersection(_user_perms):
2125 2127 return True
2126 2128 return False
2127 2129
2128 2130
2129 2131 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2130 2132 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2131 2133 user_group_name=None):
2132 2134 try:
2133 2135 _user_perms = set([perm_defs['repositories_groups'][group_name]])
2134 2136 except KeyError:
2135 2137 log.warning(traceback.format_exc())
2136 2138 return False
2137 2139 if self.required_perms.issubset(_user_perms):
2138 2140 return True
2139 2141 return False
2140 2142
2141 2143
2142 2144 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2143 2145 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2144 2146 user_group_name=None):
2145 2147 try:
2146 2148 _user_perms = set([perm_defs['user_groups'][user_group_name]])
2147 2149 except KeyError:
2148 2150 log.warning(traceback.format_exc())
2149 2151 return False
2150 2152 if self.required_perms.intersection(_user_perms):
2151 2153 return True
2152 2154 return False
2153 2155
2154 2156
2155 2157 def check_ip_access(source_ip, allowed_ips=None):
2156 2158 """
2157 2159 Checks if source_ip is a subnet of any of allowed_ips.
2158 2160
2159 2161 :param source_ip:
2160 2162 :param allowed_ips: list of allowed ips together with mask
2161 2163 """
2162 2164 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
2163 2165 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2164 2166 if isinstance(allowed_ips, (tuple, list, set)):
2165 2167 for ip in allowed_ips:
2166 2168 ip = safe_unicode(ip)
2167 2169 try:
2168 2170 network_address = ipaddress.ip_network(ip, strict=False)
2169 2171 if source_ip_address in network_address:
2170 2172 log.debug('IP %s is network %s' %
2171 2173 (source_ip_address, network_address))
2172 2174 return True
2173 2175 # for any case we cannot determine the IP, don't crash just
2174 2176 # skip it and log as error, we want to say forbidden still when
2175 2177 # sending bad IP
2176 2178 except Exception:
2177 2179 log.error(traceback.format_exc())
2178 2180 continue
2179 2181 return False
2180 2182
2181 2183
2182 2184 def get_cython_compat_decorator(wrapper, func):
2183 2185 """
2184 2186 Creates a cython compatible decorator. The previously used
2185 2187 decorator.decorator() function seems to be incompatible with cython.
2186 2188
2187 2189 :param wrapper: __wrapper method of the decorator class
2188 2190 :param func: decorated function
2189 2191 """
2190 2192 @wraps(func)
2191 2193 def local_wrapper(*args, **kwds):
2192 2194 return wrapper(func, *args, **kwds)
2193 2195 local_wrapper.__wrapped__ = func
2194 2196 return local_wrapper
2195 2197
2196 2198
General Comments 0
You need to be logged in to leave comments. Login now