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