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