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