##// END OF EJS Templates
permissions: show overridden permissions in permission summary page
dan -
r100:5b55789f default
parent child Browse files
Show More
@@ -1,1828 +1,1898 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 inspect
26 26 import collections
27 27 import fnmatch
28 28 import hashlib
29 29 import itertools
30 30 import logging
31 31 import os
32 32 import random
33 33 import time
34 34 import traceback
35 35 from functools import wraps
36 36
37 37 import ipaddress
38 38 from pylons import url, request
39 39 from pylons.controllers.util import abort, redirect
40 40 from pylons.i18n.translation import _
41 41 from sqlalchemy import or_
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)
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
102 102 def hash_create(self, str_):
103 103 """
104 104 hash the string using
105 105
106 106 :param str_: password to hash
107 107 """
108 108 raise NotImplementedError
109 109
110 110 def hash_check_with_upgrade(self, password, hashed):
111 111 """
112 112 Returns tuple in which first element is boolean that states that
113 113 given password matches it's hashed version, and the second is new hash
114 114 of the password, in case this password should be migrated to new
115 115 cipher.
116 116 """
117 117 checked_hash = self.hash_check(password, hashed)
118 118 return checked_hash, None
119 119
120 120 def hash_check(self, password, hashed):
121 121 """
122 122 Checks matching password with it's hashed value.
123 123
124 124 :param password: password
125 125 :param hashed: password in hashed form
126 126 """
127 127 raise NotImplementedError
128 128
129 129 def _assert_bytes(self, value):
130 130 """
131 131 Passing in an `unicode` object can lead to hard to detect issues
132 132 if passwords contain non-ascii characters. Doing a type check
133 133 during runtime, so that such mistakes are detected early on.
134 134 """
135 135 if not isinstance(value, str):
136 136 raise TypeError(
137 137 "Bytestring required as input, got %r." % (value, ))
138 138
139 139
140 140 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
141 141
142 142 def hash_create(self, str_):
143 143 self._assert_bytes(str_)
144 144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
145 145
146 146 def hash_check_with_upgrade(self, password, hashed):
147 147 """
148 148 Returns tuple in which first element is boolean that states that
149 149 given password matches it's hashed version, and the second is new hash
150 150 of the password, in case this password should be migrated to new
151 151 cipher.
152 152
153 153 This implements special upgrade logic which works like that:
154 154 - check if the given password == bcrypted hash, if yes then we
155 155 properly used password and it was already in bcrypt. Proceed
156 156 without any changes
157 157 - if bcrypt hash check is not working try with sha256. If hash compare
158 158 is ok, it means we using correct but old hashed password. indicate
159 159 hash change and proceed
160 160 """
161 161
162 162 new_hash = None
163 163
164 164 # regular pw check
165 165 password_match_bcrypt = self.hash_check(password, hashed)
166 166
167 167 # now we want to know if the password was maybe from sha256
168 168 # basically calling _RhodeCodeCryptoSha256().hash_check()
169 169 if not password_match_bcrypt:
170 170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
171 171 new_hash = self.hash_create(password) # make new bcrypt hash
172 172 password_match_bcrypt = True
173 173
174 174 return password_match_bcrypt, new_hash
175 175
176 176 def hash_check(self, password, hashed):
177 177 """
178 178 Checks matching password with it's hashed value.
179 179
180 180 :param password: password
181 181 :param hashed: password in hashed form
182 182 """
183 183 self._assert_bytes(password)
184 184 try:
185 185 return bcrypt.hashpw(password, hashed) == hashed
186 186 except ValueError as e:
187 187 # we're having a invalid salt here probably, we should not crash
188 188 # just return with False as it would be a wrong password.
189 189 log.debug('Failed to check password hash using bcrypt %s',
190 190 safe_str(e))
191 191
192 192 return False
193 193
194 194
195 195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
196 196
197 197 def hash_create(self, str_):
198 198 self._assert_bytes(str_)
199 199 return hashlib.sha256(str_).hexdigest()
200 200
201 201 def hash_check(self, password, hashed):
202 202 """
203 203 Checks matching password with it's hashed value.
204 204
205 205 :param password: password
206 206 :param hashed: password in hashed form
207 207 """
208 208 self._assert_bytes(password)
209 209 return hashlib.sha256(password).hexdigest() == hashed
210 210
211 211
212 212 class _RhodeCodeCryptoMd5(_RhodeCodeCryptoBase):
213 213
214 214 def hash_create(self, str_):
215 215 self._assert_bytes(str_)
216 216 return hashlib.md5(str_).hexdigest()
217 217
218 218 def hash_check(self, password, hashed):
219 219 """
220 220 Checks matching password with it's hashed value.
221 221
222 222 :param password: password
223 223 :param hashed: password in hashed form
224 224 """
225 225 self._assert_bytes(password)
226 226 return hashlib.md5(password).hexdigest() == hashed
227 227
228 228
229 229 def crypto_backend():
230 230 """
231 231 Return the matching crypto backend.
232 232
233 233 Selection is based on if we run tests or not, we pick md5 backend to run
234 234 tests faster since BCRYPT is expensive to calculate
235 235 """
236 236 if rhodecode.is_test:
237 237 RhodeCodeCrypto = _RhodeCodeCryptoMd5()
238 238 else:
239 239 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
240 240
241 241 return RhodeCodeCrypto
242 242
243 243
244 244 def get_crypt_password(password):
245 245 """
246 246 Create the hash of `password` with the active crypto backend.
247 247
248 248 :param password: The cleartext password.
249 249 :type password: unicode
250 250 """
251 251 password = safe_str(password)
252 252 return crypto_backend().hash_create(password)
253 253
254 254
255 255 def check_password(password, hashed):
256 256 """
257 257 Check if the value in `password` matches the hash in `hashed`.
258 258
259 259 :param password: The cleartext password.
260 260 :type password: unicode
261 261
262 262 :param hashed: The expected hashed version of the password.
263 263 :type hashed: The hash has to be passed in in text representation.
264 264 """
265 265 password = safe_str(password)
266 266 return crypto_backend().hash_check(password, hashed)
267 267
268 268
269 269 def generate_auth_token(data, salt=None):
270 270 """
271 271 Generates API KEY from given string
272 272 """
273 273
274 274 if salt is None:
275 275 salt = os.urandom(16)
276 276 return hashlib.sha1(safe_str(data) + salt).hexdigest()
277 277
278 278
279 279 class CookieStoreWrapper(object):
280 280
281 281 def __init__(self, cookie_store):
282 282 self.cookie_store = cookie_store
283 283
284 284 def __repr__(self):
285 285 return 'CookieStore<%s>' % (self.cookie_store)
286 286
287 287 def get(self, key, other=None):
288 288 if isinstance(self.cookie_store, dict):
289 289 return self.cookie_store.get(key, other)
290 290 elif isinstance(self.cookie_store, AuthUser):
291 291 return self.cookie_store.__dict__.get(key, other)
292 292
293 293
294 294 def _cached_perms_data(user_id, scope, user_is_admin,
295 295 user_inherit_default_permissions, explicit, algo):
296 296
297 297 permissions = PermissionCalculator(
298 298 user_id, scope, user_is_admin, user_inherit_default_permissions,
299 299 explicit, algo)
300 300 return permissions.calculate()
301 301
302 class PermOrigin:
303 ADMIN = 'superadmin'
304
305 REPO_USER = 'user:%s'
306 REPO_USERGROUP = 'usergroup:%s'
307 REPO_OWNER = 'repo.owner'
308 REPO_DEFAULT = 'repo.default'
309 REPO_PRIVATE = 'repo.private'
310
311 REPOGROUP_USER = 'user:%s'
312 REPOGROUP_USERGROUP = 'usergroup:%s'
313 REPOGROUP_OWNER = 'group.owner'
314 REPOGROUP_DEFAULT = 'group.default'
315
316 USERGROUP_USER = 'user:%s'
317 USERGROUP_USERGROUP = 'usergroup:%s'
318 USERGROUP_OWNER = 'usergroup.owner'
319 USERGROUP_DEFAULT = 'usergroup.default'
320
321
322 class PermOriginDict(dict):
323 """
324 A special dict used for tracking permissions along with their origins.
325
326 `__setitem__` has been overridden to expect a tuple(perm, origin)
327 `__getitem__` will return only the perm
328 `.perm_origin_stack` will return the stack of (perm, origin) set per key
329
330 >>> perms = PermOriginDict()
331 >>> perms['resource'] = 'read', 'default'
332 >>> perms['resource']
333 'read'
334 >>> perms['resource'] = 'write', 'admin'
335 >>> perms['resource']
336 'write'
337 >>> perms.perm_origin_stack
338 {'resource': [('read', 'default'), ('write', 'admin')]}
339 """
340
341
342 def __init__(self, *args, **kw):
343 dict.__init__(self, *args, **kw)
344 self.perm_origin_stack = {}
345
346 def __setitem__(self, key, (perm, origin)):
347 self.perm_origin_stack.setdefault(key, []).append((perm, origin))
348 dict.__setitem__(self, key, perm)
349
302 350
303 351 class PermissionCalculator(object):
304 352
305 353 def __init__(
306 354 self, user_id, scope, user_is_admin,
307 355 user_inherit_default_permissions, explicit, algo):
308 356 self.user_id = user_id
309 357 self.user_is_admin = user_is_admin
310 358 self.inherit_default_permissions = user_inherit_default_permissions
311 359 self.explicit = explicit
312 360 self.algo = algo
313 361
314 362 scope = scope or {}
315 363 self.scope_repo_id = scope.get('repo_id')
316 364 self.scope_repo_group_id = scope.get('repo_group_id')
317 365 self.scope_user_group_id = scope.get('user_group_id')
318 366
319 367 self.default_user_id = User.get_default_user(cache=True).user_id
320 368
321 self.permissions_repositories = {}
322 self.permissions_repository_groups = {}
323 self.permissions_user_groups = {}
369 self.permissions_repositories = PermOriginDict()
370 self.permissions_repository_groups = PermOriginDict()
371 self.permissions_user_groups = PermOriginDict()
324 372 self.permissions_global = set()
325 373
326 374 self.default_repo_perms = Permission.get_default_repo_perms(
327 375 self.default_user_id, self.scope_repo_id)
328 376 self.default_repo_groups_perms = Permission.get_default_group_perms(
329 377 self.default_user_id, self.scope_repo_group_id)
330 378 self.default_user_group_perms = \
331 379 Permission.get_default_user_group_perms(
332 380 self.default_user_id, self.scope_user_group_id)
333 381
334 382 def calculate(self):
335 383 if self.user_is_admin:
336 384 return self._admin_permissions()
337 385
338 386 self._calculate_global_default_permissions()
339 387 self._calculate_global_permissions()
340 388 self._calculate_default_permissions()
341 389 self._calculate_repository_permissions()
342 390 self._calculate_repository_group_permissions()
343 391 self._calculate_user_group_permissions()
344 392 return self._permission_structure()
345 393
346 394 def _admin_permissions(self):
347 395 """
348 396 admin user have all default rights for repositories
349 397 and groups set to admin
350 398 """
351 399 self.permissions_global.add('hg.admin')
352 400 self.permissions_global.add('hg.create.write_on_repogroup.true')
353 401
354 402 # repositories
355 403 for perm in self.default_repo_perms:
356 404 r_k = perm.UserRepoToPerm.repository.repo_name
357 405 p = 'repository.admin'
358 self.permissions_repositories[r_k] = p
406 self.permissions_repositories[r_k] = p, PermOrigin.ADMIN
359 407
360 408 # repository groups
361 409 for perm in self.default_repo_groups_perms:
362 410 rg_k = perm.UserRepoGroupToPerm.group.group_name
363 411 p = 'group.admin'
364 self.permissions_repository_groups[rg_k] = p
412 self.permissions_repository_groups[rg_k] = p, PermOrigin.ADMIN
365 413
366 414 # user groups
367 415 for perm in self.default_user_group_perms:
368 416 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
369 417 p = 'usergroup.admin'
370 self.permissions_user_groups[u_k] = p
418 self.permissions_user_groups[u_k] = p, PermOrigin.ADMIN
371 419
372 420 return self._permission_structure()
373 421
374 422 def _calculate_global_default_permissions(self):
375 423 """
376 424 global permissions taken from the default user
377 425 """
378 426 default_global_perms = UserToPerm.query()\
379 427 .filter(UserToPerm.user_id == self.default_user_id)\
380 428 .options(joinedload(UserToPerm.permission))
381 429
382 430 for perm in default_global_perms:
383 431 self.permissions_global.add(perm.permission.permission_name)
384 432
385 433 def _calculate_global_permissions(self):
386 434 """
387 435 Set global system permissions with user permissions or permissions
388 436 taken from the user groups of the current user.
389 437
390 438 The permissions include repo creating, repo group creating, forking
391 439 etc.
392 440 """
393 441
394 442 # now we read the defined permissions and overwrite what we have set
395 443 # before those can be configured from groups or users explicitly.
396 444
397 445 # TODO: johbo: This seems to be out of sync, find out the reason
398 446 # for the comment below and update it.
399 447
400 448 # In case we want to extend this list we should be always in sync with
401 449 # User.DEFAULT_USER_PERMISSIONS definitions
402 450 _configurable = frozenset([
403 451 'hg.fork.none', 'hg.fork.repository',
404 452 'hg.create.none', 'hg.create.repository',
405 453 'hg.usergroup.create.false', 'hg.usergroup.create.true',
406 454 'hg.repogroup.create.false', 'hg.repogroup.create.true',
407 455 'hg.create.write_on_repogroup.false',
408 456 'hg.create.write_on_repogroup.true',
409 457 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
410 458 ])
411 459
412 460 # USER GROUPS comes first user group global permissions
413 461 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
414 462 .options(joinedload(UserGroupToPerm.permission))\
415 463 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
416 464 UserGroupMember.users_group_id))\
417 465 .filter(UserGroupMember.user_id == self.user_id)\
418 466 .order_by(UserGroupToPerm.users_group_id)\
419 467 .all()
420 468
421 469 # need to group here by groups since user can be in more than
422 470 # one group, so we get all groups
423 471 _explicit_grouped_perms = [
424 472 [x, list(y)] for x, y in
425 473 itertools.groupby(user_perms_from_users_groups,
426 474 lambda _x: _x.users_group)]
427 475
428 476 for gr, perms in _explicit_grouped_perms:
429 477 # since user can be in multiple groups iterate over them and
430 478 # select the lowest permissions first (more explicit)
431 479 # TODO: marcink: do this^^
432 480
433 481 # group doesn't inherit default permissions so we actually set them
434 482 if not gr.inherit_default_permissions:
435 483 # NEED TO IGNORE all previously set configurable permissions
436 484 # and replace them with explicitly set from this user
437 485 # group permissions
438 486 self.permissions_global = self.permissions_global.difference(
439 487 _configurable)
440 488 for perm in perms:
441 self.permissions_global.add(
442 perm.permission.permission_name)
489 self.permissions_global.add(perm.permission.permission_name)
443 490
444 491 # user explicit global permissions
445 492 user_perms = Session().query(UserToPerm)\
446 493 .options(joinedload(UserToPerm.permission))\
447 494 .filter(UserToPerm.user_id == self.user_id).all()
448 495
449 496 if not self.inherit_default_permissions:
450 497 # NEED TO IGNORE all configurable permissions and
451 498 # replace them with explicitly set from this user permissions
452 499 self.permissions_global = self.permissions_global.difference(
453 500 _configurable)
454 501 for perm in user_perms:
455 502 self.permissions_global.add(perm.permission.permission_name)
456 503
457 504 def _calculate_default_permissions(self):
458 505 """
459 506 Set default user permissions for repositories, repository groups
460 507 taken from the default user.
461 508
462 509 Calculate inheritance of object permissions based on what we have now
463 510 in GLOBAL permissions. We check if .false is in GLOBAL since this is
464 511 explicitly set. Inherit is the opposite of .false being there.
465 512
466 513 .. note::
467 514
468 515 the syntax is little bit odd but what we need to check here is
469 516 the opposite of .false permission being in the list so even for
470 517 inconsistent state when both .true/.false is there
471 518 .false is more important
472 519
473 520 """
474 521 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
475 522 in self.permissions_global)
476 523
477 524 # defaults for repositories, taken from `default` user permissions
478 525 # on given repo
479 526 for perm in self.default_repo_perms:
480 527 r_k = perm.UserRepoToPerm.repository.repo_name
528 o = PermOrigin.REPO_DEFAULT
481 529 if perm.Repository.private and not (
482 530 perm.Repository.user_id == self.user_id):
483 531 # disable defaults for private repos,
484 532 p = 'repository.none'
533 o = PermOrigin.REPO_PRIVATE
485 534 elif perm.Repository.user_id == self.user_id:
486 535 # set admin if owner
487 536 p = 'repository.admin'
537 o = PermOrigin.REPO_OWNER
488 538 else:
489 539 p = perm.Permission.permission_name
490 540 # if we decide this user isn't inheriting permissions from
491 541 # default user we set him to .none so only explicit
492 542 # permissions work
493 543 if not user_inherit_object_permissions:
494 544 p = 'repository.none'
495 self.permissions_repositories[r_k] = p
545 self.permissions_repositories[r_k] = p, o
496 546
497 547 # defaults for repository groups taken from `default` user permission
498 548 # on given group
499 549 for perm in self.default_repo_groups_perms:
500 550 rg_k = perm.UserRepoGroupToPerm.group.group_name
551 o = PermOrigin.REPOGROUP_DEFAULT
501 552 if perm.RepoGroup.user_id == self.user_id:
502 553 # set admin if owner
503 554 p = 'group.admin'
555 o = PermOrigin.REPOGROUP_OWNER
504 556 else:
505 557 p = perm.Permission.permission_name
506 558
507 559 # if we decide this user isn't inheriting permissions from default
508 560 # user we set him to .none so only explicit permissions work
509 561 if not user_inherit_object_permissions:
510 562 p = 'group.none'
511 self.permissions_repository_groups[rg_k] = p
563 self.permissions_repository_groups[rg_k] = p, o
512 564
513 565 # defaults for user groups taken from `default` user permission
514 566 # on given user group
515 567 for perm in self.default_user_group_perms:
516 568 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
517 569 p = perm.Permission.permission_name
570 o = PermOrigin.USERGROUP_DEFAULT
518 571 # if we decide this user isn't inheriting permissions from default
519 572 # user we set him to .none so only explicit permissions work
520 573 if not user_inherit_object_permissions:
521 574 p = 'usergroup.none'
522 self.permissions_user_groups[u_k] = p
575 self.permissions_user_groups[u_k] = p, o
523 576
524 577 def _calculate_repository_permissions(self):
525 578 """
526 579 Repository permissions for the current user.
527 580
528 581 Check if the user is part of user groups for this repository and
529 582 fill in the permission from it. `_choose_permission` decides of which
530 583 permission should be selected based on selected method.
531 584 """
532 585
533 586 # user group for repositories permissions
534 587 user_repo_perms_from_user_group = Permission\
535 588 .get_default_repo_perms_from_user_group(
536 589 self.user_id, self.scope_repo_id)
537 590
538 591 multiple_counter = collections.defaultdict(int)
539 592 for perm in user_repo_perms_from_user_group:
540 593 r_k = perm.UserGroupRepoToPerm.repository.repo_name
594 ug_k = perm.UserGroupRepoToPerm.users_group.users_group_name
541 595 multiple_counter[r_k] += 1
542 596 p = perm.Permission.permission_name
597 o = PermOrigin.REPO_USERGROUP % ug_k
543 598
544 599 if perm.Repository.user_id == self.user_id:
545 600 # set admin if owner
546 601 p = 'repository.admin'
602 o = PermOrigin.REPO_OWNER
547 603 else:
548 604 if multiple_counter[r_k] > 1:
549 605 cur_perm = self.permissions_repositories[r_k]
550 606 p = self._choose_permission(p, cur_perm)
551 self.permissions_repositories[r_k] = p
607 self.permissions_repositories[r_k] = p, o
552 608
553 609 # user explicit permissions for repositories, overrides any specified
554 610 # by the group permission
555 611 user_repo_perms = Permission.get_default_repo_perms(
556 612 self.user_id, self.scope_repo_id)
557 613 for perm in user_repo_perms:
558 614 r_k = perm.UserRepoToPerm.repository.repo_name
615 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
559 616 # set admin if owner
560 617 if perm.Repository.user_id == self.user_id:
561 618 p = 'repository.admin'
619 o = PermOrigin.REPO_OWNER
562 620 else:
563 621 p = perm.Permission.permission_name
564 622 if not self.explicit:
565 623 cur_perm = self.permissions_repositories.get(
566 624 r_k, 'repository.none')
567 625 p = self._choose_permission(p, cur_perm)
568 self.permissions_repositories[r_k] = p
626 self.permissions_repositories[r_k] = p, o
569 627
570 628 def _calculate_repository_group_permissions(self):
571 629 """
572 630 Repository group permissions for the current user.
573 631
574 632 Check if the user is part of user groups for repository groups and
575 633 fill in the permissions from it. `_choose_permmission` decides of which
576 634 permission should be selected based on selected method.
577 635 """
578 636 # user group for repo groups permissions
579 637 user_repo_group_perms_from_user_group = Permission\
580 638 .get_default_group_perms_from_user_group(
581 639 self.user_id, self.scope_repo_group_id)
582 640
583 641 multiple_counter = collections.defaultdict(int)
584 642 for perm in user_repo_group_perms_from_user_group:
585 643 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
644 ug_k = perm.UserGroupRepoGroupToPerm.users_group.users_group_name
645 o = PermOrigin.REPOGROUP_USERGROUP % ug_k
586 646 multiple_counter[g_k] += 1
587 647 p = perm.Permission.permission_name
588 648 if perm.RepoGroup.user_id == self.user_id:
589 649 # set admin if owner
590 650 p = 'group.admin'
651 o = PermOrigin.REPOGROUP_OWNER
591 652 else:
592 653 if multiple_counter[g_k] > 1:
593 654 cur_perm = self.permissions_repository_groups[g_k]
594 655 p = self._choose_permission(p, cur_perm)
595 self.permissions_repository_groups[g_k] = p
656 self.permissions_repository_groups[g_k] = p, o
596 657
597 658 # user explicit permissions for repository groups
598 659 user_repo_groups_perms = Permission.get_default_group_perms(
599 660 self.user_id, self.scope_repo_group_id)
600 661 for perm in user_repo_groups_perms:
601 662 rg_k = perm.UserRepoGroupToPerm.group.group_name
663 u_k = perm.UserRepoGroupToPerm.user.username
664 o = PermOrigin.REPOGROUP_USER % u_k
665
602 666 if perm.RepoGroup.user_id == self.user_id:
603 667 # set admin if owner
604 668 p = 'group.admin'
669 o = PermOrigin.REPOGROUP_OWNER
605 670 else:
606 671 p = perm.Permission.permission_name
607 672 if not self.explicit:
608 673 cur_perm = self.permissions_repository_groups.get(
609 674 rg_k, 'group.none')
610 675 p = self._choose_permission(p, cur_perm)
611 self.permissions_repository_groups[rg_k] = p
676 self.permissions_repository_groups[rg_k] = p, o
612 677
613 678 def _calculate_user_group_permissions(self):
614 679 """
615 680 User group permissions for the current user.
616 681 """
617 682 # user group for user group permissions
618 683 user_group_from_user_group = Permission\
619 684 .get_default_user_group_perms_from_user_group(
620 685 self.user_id, self.scope_repo_group_id)
621 686
622 687 multiple_counter = collections.defaultdict(int)
623 688 for perm in user_group_from_user_group:
624 689 g_k = perm.UserGroupUserGroupToPerm\
625 690 .target_user_group.users_group_name
691 u_k = perm.UserGroupUserGroupToPerm\
692 .user_group.users_group_name
693 o = PermOrigin.USERGROUP_USERGROUP % u_k
626 694 multiple_counter[g_k] += 1
627 695 p = perm.Permission.permission_name
628 696 if multiple_counter[g_k] > 1:
629 697 cur_perm = self.permissions_user_groups[g_k]
630 698 p = self._choose_permission(p, cur_perm)
631 self.permissions_user_groups[g_k] = p
699 self.permissions_user_groups[g_k] = p, o
632 700
633 701 # user explicit permission for user groups
634 702 user_user_groups_perms = Permission.get_default_user_group_perms(
635 703 self.user_id, self.scope_user_group_id)
636 704 for perm in user_user_groups_perms:
637 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
705 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
706 u_k = perm.UserUserGroupToPerm.user.username
638 707 p = perm.Permission.permission_name
708 o = PermOrigin.USERGROUP_USER % u_k
639 709 if not self.explicit:
640 710 cur_perm = self.permissions_user_groups.get(
641 u_k, 'usergroup.none')
711 ug_k, 'usergroup.none')
642 712 p = self._choose_permission(p, cur_perm)
643 self.permissions_user_groups[u_k] = p
713 self.permissions_user_groups[ug_k] = p, o
644 714
645 715 def _choose_permission(self, new_perm, cur_perm):
646 716 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
647 717 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
648 718 if self.algo == 'higherwin':
649 719 if new_perm_val > cur_perm_val:
650 720 return new_perm
651 721 return cur_perm
652 722 elif self.algo == 'lowerwin':
653 723 if new_perm_val < cur_perm_val:
654 724 return new_perm
655 725 return cur_perm
656 726
657 727 def _permission_structure(self):
658 728 return {
659 729 'global': self.permissions_global,
660 730 'repositories': self.permissions_repositories,
661 731 'repositories_groups': self.permissions_repository_groups,
662 732 'user_groups': self.permissions_user_groups,
663 733 }
664 734
665 735
666 736 def allowed_auth_token_access(controller_name, whitelist=None, auth_token=None):
667 737 """
668 738 Check if given controller_name is in whitelist of auth token access
669 739 """
670 740 if not whitelist:
671 741 from rhodecode import CONFIG
672 742 whitelist = aslist(
673 743 CONFIG.get('api_access_controllers_whitelist'), sep=',')
674 744 log.debug(
675 745 'Allowed controllers for AUTH TOKEN access: %s' % (whitelist,))
676 746
677 747 auth_token_access_valid = False
678 748 for entry in whitelist:
679 749 if fnmatch.fnmatch(controller_name, entry):
680 750 auth_token_access_valid = True
681 751 break
682 752
683 753 if auth_token_access_valid:
684 754 log.debug('controller:%s matches entry in whitelist'
685 755 % (controller_name,))
686 756 else:
687 757 msg = ('controller: %s does *NOT* match any entry in whitelist'
688 758 % (controller_name,))
689 759 if auth_token:
690 760 # if we use auth token key and don't have access it's a warning
691 761 log.warning(msg)
692 762 else:
693 763 log.debug(msg)
694 764
695 765 return auth_token_access_valid
696 766
697 767
698 768 class AuthUser(object):
699 769 """
700 770 A simple object that handles all attributes of user in RhodeCode
701 771
702 772 It does lookup based on API key,given user, or user present in session
703 773 Then it fills all required information for such user. It also checks if
704 774 anonymous access is enabled and if so, it returns default user as logged in
705 775 """
706 776 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
707 777
708 778 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
709 779
710 780 self.user_id = user_id
711 781 self._api_key = api_key
712 782
713 783 self.api_key = None
714 784 self.feed_token = ''
715 785 self.username = username
716 786 self.ip_addr = ip_addr
717 787 self.name = ''
718 788 self.lastname = ''
719 789 self.email = ''
720 790 self.is_authenticated = False
721 791 self.admin = False
722 792 self.inherit_default_permissions = False
723 793 self.password = ''
724 794
725 795 self.anonymous_user = None # propagated on propagate_data
726 796 self.propagate_data()
727 797 self._instance = None
728 798 self._permissions_scoped_cache = {} # used to bind scoped calculation
729 799
730 800 @LazyProperty
731 801 def permissions(self):
732 802 return self.get_perms(user=self, cache=False)
733 803
734 804 def permissions_with_scope(self, scope):
735 805 """
736 806 Call the get_perms function with scoped data. The scope in that function
737 807 narrows the SQL calls to the given ID of objects resulting in fetching
738 808 Just particular permission we want to obtain. If scope is an empty dict
739 809 then it basically narrows the scope to GLOBAL permissions only.
740 810
741 811 :param scope: dict
742 812 """
743 813 if 'repo_name' in scope:
744 814 obj = Repository.get_by_repo_name(scope['repo_name'])
745 815 if obj:
746 816 scope['repo_id'] = obj.repo_id
747 817 _scope = {
748 818 'repo_id': -1,
749 819 'user_group_id': -1,
750 820 'repo_group_id': -1,
751 821 }
752 822 _scope.update(scope)
753 823 cache_key = "_".join(map(safe_str, reduce(lambda a, b: a+b,
754 824 _scope.items())))
755 825 if cache_key not in self._permissions_scoped_cache:
756 826 # store in cache to mimic how the @LazyProperty works,
757 827 # the difference here is that we use the unique key calculated
758 828 # from params and values
759 829 res = self.get_perms(user=self, cache=False, scope=_scope)
760 830 self._permissions_scoped_cache[cache_key] = res
761 831 return self._permissions_scoped_cache[cache_key]
762 832
763 833 @property
764 834 def auth_tokens(self):
765 835 return self.get_auth_tokens()
766 836
767 837 def get_instance(self):
768 838 return User.get(self.user_id)
769 839
770 840 def update_lastactivity(self):
771 841 if self.user_id:
772 842 User.get(self.user_id).update_lastactivity()
773 843
774 844 def propagate_data(self):
775 845 """
776 846 Fills in user data and propagates values to this instance. Maps fetched
777 847 user attributes to this class instance attributes
778 848 """
779 849
780 850 user_model = UserModel()
781 851 anon_user = self.anonymous_user = User.get_default_user(cache=True)
782 852 is_user_loaded = False
783 853
784 854 # lookup by userid
785 855 if self.user_id is not None and self.user_id != anon_user.user_id:
786 856 log.debug('Trying Auth User lookup by USER ID %s' % self.user_id)
787 857 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
788 858
789 859 # try go get user by api key
790 860 elif self._api_key and self._api_key != anon_user.api_key:
791 861 log.debug('Trying Auth User lookup by API KEY %s' % self._api_key)
792 862 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
793 863
794 864 # lookup by username
795 865 elif self.username:
796 866 log.debug('Trying Auth User lookup by USER NAME %s' % self.username)
797 867 is_user_loaded = user_model.fill_data(self, username=self.username)
798 868 else:
799 869 log.debug('No data in %s that could been used to log in' % self)
800 870
801 871 if not is_user_loaded:
802 log.debug('Failed to load user %s. Fallback to default user', self)
872 log.debug('Failed to load user. Fallback to default user')
803 873 # if we cannot authenticate user try anonymous
804 874 if anon_user.active:
805 875 user_model.fill_data(self, user_id=anon_user.user_id)
806 876 # then we set this user is logged in
807 877 self.is_authenticated = True
808 878 else:
809 879 # in case of disabled anonymous user we reset some of the
810 880 # parameters so such user is "corrupted", skipping the fill_data
811 881 for attr in ['user_id', 'username', 'admin', 'active']:
812 882 setattr(self, attr, None)
813 883 self.is_authenticated = False
814 884
815 885 if not self.username:
816 886 self.username = 'None'
817 887
818 888 log.debug('Auth User is now %s' % self)
819 889
820 890 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
821 891 cache=False):
822 892 """
823 893 Fills user permission attribute with permissions taken from database
824 894 works for permissions given for repositories, and for permissions that
825 895 are granted to groups
826 896
827 897 :param user: instance of User object from database
828 898 :param explicit: In case there are permissions both for user and a group
829 899 that user is part of, explicit flag will defiine if user will
830 900 explicitly override permissions from group, if it's False it will
831 901 make decision based on the algo
832 902 :param algo: algorithm to decide what permission should be choose if
833 903 it's multiple defined, eg user in two different groups. It also
834 904 decides if explicit flag is turned off how to specify the permission
835 905 for case when user is in a group + have defined separate permission
836 906 """
837 907 user_id = user.user_id
838 908 user_is_admin = user.is_admin
839 909
840 910 # inheritance of global permissions like create repo/fork repo etc
841 911 user_inherit_default_permissions = user.inherit_default_permissions
842 912
843 913 log.debug('Computing PERMISSION tree for scope %s' % (scope, ))
844 914 compute = caches.conditional_cache(
845 915 'short_term', 'cache_desc',
846 916 condition=cache, func=_cached_perms_data)
847 917 result = compute(user_id, scope, user_is_admin,
848 918 user_inherit_default_permissions, explicit, algo)
849 919
850 920 result_repr = []
851 921 for k in result:
852 922 result_repr.append((k, len(result[k])))
853 923
854 924 log.debug('PERMISSION tree computed %s' % (result_repr,))
855 925 return result
856 926
857 927 def get_auth_tokens(self):
858 928 auth_tokens = [self.api_key]
859 929 for api_key in UserApiKeys.query()\
860 930 .filter(UserApiKeys.user_id == self.user_id)\
861 931 .filter(or_(UserApiKeys.expires == -1,
862 932 UserApiKeys.expires >= time.time())).all():
863 933 auth_tokens.append(api_key.api_key)
864 934
865 935 return auth_tokens
866 936
867 937 @property
868 938 def is_default(self):
869 939 return self.username == User.DEFAULT_USER
870 940
871 941 @property
872 942 def is_admin(self):
873 943 return self.admin
874 944
875 945 @property
876 946 def is_user_object(self):
877 947 return self.user_id is not None
878 948
879 949 @property
880 950 def repositories_admin(self):
881 951 """
882 952 Returns list of repositories you're an admin of
883 953 """
884 954 return [x[0] for x in self.permissions['repositories'].iteritems()
885 955 if x[1] == 'repository.admin']
886 956
887 957 @property
888 958 def repository_groups_admin(self):
889 959 """
890 960 Returns list of repository groups you're an admin of
891 961 """
892 962 return [x[0]
893 963 for x in self.permissions['repositories_groups'].iteritems()
894 964 if x[1] == 'group.admin']
895 965
896 966 @property
897 967 def user_groups_admin(self):
898 968 """
899 969 Returns list of user groups you're an admin of
900 970 """
901 971 return [x[0] for x in self.permissions['user_groups'].iteritems()
902 972 if x[1] == 'usergroup.admin']
903 973
904 974 @property
905 975 def ip_allowed(self):
906 976 """
907 977 Checks if ip_addr used in constructor is allowed from defined list of
908 978 allowed ip_addresses for user
909 979
910 980 :returns: boolean, True if ip is in allowed ip range
911 981 """
912 982 # check IP
913 983 inherit = self.inherit_default_permissions
914 984 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
915 985 inherit_from_default=inherit)
916 986
917 987 @classmethod
918 988 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
919 989 allowed_ips = AuthUser.get_allowed_ips(
920 990 user_id, cache=True, inherit_from_default=inherit_from_default)
921 991 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
922 992 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
923 993 return True
924 994 else:
925 995 log.info('Access for IP:%s forbidden, '
926 996 'not in %s' % (ip_addr, allowed_ips))
927 997 return False
928 998
929 999 def __repr__(self):
930 1000 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
931 1001 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
932 1002
933 1003 def set_authenticated(self, authenticated=True):
934 1004 if self.user_id != self.anonymous_user.user_id:
935 1005 self.is_authenticated = authenticated
936 1006
937 1007 def get_cookie_store(self):
938 1008 return {
939 1009 'username': self.username,
940 1010 'password': md5(self.password),
941 1011 'user_id': self.user_id,
942 1012 'is_authenticated': self.is_authenticated
943 1013 }
944 1014
945 1015 @classmethod
946 1016 def from_cookie_store(cls, cookie_store):
947 1017 """
948 1018 Creates AuthUser from a cookie store
949 1019
950 1020 :param cls:
951 1021 :param cookie_store:
952 1022 """
953 1023 user_id = cookie_store.get('user_id')
954 1024 username = cookie_store.get('username')
955 1025 api_key = cookie_store.get('api_key')
956 1026 return AuthUser(user_id, api_key, username)
957 1027
958 1028 @classmethod
959 1029 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
960 1030 _set = set()
961 1031
962 1032 if inherit_from_default:
963 1033 default_ips = UserIpMap.query().filter(
964 1034 UserIpMap.user == User.get_default_user(cache=True))
965 1035 if cache:
966 1036 default_ips = default_ips.options(FromCache("sql_cache_short",
967 1037 "get_user_ips_default"))
968 1038
969 1039 # populate from default user
970 1040 for ip in default_ips:
971 1041 try:
972 1042 _set.add(ip.ip_addr)
973 1043 except ObjectDeletedError:
974 1044 # since we use heavy caching sometimes it happens that
975 1045 # we get deleted objects here, we just skip them
976 1046 pass
977 1047
978 1048 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
979 1049 if cache:
980 1050 user_ips = user_ips.options(FromCache("sql_cache_short",
981 1051 "get_user_ips_%s" % user_id))
982 1052
983 1053 for ip in user_ips:
984 1054 try:
985 1055 _set.add(ip.ip_addr)
986 1056 except ObjectDeletedError:
987 1057 # since we use heavy caching sometimes it happens that we get
988 1058 # deleted objects here, we just skip them
989 1059 pass
990 1060 return _set or set(['0.0.0.0/0', '::/0'])
991 1061
992 1062
993 1063 def set_available_permissions(config):
994 1064 """
995 1065 This function will propagate pylons globals with all available defined
996 1066 permission given in db. We don't want to check each time from db for new
997 1067 permissions since adding a new permission also requires application restart
998 1068 ie. to decorate new views with the newly created permission
999 1069
1000 1070 :param config: current pylons config instance
1001 1071
1002 1072 """
1003 1073 log.info('getting information about all available permissions')
1004 1074 try:
1005 1075 sa = meta.Session
1006 1076 all_perms = sa.query(Permission).all()
1007 1077 config['available_permissions'] = [x.permission_name for x in all_perms]
1008 1078 except Exception:
1009 1079 log.error(traceback.format_exc())
1010 1080 finally:
1011 1081 meta.Session.remove()
1012 1082
1013 1083
1014 1084 def get_csrf_token(session=None, force_new=False, save_if_missing=True):
1015 1085 """
1016 1086 Return the current authentication token, creating one if one doesn't
1017 1087 already exist and the save_if_missing flag is present.
1018 1088
1019 1089 :param session: pass in the pylons session, else we use the global ones
1020 1090 :param force_new: force to re-generate the token and store it in session
1021 1091 :param save_if_missing: save the newly generated token if it's missing in
1022 1092 session
1023 1093 """
1024 1094 if not session:
1025 1095 from pylons import session
1026 1096
1027 1097 if (csrf_token_key not in session and save_if_missing) or force_new:
1028 1098 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1029 1099 session[csrf_token_key] = token
1030 1100 if hasattr(session, 'save'):
1031 1101 session.save()
1032 1102 return session.get(csrf_token_key)
1033 1103
1034 1104
1035 1105 # CHECK DECORATORS
1036 1106 class CSRFRequired(object):
1037 1107 """
1038 1108 Decorator for authenticating a form
1039 1109
1040 1110 This decorator uses an authorization token stored in the client's
1041 1111 session for prevention of certain Cross-site request forgery (CSRF)
1042 1112 attacks (See
1043 1113 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1044 1114 information).
1045 1115
1046 1116 For use with the ``webhelpers.secure_form`` helper functions.
1047 1117
1048 1118 """
1049 1119 def __init__(self, token=csrf_token_key, header='X-CSRF-Token'):
1050 1120 self.token = token
1051 1121 self.header = header
1052 1122
1053 1123 def __call__(self, func):
1054 1124 return get_cython_compat_decorator(self.__wrapper, func)
1055 1125
1056 1126 def _get_csrf(self, _request):
1057 1127 return _request.POST.get(self.token, _request.headers.get(self.header))
1058 1128
1059 1129 def check_csrf(self, _request, cur_token):
1060 1130 supplied_token = self._get_csrf(_request)
1061 1131 return supplied_token and supplied_token == cur_token
1062 1132
1063 1133 def __wrapper(self, func, *fargs, **fkwargs):
1064 1134 cur_token = get_csrf_token(save_if_missing=False)
1065 1135 if self.check_csrf(request, cur_token):
1066 1136 if request.POST.get(self.token):
1067 1137 del request.POST[self.token]
1068 1138 return func(*fargs, **fkwargs)
1069 1139 else:
1070 1140 reason = 'token-missing'
1071 1141 supplied_token = self._get_csrf(request)
1072 1142 if supplied_token and cur_token != supplied_token:
1073 1143 reason = 'token-mismatch [%s:%s]' % (cur_token or ''[:6],
1074 1144 supplied_token or ''[:6])
1075 1145
1076 1146 csrf_message = \
1077 1147 ("Cross-site request forgery detected, request denied. See "
1078 1148 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1079 1149 "more information.")
1080 1150 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1081 1151 'REMOTE_ADDR:%s, HEADERS:%s' % (
1082 1152 request, reason, request.remote_addr, request.headers))
1083 1153
1084 1154 abort(403, detail=csrf_message)
1085 1155
1086 1156
1087 1157 class LoginRequired(object):
1088 1158 """
1089 1159 Must be logged in to execute this function else
1090 1160 redirect to login page
1091 1161
1092 1162 :param api_access: if enabled this checks only for valid auth token
1093 1163 and grants access based on valid token
1094 1164 """
1095 1165 def __init__(self, auth_token_access=False):
1096 1166 self.auth_token_access = auth_token_access
1097 1167
1098 1168 def __call__(self, func):
1099 1169 return get_cython_compat_decorator(self.__wrapper, func)
1100 1170
1101 1171 def __wrapper(self, func, *fargs, **fkwargs):
1102 1172 from rhodecode.lib import helpers as h
1103 1173 cls = fargs[0]
1104 1174 user = cls._rhodecode_user
1105 1175 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1106 1176 log.debug('Starting login restriction checks for user: %s' % (user,))
1107 1177 # check if our IP is allowed
1108 1178 ip_access_valid = True
1109 1179 if not user.ip_allowed:
1110 1180 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1111 1181 category='warning')
1112 1182 ip_access_valid = False
1113 1183
1114 1184 # check if we used an APIKEY and it's a valid one
1115 1185 # defined whitelist of controllers which API access will be enabled
1116 1186 _auth_token = request.GET.get(
1117 1187 'auth_token', '') or request.GET.get('api_key', '')
1118 1188 auth_token_access_valid = allowed_auth_token_access(
1119 1189 loc, auth_token=_auth_token)
1120 1190
1121 1191 # explicit controller is enabled or API is in our whitelist
1122 1192 if self.auth_token_access or auth_token_access_valid:
1123 1193 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1124 1194
1125 1195 if _auth_token and _auth_token in user.auth_tokens:
1126 1196 auth_token_access_valid = True
1127 1197 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1128 1198 else:
1129 1199 auth_token_access_valid = False
1130 1200 if not _auth_token:
1131 1201 log.debug("AUTH TOKEN *NOT* present in request")
1132 1202 else:
1133 1203 log.warning(
1134 1204 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1135 1205
1136 1206 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1137 1207 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1138 1208 else 'AUTH_TOKEN_AUTH'
1139 1209
1140 1210 if ip_access_valid and (
1141 1211 user.is_authenticated or auth_token_access_valid):
1142 1212 log.info(
1143 1213 'user %s authenticating with:%s IS authenticated on func %s'
1144 1214 % (user, reason, loc))
1145 1215
1146 1216 # update user data to check last activity
1147 1217 user.update_lastactivity()
1148 1218 Session().commit()
1149 1219 return func(*fargs, **fkwargs)
1150 1220 else:
1151 1221 log.warning(
1152 1222 'user %s authenticating with:%s NOT authenticated on '
1153 1223 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1154 1224 % (user, reason, loc, ip_access_valid,
1155 1225 auth_token_access_valid))
1156 1226 # we preserve the get PARAM
1157 1227 came_from = request.path_qs
1158 1228
1159 1229 log.debug('redirecting to login page with %s' % (came_from,))
1160 1230 return redirect(
1161 1231 h.route_path('login', _query={'came_from': came_from}))
1162 1232
1163 1233
1164 1234 class NotAnonymous(object):
1165 1235 """
1166 1236 Must be logged in to execute this function else
1167 1237 redirect to login page"""
1168 1238
1169 1239 def __call__(self, func):
1170 1240 return get_cython_compat_decorator(self.__wrapper, func)
1171 1241
1172 1242 def __wrapper(self, func, *fargs, **fkwargs):
1173 1243 cls = fargs[0]
1174 1244 self.user = cls._rhodecode_user
1175 1245
1176 1246 log.debug('Checking if user is not anonymous @%s' % cls)
1177 1247
1178 1248 anonymous = self.user.username == User.DEFAULT_USER
1179 1249
1180 1250 if anonymous:
1181 1251 came_from = request.path_qs
1182 1252
1183 1253 import rhodecode.lib.helpers as h
1184 1254 h.flash(_('You need to be a registered user to '
1185 1255 'perform this action'),
1186 1256 category='warning')
1187 1257 return redirect(
1188 1258 h.route_path('login', _query={'came_from': came_from}))
1189 1259 else:
1190 1260 return func(*fargs, **fkwargs)
1191 1261
1192 1262
1193 1263 class XHRRequired(object):
1194 1264 def __call__(self, func):
1195 1265 return get_cython_compat_decorator(self.__wrapper, func)
1196 1266
1197 1267 def __wrapper(self, func, *fargs, **fkwargs):
1198 1268 log.debug('Checking if request is XMLHttpRequest (XHR)')
1199 1269 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1200 1270 if not request.is_xhr:
1201 1271 abort(400, detail=xhr_message)
1202 1272
1203 1273 return func(*fargs, **fkwargs)
1204 1274
1205 1275
1206 1276 class HasAcceptedRepoType(object):
1207 1277 """
1208 1278 Check if requested repo is within given repo type aliases
1209 1279
1210 1280 TODO: anderson: not sure where to put this decorator
1211 1281 """
1212 1282
1213 1283 def __init__(self, *repo_type_list):
1214 1284 self.repo_type_list = set(repo_type_list)
1215 1285
1216 1286 def __call__(self, func):
1217 1287 return get_cython_compat_decorator(self.__wrapper, func)
1218 1288
1219 1289 def __wrapper(self, func, *fargs, **fkwargs):
1220 1290 cls = fargs[0]
1221 1291 rhodecode_repo = cls.rhodecode_repo
1222 1292
1223 1293 log.debug('%s checking repo type for %s in %s',
1224 1294 self.__class__.__name__,
1225 1295 rhodecode_repo.alias, self.repo_type_list)
1226 1296
1227 1297 if rhodecode_repo.alias in self.repo_type_list:
1228 1298 return func(*fargs, **fkwargs)
1229 1299 else:
1230 1300 import rhodecode.lib.helpers as h
1231 1301 h.flash(h.literal(
1232 1302 _('Action not supported for %s.' % rhodecode_repo.alias)),
1233 1303 category='warning')
1234 1304 return redirect(
1235 1305 url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name))
1236 1306
1237 1307
1238 1308 class PermsDecorator(object):
1239 1309 """
1240 1310 Base class for controller decorators, we extract the current user from
1241 1311 the class itself, which has it stored in base controllers
1242 1312 """
1243 1313
1244 1314 def __init__(self, *required_perms):
1245 1315 self.required_perms = set(required_perms)
1246 1316
1247 1317 def __call__(self, func):
1248 1318 return get_cython_compat_decorator(self.__wrapper, func)
1249 1319
1250 1320 def __wrapper(self, func, *fargs, **fkwargs):
1251 1321 cls = fargs[0]
1252 1322 _user = cls._rhodecode_user
1253 1323
1254 1324 log.debug('checking %s permissions %s for %s %s',
1255 1325 self.__class__.__name__, self.required_perms, cls, _user)
1256 1326
1257 1327 if self.check_permissions(_user):
1258 1328 log.debug('Permission granted for %s %s', cls, _user)
1259 1329 return func(*fargs, **fkwargs)
1260 1330
1261 1331 else:
1262 1332 log.debug('Permission denied for %s %s', cls, _user)
1263 1333 anonymous = _user.username == User.DEFAULT_USER
1264 1334
1265 1335 if anonymous:
1266 1336 came_from = request.path_qs
1267 1337
1268 1338 import rhodecode.lib.helpers as h
1269 1339 h.flash(_('You need to be signed in to view this page'),
1270 1340 category='warning')
1271 1341 return redirect(
1272 1342 h.route_path('login', _query={'came_from': came_from}))
1273 1343
1274 1344 else:
1275 1345 # redirect with forbidden ret code
1276 1346 return abort(403)
1277 1347
1278 1348 def check_permissions(self, user):
1279 1349 """Dummy function for overriding"""
1280 1350 raise NotImplementedError(
1281 1351 'You have to write this function in child class')
1282 1352
1283 1353
1284 1354 class HasPermissionAllDecorator(PermsDecorator):
1285 1355 """
1286 1356 Checks for access permission for all given predicates. All of them
1287 1357 have to be meet in order to fulfill the request
1288 1358 """
1289 1359
1290 1360 def check_permissions(self, user):
1291 1361 perms = user.permissions_with_scope({})
1292 1362 if self.required_perms.issubset(perms['global']):
1293 1363 return True
1294 1364 return False
1295 1365
1296 1366
1297 1367 class HasPermissionAnyDecorator(PermsDecorator):
1298 1368 """
1299 1369 Checks for access permission for any of given predicates. In order to
1300 1370 fulfill the request any of predicates must be meet
1301 1371 """
1302 1372
1303 1373 def check_permissions(self, user):
1304 1374 perms = user.permissions_with_scope({})
1305 1375 if self.required_perms.intersection(perms['global']):
1306 1376 return True
1307 1377 return False
1308 1378
1309 1379
1310 1380 class HasRepoPermissionAllDecorator(PermsDecorator):
1311 1381 """
1312 1382 Checks for access permission for all given predicates for specific
1313 1383 repository. All of them have to be meet in order to fulfill the request
1314 1384 """
1315 1385
1316 1386 def check_permissions(self, user):
1317 1387 perms = user.permissions
1318 1388 repo_name = get_repo_slug(request)
1319 1389 try:
1320 1390 user_perms = set([perms['repositories'][repo_name]])
1321 1391 except KeyError:
1322 1392 return False
1323 1393 if self.required_perms.issubset(user_perms):
1324 1394 return True
1325 1395 return False
1326 1396
1327 1397
1328 1398 class HasRepoPermissionAnyDecorator(PermsDecorator):
1329 1399 """
1330 1400 Checks for access permission for any of given predicates for specific
1331 1401 repository. In order to fulfill the request any of predicates must be meet
1332 1402 """
1333 1403
1334 1404 def check_permissions(self, user):
1335 1405 perms = user.permissions
1336 1406 repo_name = get_repo_slug(request)
1337 1407 try:
1338 1408 user_perms = set([perms['repositories'][repo_name]])
1339 1409 except KeyError:
1340 1410 return False
1341 1411
1342 1412 if self.required_perms.intersection(user_perms):
1343 1413 return True
1344 1414 return False
1345 1415
1346 1416
1347 1417 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1348 1418 """
1349 1419 Checks for access permission for all given predicates for specific
1350 1420 repository group. All of them have to be meet in order to
1351 1421 fulfill the request
1352 1422 """
1353 1423
1354 1424 def check_permissions(self, user):
1355 1425 perms = user.permissions
1356 1426 group_name = get_repo_group_slug(request)
1357 1427 try:
1358 1428 user_perms = set([perms['repositories_groups'][group_name]])
1359 1429 except KeyError:
1360 1430 return False
1361 1431
1362 1432 if self.required_perms.issubset(user_perms):
1363 1433 return True
1364 1434 return False
1365 1435
1366 1436
1367 1437 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1368 1438 """
1369 1439 Checks for access permission for any of given predicates for specific
1370 1440 repository group. In order to fulfill the request any
1371 1441 of predicates must be met
1372 1442 """
1373 1443
1374 1444 def check_permissions(self, user):
1375 1445 perms = user.permissions
1376 1446 group_name = get_repo_group_slug(request)
1377 1447 try:
1378 1448 user_perms = set([perms['repositories_groups'][group_name]])
1379 1449 except KeyError:
1380 1450 return False
1381 1451
1382 1452 if self.required_perms.intersection(user_perms):
1383 1453 return True
1384 1454 return False
1385 1455
1386 1456
1387 1457 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1388 1458 """
1389 1459 Checks for access permission for all given predicates for specific
1390 1460 user group. All of them have to be meet in order to fulfill the request
1391 1461 """
1392 1462
1393 1463 def check_permissions(self, user):
1394 1464 perms = user.permissions
1395 1465 group_name = get_user_group_slug(request)
1396 1466 try:
1397 1467 user_perms = set([perms['user_groups'][group_name]])
1398 1468 except KeyError:
1399 1469 return False
1400 1470
1401 1471 if self.required_perms.issubset(user_perms):
1402 1472 return True
1403 1473 return False
1404 1474
1405 1475
1406 1476 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1407 1477 """
1408 1478 Checks for access permission for any of given predicates for specific
1409 1479 user group. In order to fulfill the request any of predicates must be meet
1410 1480 """
1411 1481
1412 1482 def check_permissions(self, user):
1413 1483 perms = user.permissions
1414 1484 group_name = get_user_group_slug(request)
1415 1485 try:
1416 1486 user_perms = set([perms['user_groups'][group_name]])
1417 1487 except KeyError:
1418 1488 return False
1419 1489
1420 1490 if self.required_perms.intersection(user_perms):
1421 1491 return True
1422 1492 return False
1423 1493
1424 1494
1425 1495 # CHECK FUNCTIONS
1426 1496 class PermsFunction(object):
1427 1497 """Base function for other check functions"""
1428 1498
1429 1499 def __init__(self, *perms):
1430 1500 self.required_perms = set(perms)
1431 1501 self.repo_name = None
1432 1502 self.repo_group_name = None
1433 1503 self.user_group_name = None
1434 1504
1435 1505 def __bool__(self):
1436 1506 frame = inspect.currentframe()
1437 1507 stack_trace = traceback.format_stack(frame)
1438 1508 log.error('Checking bool value on a class instance of perm '
1439 1509 'function is not allowed: %s' % ''.join(stack_trace))
1440 1510 # rather than throwing errors, here we always return False so if by
1441 1511 # accident someone checks truth for just an instance it will always end
1442 1512 # up in returning False
1443 1513 return False
1444 1514 __nonzero__ = __bool__
1445 1515
1446 1516 def __call__(self, check_location='', user=None):
1447 1517 if not user:
1448 1518 log.debug('Using user attribute from global request')
1449 1519 # TODO: remove this someday,put as user as attribute here
1450 1520 user = request.user
1451 1521
1452 1522 # init auth user if not already given
1453 1523 if not isinstance(user, AuthUser):
1454 1524 log.debug('Wrapping user %s into AuthUser', user)
1455 1525 user = AuthUser(user.user_id)
1456 1526
1457 1527 cls_name = self.__class__.__name__
1458 1528 check_scope = self._get_check_scope(cls_name)
1459 1529 check_location = check_location or 'unspecified location'
1460 1530
1461 1531 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1462 1532 self.required_perms, user, check_scope, check_location)
1463 1533 if not user:
1464 1534 log.warning('Empty user given for permission check')
1465 1535 return False
1466 1536
1467 1537 if self.check_permissions(user):
1468 1538 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1469 1539 check_scope, user, check_location)
1470 1540 return True
1471 1541
1472 1542 else:
1473 1543 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1474 1544 check_scope, user, check_location)
1475 1545 return False
1476 1546
1477 1547 def _get_check_scope(self, cls_name):
1478 1548 return {
1479 1549 'HasPermissionAll': 'GLOBAL',
1480 1550 'HasPermissionAny': 'GLOBAL',
1481 1551 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1482 1552 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1483 1553 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1484 1554 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1485 1555 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1486 1556 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1487 1557 }.get(cls_name, '?:%s' % cls_name)
1488 1558
1489 1559 def check_permissions(self, user):
1490 1560 """Dummy function for overriding"""
1491 1561 raise Exception('You have to write this function in child class')
1492 1562
1493 1563
1494 1564 class HasPermissionAll(PermsFunction):
1495 1565 def check_permissions(self, user):
1496 1566 perms = user.permissions_with_scope({})
1497 1567 if self.required_perms.issubset(perms.get('global')):
1498 1568 return True
1499 1569 return False
1500 1570
1501 1571
1502 1572 class HasPermissionAny(PermsFunction):
1503 1573 def check_permissions(self, user):
1504 1574 perms = user.permissions_with_scope({})
1505 1575 if self.required_perms.intersection(perms.get('global')):
1506 1576 return True
1507 1577 return False
1508 1578
1509 1579
1510 1580 class HasRepoPermissionAll(PermsFunction):
1511 1581 def __call__(self, repo_name=None, check_location='', user=None):
1512 1582 self.repo_name = repo_name
1513 1583 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1514 1584
1515 1585 def check_permissions(self, user):
1516 1586 if not self.repo_name:
1517 1587 self.repo_name = get_repo_slug(request)
1518 1588
1519 1589 perms = user.permissions
1520 1590 try:
1521 1591 user_perms = set([perms['repositories'][self.repo_name]])
1522 1592 except KeyError:
1523 1593 return False
1524 1594 if self.required_perms.issubset(user_perms):
1525 1595 return True
1526 1596 return False
1527 1597
1528 1598
1529 1599 class HasRepoPermissionAny(PermsFunction):
1530 1600 def __call__(self, repo_name=None, check_location='', user=None):
1531 1601 self.repo_name = repo_name
1532 1602 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1533 1603
1534 1604 def check_permissions(self, user):
1535 1605 if not self.repo_name:
1536 1606 self.repo_name = get_repo_slug(request)
1537 1607
1538 1608 perms = user.permissions
1539 1609 try:
1540 1610 user_perms = set([perms['repositories'][self.repo_name]])
1541 1611 except KeyError:
1542 1612 return False
1543 1613 if self.required_perms.intersection(user_perms):
1544 1614 return True
1545 1615 return False
1546 1616
1547 1617
1548 1618 class HasRepoGroupPermissionAny(PermsFunction):
1549 1619 def __call__(self, group_name=None, check_location='', user=None):
1550 1620 self.repo_group_name = group_name
1551 1621 return super(HasRepoGroupPermissionAny, self).__call__(
1552 1622 check_location, user)
1553 1623
1554 1624 def check_permissions(self, user):
1555 1625 perms = user.permissions
1556 1626 try:
1557 1627 user_perms = set(
1558 1628 [perms['repositories_groups'][self.repo_group_name]])
1559 1629 except KeyError:
1560 1630 return False
1561 1631 if self.required_perms.intersection(user_perms):
1562 1632 return True
1563 1633 return False
1564 1634
1565 1635
1566 1636 class HasRepoGroupPermissionAll(PermsFunction):
1567 1637 def __call__(self, group_name=None, check_location='', user=None):
1568 1638 self.repo_group_name = group_name
1569 1639 return super(HasRepoGroupPermissionAll, self).__call__(
1570 1640 check_location, user)
1571 1641
1572 1642 def check_permissions(self, user):
1573 1643 perms = user.permissions
1574 1644 try:
1575 1645 user_perms = set(
1576 1646 [perms['repositories_groups'][self.repo_group_name]])
1577 1647 except KeyError:
1578 1648 return False
1579 1649 if self.required_perms.issubset(user_perms):
1580 1650 return True
1581 1651 return False
1582 1652
1583 1653
1584 1654 class HasUserGroupPermissionAny(PermsFunction):
1585 1655 def __call__(self, user_group_name=None, check_location='', user=None):
1586 1656 self.user_group_name = user_group_name
1587 1657 return super(HasUserGroupPermissionAny, self).__call__(
1588 1658 check_location, user)
1589 1659
1590 1660 def check_permissions(self, user):
1591 1661 perms = user.permissions
1592 1662 try:
1593 1663 user_perms = set([perms['user_groups'][self.user_group_name]])
1594 1664 except KeyError:
1595 1665 return False
1596 1666 if self.required_perms.intersection(user_perms):
1597 1667 return True
1598 1668 return False
1599 1669
1600 1670
1601 1671 class HasUserGroupPermissionAll(PermsFunction):
1602 1672 def __call__(self, user_group_name=None, check_location='', user=None):
1603 1673 self.user_group_name = user_group_name
1604 1674 return super(HasUserGroupPermissionAll, self).__call__(
1605 1675 check_location, user)
1606 1676
1607 1677 def check_permissions(self, user):
1608 1678 perms = user.permissions
1609 1679 try:
1610 1680 user_perms = set([perms['user_groups'][self.user_group_name]])
1611 1681 except KeyError:
1612 1682 return False
1613 1683 if self.required_perms.issubset(user_perms):
1614 1684 return True
1615 1685 return False
1616 1686
1617 1687
1618 1688 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1619 1689 class HasPermissionAnyMiddleware(object):
1620 1690 def __init__(self, *perms):
1621 1691 self.required_perms = set(perms)
1622 1692
1623 1693 def __call__(self, user, repo_name):
1624 1694 # repo_name MUST be unicode, since we handle keys in permission
1625 1695 # dict by unicode
1626 1696 repo_name = safe_unicode(repo_name)
1627 1697 user = AuthUser(user.user_id)
1628 1698 log.debug(
1629 1699 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1630 1700 self.required_perms, user, repo_name)
1631 1701
1632 1702 if self.check_permissions(user, repo_name):
1633 1703 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1634 1704 repo_name, user, 'PermissionMiddleware')
1635 1705 return True
1636 1706
1637 1707 else:
1638 1708 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1639 1709 repo_name, user, 'PermissionMiddleware')
1640 1710 return False
1641 1711
1642 1712 def check_permissions(self, user, repo_name):
1643 1713 perms = user.permissions_with_scope({'repo_name': repo_name})
1644 1714
1645 1715 try:
1646 1716 user_perms = set([perms['repositories'][repo_name]])
1647 1717 except Exception:
1648 1718 log.exception('Error while accessing user permissions')
1649 1719 return False
1650 1720
1651 1721 if self.required_perms.intersection(user_perms):
1652 1722 return True
1653 1723 return False
1654 1724
1655 1725
1656 1726 # SPECIAL VERSION TO HANDLE API AUTH
1657 1727 class _BaseApiPerm(object):
1658 1728 def __init__(self, *perms):
1659 1729 self.required_perms = set(perms)
1660 1730
1661 1731 def __call__(self, check_location=None, user=None, repo_name=None,
1662 1732 group_name=None, user_group_name=None):
1663 1733 cls_name = self.__class__.__name__
1664 1734 check_scope = 'global:%s' % (self.required_perms,)
1665 1735 if repo_name:
1666 1736 check_scope += ', repo_name:%s' % (repo_name,)
1667 1737
1668 1738 if group_name:
1669 1739 check_scope += ', repo_group_name:%s' % (group_name,)
1670 1740
1671 1741 if user_group_name:
1672 1742 check_scope += ', user_group_name:%s' % (user_group_name,)
1673 1743
1674 1744 log.debug(
1675 1745 'checking cls:%s %s %s @ %s'
1676 1746 % (cls_name, self.required_perms, check_scope, check_location))
1677 1747 if not user:
1678 1748 log.debug('Empty User passed into arguments')
1679 1749 return False
1680 1750
1681 1751 # process user
1682 1752 if not isinstance(user, AuthUser):
1683 1753 user = AuthUser(user.user_id)
1684 1754 if not check_location:
1685 1755 check_location = 'unspecified'
1686 1756 if self.check_permissions(user.permissions, repo_name, group_name,
1687 1757 user_group_name):
1688 1758 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1689 1759 check_scope, user, check_location)
1690 1760 return True
1691 1761
1692 1762 else:
1693 1763 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1694 1764 check_scope, user, check_location)
1695 1765 return False
1696 1766
1697 1767 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1698 1768 user_group_name=None):
1699 1769 """
1700 1770 implement in child class should return True if permissions are ok,
1701 1771 False otherwise
1702 1772
1703 1773 :param perm_defs: dict with permission definitions
1704 1774 :param repo_name: repo name
1705 1775 """
1706 1776 raise NotImplementedError()
1707 1777
1708 1778
1709 1779 class HasPermissionAllApi(_BaseApiPerm):
1710 1780 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1711 1781 user_group_name=None):
1712 1782 if self.required_perms.issubset(perm_defs.get('global')):
1713 1783 return True
1714 1784 return False
1715 1785
1716 1786
1717 1787 class HasPermissionAnyApi(_BaseApiPerm):
1718 1788 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1719 1789 user_group_name=None):
1720 1790 if self.required_perms.intersection(perm_defs.get('global')):
1721 1791 return True
1722 1792 return False
1723 1793
1724 1794
1725 1795 class HasRepoPermissionAllApi(_BaseApiPerm):
1726 1796 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1727 1797 user_group_name=None):
1728 1798 try:
1729 1799 _user_perms = set([perm_defs['repositories'][repo_name]])
1730 1800 except KeyError:
1731 1801 log.warning(traceback.format_exc())
1732 1802 return False
1733 1803 if self.required_perms.issubset(_user_perms):
1734 1804 return True
1735 1805 return False
1736 1806
1737 1807
1738 1808 class HasRepoPermissionAnyApi(_BaseApiPerm):
1739 1809 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1740 1810 user_group_name=None):
1741 1811 try:
1742 1812 _user_perms = set([perm_defs['repositories'][repo_name]])
1743 1813 except KeyError:
1744 1814 log.warning(traceback.format_exc())
1745 1815 return False
1746 1816 if self.required_perms.intersection(_user_perms):
1747 1817 return True
1748 1818 return False
1749 1819
1750 1820
1751 1821 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
1752 1822 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1753 1823 user_group_name=None):
1754 1824 try:
1755 1825 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1756 1826 except KeyError:
1757 1827 log.warning(traceback.format_exc())
1758 1828 return False
1759 1829 if self.required_perms.intersection(_user_perms):
1760 1830 return True
1761 1831 return False
1762 1832
1763 1833
1764 1834 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
1765 1835 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1766 1836 user_group_name=None):
1767 1837 try:
1768 1838 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1769 1839 except KeyError:
1770 1840 log.warning(traceback.format_exc())
1771 1841 return False
1772 1842 if self.required_perms.issubset(_user_perms):
1773 1843 return True
1774 1844 return False
1775 1845
1776 1846
1777 1847 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
1778 1848 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1779 1849 user_group_name=None):
1780 1850 try:
1781 1851 _user_perms = set([perm_defs['user_groups'][user_group_name]])
1782 1852 except KeyError:
1783 1853 log.warning(traceback.format_exc())
1784 1854 return False
1785 1855 if self.required_perms.intersection(_user_perms):
1786 1856 return True
1787 1857 return False
1788 1858
1789 1859
1790 1860 def check_ip_access(source_ip, allowed_ips=None):
1791 1861 """
1792 1862 Checks if source_ip is a subnet of any of allowed_ips.
1793 1863
1794 1864 :param source_ip:
1795 1865 :param allowed_ips: list of allowed ips together with mask
1796 1866 """
1797 1867 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1798 1868 source_ip_address = ipaddress.ip_address(source_ip)
1799 1869 if isinstance(allowed_ips, (tuple, list, set)):
1800 1870 for ip in allowed_ips:
1801 1871 try:
1802 1872 network_address = ipaddress.ip_network(ip, strict=False)
1803 1873 if source_ip_address in network_address:
1804 1874 log.debug('IP %s is network %s' %
1805 1875 (source_ip_address, network_address))
1806 1876 return True
1807 1877 # for any case we cannot determine the IP, don't crash just
1808 1878 # skip it and log as error, we want to say forbidden still when
1809 1879 # sending bad IP
1810 1880 except Exception:
1811 1881 log.error(traceback.format_exc())
1812 1882 continue
1813 1883 return False
1814 1884
1815 1885
1816 1886 def get_cython_compat_decorator(wrapper, func):
1817 1887 """
1818 1888 Creates a cython compatible decorator. The previously used
1819 1889 decorator.decorator() function seems to be incompatible with cython.
1820 1890
1821 1891 :param wrapper: __wrapper method of the decorator class
1822 1892 :param func: decorated function
1823 1893 """
1824 1894 @wraps(func)
1825 1895 def local_wrapper(*args, **kwds):
1826 1896 return wrapper(func, *args, **kwds)
1827 1897 local_wrapper.__wrapped__ = func
1828 1898 return local_wrapper
@@ -1,96 +1,101 b''
1 1 // tags.less
2 2 // For use in RhodeCode applications;
3 3 // see style guide documentation for guidelines.
4 4
5 5 // TAGS
6 6 .tag,
7 7 .tagtag {
8 8 display: inline-block;
9 9 min-height: 0;
10 10 margin: 0 auto;
11 11 padding: .25em;
12 12 text-align: center;
13 13 font-size: (-1 + @basefontsize); //fit in tables
14 14 line-height: .9em;
15 15 border: none;
16 16 .border-radius(@border-radius);
17 17 font-family: @text-regular;
18 18 background-image: none;
19 19 color: @grey4;
20 20 .border ( @border-thickness-tags, @grey4 );
21 21 white-space: nowrap;
22 22 a {
23 23 color: inherit;
24 24 text-decoration: underline;
25 25
26 26 i,
27 27 [class^="icon-"]:before,
28 28 [class*=" icon-"]:before {
29 29 text-decoration: none;
30 30 }
31 31 }
32 32 }
33 33
34 34 .tag0 { .border ( @border-thickness-tags, @grey4 ); color:@grey4; }
35 35 .tag1 { .border ( @border-thickness-tags, @color1 ); color:@color1; }
36 36 .tag2 { .border ( @border-thickness-tags, @color2 ); color:@color2; }
37 37 .tag3 { .border ( @border-thickness-tags, @color3 ); color:@color3; }
38 38 .tag4 { .border ( @border-thickness-tags, @color4 ); color:@color4; }
39 39 .tag5 { .border ( @border-thickness-tags, @color5 ); color:@color5; }
40 40 .tag6 { .border ( @border-thickness-tags, @color6 ); color:@color6; }
41 41 .tag7 { .border ( @border-thickness-tags, @color7 ); color:@color7; }
42 42 .tag8 { .border ( @border-thickness-tags, @color8 ); color:@color8; }
43 43
44 44 .metatag-list {
45 45 margin: 0;
46 46 padding: 0;
47 47
48 48 li {
49 49 margin: 0 0 @padding;
50 50 line-height: 1em;
51 51 list-style-type: none;
52 52
53 53 &:before { content: none; }
54 54 }
55 55 }
56 56
57 57 .branchtag, .booktag {
58 58 &:extend(.tag);
59 59
60 60
61 61 a {
62 62 color:inherit;
63 63 }
64 64 }
65 65
66 66 .metatag {
67 67 &:extend(.tag);
68 68 a {
69 69 color:inherit;
70 70 text-decoration: underline;
71 71 }
72 72 }
73 73
74 74 [tag="featured"] { &:extend(.tag1); }
75 75 [tag="stale"] { &:extend(.tag2); }
76 76 [tag="dead"] { &:extend(.tag3); }
77 77 [tag="lang"] { &:extend(.tag4); }
78 78 [tag="license"] { &:extend(.tag5); }
79 79 [tag="requires"] { &:extend(.tag6); }
80 80 [tag="recommends"] { &:extend(.tag7); }
81 81 [tag="see"] { &:extend(.tag8); }
82 82
83 .perm_overriden {
84 text-decoration: line-through;
85 opacity: 0.6;
86 }
87
83 88 .perm_tag {
84 89 &:extend(.tag);
85 90
86 91 &.read {
87 92 &:extend(.tag1);
88 93 }
89 94
90 95 &.write {
91 96 &:extend(.tag4);
92 97 }
93 98 &.admin {
94 99 &:extend(.tag5);
95 100 }
96 101 } No newline at end of file
@@ -1,200 +1,207 b''
1 1 ## snippet for displaying permissions overview for users
2 2 ## usage:
3 3 ## <%namespace name="p" file="/base/perms_summary.html"/>
4 4 ## ${p.perms_summary(c.perm_user.permissions)}
5 5
6 6 <%def name="perms_summary(permissions, show_all=False, actions=True)">
7
8 7 <div id="perms" class="table fields">
9 8 %for section in sorted(permissions.keys()):
10 9 <div class="panel panel-default">
11 10 <div class="panel-heading">
12 11 <h3 class="panel-title">${section.replace("_"," ").capitalize()}</h3>
13 12 </div>
14 13 <div class="panel-body">
15 14 <div class="perms_section_head field">
16 15 <div class="radios">
17 16 %if section != 'global':
18 17 <span class="permissions_boxes">
19 18 <span class="desc">${_('show')}: </span>
20 19 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_%s' % section}"><span class="perm_tag none">${_('none')}</span></label>
21 20 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_%s' % section}"><span class="perm_tag read">${_('read')}</span></label>
22 21 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_%s' % section}"> <span class="perm_tag write">${_('write')}</span></label>
23 22 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_%s' % section}"><span class="perm_tag admin">${_('admin')}</span></label>
24 23 </span>
25 24 %endif
26 25 </div>
27 26 </div>
28 27 <div class="field">
29 28 %if not permissions[section]:
30 29 <p class="empty_data help-block">${_('No permissions defined')}</p>
31 30 %else:
32 31 <div id='tbl_list_wrap_${section}'>
33 32 <table id="tbl_list_${section}" class="rctable">
34 33 ## global permission box
35 34 %if section == 'global':
36 35 <thead>
37 36 <tr>
38 37 <th colspan="2" class="left">${_('Permission')}</th>
39 38 %if actions:
40 39 <th>${_('Edit Permission')}</th>
41 40 %endif
42 41 </thead>
43 42 <tbody>
44 43
45 44 <%
46 45 def get_section_perms(prefix, opts):
47 46 _selected = []
48 47 for op in opts:
49 48 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
50 49 _selected.append(op)
51 50 admin = 'hg.admin' in opts
52 51 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
53 52 return admin, _selected_vals, _selected
54 53 %>
55 54 <%def name="glob(lbl, val, val_lbl=None, custom_url=None)">
56 55 <tr>
57 56 <td class="td-tags">
58 57 ${lbl}
59 58 </td>
60 59 <td class="td-tags">
61 60 %if val[0]:
62 61 %if not val_lbl:
63 62 ${h.bool2icon(True)}
64 63 %else:
65 64 <span class="perm_tag admin">${val_lbl}.admin</span>
66 65 %endif
67 66 %else:
68 67 %if not val_lbl:
69 68 ${h.bool2icon({'false': False,
70 69 'true': True,
71 70 'none': False,
72 71 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false'))}
73 72 %else:
74 73 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
75 74 %endif
76 75 %endif
77 76 </td>
78 77 %if actions:
79 78 <td class="td-action">
80 79 <a href="${custom_url or h.url('admin_permissions_global')}">${_('edit')}</a>
81 80 </td>
82 81 %endif
83 82 </tr>
84 83 </%def>
85 84
86 85 ${glob(_('Super admin'), get_section_perms('hg.admin', permissions[section]))}
87 86
88 87 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository', h.url('admin_permissions_object'))}
89 88 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group', h.url('admin_permissions_object'))}
90 89 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup', h.url('admin_permissions_object'))}
91 90
92 91 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]), custom_url=h.url('admin_permissions_global'))}
93 92 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]), custom_url=h.url('admin_permissions_global'))}
94 93 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]), custom_url=h.url('admin_permissions_global'))}
95 94 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]), custom_url=h.url('admin_permissions_global'))}
96 95
97 96
98 97 </tbody>
99 98 %else:
100 99 ## none/read/write/admin permissions on groups/repos etc
101 100 <thead>
102 101 <tr>
103 102 <th>${_('Name')}</th>
104 103 <th>${_('Permission')}</th>
105 104 %if actions:
106 105 <th>${_('Edit Permission')}</th>
107 106 %endif
108 107 </thead>
109 108 <tbody class="section_${section}">
110 109 <%
111 110 def sorter(permissions):
112 111 def custom_sorter(item):
113 112 ## read/write/admin
114 113 section = item[1].split('.')[-1]
115 114 section_importance = {'none': u'0',
116 115 'read': u'1',
117 116 'write':u'2',
118 117 'admin':u'3'}.get(section)
119 118 ## sort by group importance+name
120 119 return section_importance+item[0]
121 120 return sorted(permissions, key=custom_sorter)
122 121 %>
123 122 %for k, section_perm in sorter(permissions[section].items()):
124 123 %if section_perm.split('.')[-1] != 'none' or show_all:
125 124 <tr class="perm_row ${'%s_%s' % (section, section_perm.split('.')[-1])}">
126 125 <td class="td-componentname">
127 126 %if section == 'repositories':
128 127 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
129 128 %elif section == 'repositories_groups':
130 129 <a href="${h.url('repo_group_home',group_name=k)}">${k}</a>
131 130 %elif section == 'user_groups':
132 131 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${k}</a>
133 132 ${k}
134 133 %endif
135 134 </td>
136 135 <td class="td-tags">
136 %if hasattr(permissions[section], 'perm_origin_stack'):
137 %for i, (perm, origin) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
138 <span class="${i > 0 and 'perm_overriden' or ''} perm_tag ${perm.split('.')[-1]}">
139 ${perm} (${origin})
140 </span>
141 %endfor
142 %else:
137 143 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
144 %endif
138 145 </td>
139 146 %if actions:
140 147 <td class="td-action">
141 148 %if section == 'repositories':
142 149 <a href="${h.url('edit_repo_perms',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
143 150 %elif section == 'repositories_groups':
144 151 <a href="${h.url('edit_repo_group_perms',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
145 152 %elif section == 'user_groups':
146 153 ##<a href="${h.url('edit_users_group',user_group_id=k)}">${_('edit')}</a>
147 154 %endif
148 155 </td>
149 156 %endif
150 157 </tr>
151 158 %endif
152 159 %endfor
153 160
154 161 <tr id="empty_${section}" class="noborder" style="display:none;">
155 162 <td colspan="6">${_('No permission defined')}</td>
156 163 </tr>
157 164
158 165 </tbody>
159 166 %endif
160 167 </table>
161 168 </div>
162 169 %endif
163 170 </div>
164 171 </div>
165 172 </div>
166 173 %endfor
167 174 </div>
168 175
169 176 <script>
170 177 $(document).ready(function(){
171 178 var show_empty = function(section){
172 179 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
173 180 if(visible == 0){
174 181 $('#empty_{0}'.format(section)).show();
175 182 }
176 183 else{
177 184 $('#empty_{0}'.format(section)).hide();
178 185 }
179 186 };
180 187 $('.perm_filter').on('change', function(e){
181 188 var self = this;
182 189 var section = $(this).attr('section');
183 190
184 191 var opts = {};
185 192 var elems = $('.filter_' + section).each(function(el){
186 193 var perm_type = $(this).attr('perm_type');
187 194 var checked = this.checked;
188 195 opts[perm_type] = checked;
189 196 if(checked){
190 197 $('.'+section+'_'+perm_type).show();
191 198 }
192 199 else{
193 200 $('.'+section+'_'+perm_type).hide();
194 201 }
195 202 });
196 203 show_empty(section);
197 204 })
198 205 })
199 206 </script>
200 207 </%def>
@@ -1,186 +1,186 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 import pytest
22 22
23 23 from rhodecode.tests import (
24 24 TestController, url, assert_session_flash, link_to)
25 25 from rhodecode.model.db import User, UserGroup
26 26 from rhodecode.model.meta import Session
27 27 from rhodecode.tests.fixture import Fixture
28 28
29 29 TEST_USER_GROUP = 'admins_test'
30 30
31 31 fixture = Fixture()
32 32
33 33
34 34 class TestAdminUsersGroupsController(TestController):
35 35
36 36 def test_index(self):
37 37 self.log_user()
38 38 response = self.app.get(url('users_groups'))
39 response.mustcontain('No members yet')
39 response.status_int == 200
40 40
41 41 def test_create(self):
42 42 self.log_user()
43 43 users_group_name = TEST_USER_GROUP
44 44 response = self.app.post(url('users_groups'), {
45 45 'users_group_name': users_group_name,
46 46 'user_group_description': 'DESC',
47 47 'active': True,
48 48 'csrf_token': self.csrf_token})
49 49
50 50 user_group_link = link_to(
51 51 users_group_name,
52 52 url('edit_users_group',
53 53 user_group_id=UserGroup.get_by_group_name(
54 54 users_group_name).users_group_id))
55 55 assert_session_flash(
56 56 response,
57 57 'Created user group %s' % user_group_link)
58 58
59 59 def test_delete(self):
60 60 self.log_user()
61 61 users_group_name = TEST_USER_GROUP + 'another'
62 62 response = self.app.post(url('users_groups'), {
63 63 'users_group_name': users_group_name,
64 64 'user_group_description': 'DESC',
65 65 'active': True,
66 66 'csrf_token': self.csrf_token})
67 67
68 68 user_group_link = link_to(
69 69 users_group_name,
70 70 url('edit_users_group',
71 71 user_group_id=UserGroup.get_by_group_name(
72 72 users_group_name).users_group_id))
73 73 assert_session_flash(
74 74 response,
75 75 'Created user group %s' % user_group_link)
76 76
77 77 group = Session().query(UserGroup).filter(
78 78 UserGroup.users_group_name == users_group_name).one()
79 79
80 80 response = self.app.post(
81 81 url('delete_users_group', user_group_id=group.users_group_id),
82 82 params={'_method': 'delete', 'csrf_token': self.csrf_token})
83 83
84 84 group = Session().query(UserGroup).filter(
85 85 UserGroup.users_group_name == users_group_name).scalar()
86 86
87 87 assert group is None
88 88
89 89 @pytest.mark.parametrize('repo_create, repo_create_write, user_group_create, repo_group_create, fork_create, inherit_default_permissions, expect_error, expect_form_error', [
90 90 ('hg.create.none', 'hg.create.write_on_repogroup.false', 'hg.usergroup.create.false', 'hg.repogroup.create.false', 'hg.fork.none', 'hg.inherit_default_perms.false', False, False),
91 91 ('hg.create.repository', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, False),
92 92 ('hg.create.XXX', 'hg.create.write_on_repogroup.true', 'hg.usergroup.create.true', 'hg.repogroup.create.true', 'hg.fork.repository', 'hg.inherit_default_perms.false', False, True),
93 93 ('', '', '', '', '', '', True, False),
94 94 ])
95 95 def test_global_perms_on_group(
96 96 self, repo_create, repo_create_write, user_group_create,
97 97 repo_group_create, fork_create, expect_error, expect_form_error,
98 98 inherit_default_permissions):
99 99 self.log_user()
100 100 users_group_name = TEST_USER_GROUP + 'another2'
101 101 response = self.app.post(url('users_groups'),
102 102 {'users_group_name': users_group_name,
103 103 'user_group_description': 'DESC',
104 104 'active': True,
105 105 'csrf_token': self.csrf_token})
106 106
107 107 ug = UserGroup.get_by_group_name(users_group_name)
108 108 user_group_link = link_to(
109 109 users_group_name,
110 110 url('edit_users_group', user_group_id=ug.users_group_id))
111 111 assert_session_flash(
112 112 response,
113 113 'Created user group %s' % user_group_link)
114 114 response.follow()
115 115
116 116 # ENABLE REPO CREATE ON A GROUP
117 117 perm_params = {
118 118 'inherit_default_permissions': False,
119 119 'default_repo_create': repo_create,
120 120 'default_repo_create_on_write': repo_create_write,
121 121 'default_user_group_create': user_group_create,
122 122 'default_repo_group_create': repo_group_create,
123 123 'default_fork_create': fork_create,
124 124 'default_inherit_default_permissions': inherit_default_permissions,
125 125
126 126 '_method': 'put',
127 127 'csrf_token': self.csrf_token,
128 128 }
129 129 response = self.app.post(
130 130 url('edit_user_group_global_perms',
131 131 user_group_id=ug.users_group_id),
132 132 params=perm_params)
133 133
134 134 if expect_form_error:
135 135 assert response.status_int == 200
136 136 response.mustcontain('Value must be one of')
137 137 else:
138 138 if expect_error:
139 139 msg = 'An error occurred during permissions saving'
140 140 else:
141 141 msg = 'User Group global permissions updated successfully'
142 142 ug = UserGroup.get_by_group_name(users_group_name)
143 143 del perm_params['_method']
144 144 del perm_params['csrf_token']
145 145 del perm_params['inherit_default_permissions']
146 146 assert perm_params == ug.get_default_perms()
147 147 assert_session_flash(response, msg)
148 148
149 149 fixture.destroy_user_group(users_group_name)
150 150
151 151 def test_edit(self):
152 152 self.log_user()
153 153 response = self.app.get(url('edit_users_group', user_group_id=1))
154 154
155 155 def test_edit_user_group_members(self):
156 156 self.log_user()
157 157 response = self.app.get(url('edit_user_group_members', user_group_id=1))
158 158 response.mustcontain('No members yet')
159 159
160 160 def test_usergroup_escape(self):
161 161 user = User.get_by_username('test_admin')
162 162 user.name = '<img src="/image1" onload="alert(\'Hello, World!\');">'
163 163 user.lastname = (
164 164 '<img src="/image2" onload="alert(\'Hello, World!\');">')
165 165 Session().add(user)
166 166 Session().commit()
167 167
168 168 self.log_user()
169 169 users_group_name = 'samplegroup'
170 170 data = {
171 171 'users_group_name': users_group_name,
172 172 'user_group_description': (
173 173 '<strong onload="alert();">DESC</strong>'),
174 174 'active': True,
175 175 'csrf_token': self.csrf_token
176 176 }
177 177
178 178 response = self.app.post(url('users_groups'), data)
179 179 response = self.app.get(url('users_groups'))
180 180
181 181 response.mustcontain(
182 182 '&lt;strong onload=&#34;alert();&#34;&gt;'
183 183 'DESC&lt;/strong&gt;')
184 184 response.mustcontain(
185 185 '&lt;img src=&#34;/image2&#34; onload=&#34;'
186 186 'alert(&#39;Hello, World!&#39;);&#34;&gt;')
@@ -1,552 +1,582 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 import os
22 22 from hashlib import sha1
23 23
24 24 import pytest
25 25 from mock import patch
26 26
27 27 from rhodecode.lib import auth
28 28 from rhodecode.lib.utils2 import md5
29 29 from rhodecode.model.db import User
30 30 from rhodecode.model.repo import RepoModel
31 31 from rhodecode.model.user import UserModel
32 32 from rhodecode.model.user_group import UserGroupModel
33 33
34 34
35 def test_perm_origin_dict():
36 pod = auth.PermOriginDict()
37 pod['thing'] = 'read', 'default'
38 assert pod['thing'] == 'read'
39
40 assert pod.perm_origin_stack == {
41 'thing': [('read', 'default')]}
42
43 pod['thing'] = 'write', 'admin'
44 assert pod['thing'] == 'write'
45
46 assert pod.perm_origin_stack == {
47 'thing': [('read', 'default'), ('write', 'admin')]}
48
49 pod['other'] = 'write', 'default'
50
51 assert pod.perm_origin_stack == {
52 'other': [('write', 'default')],
53 'thing': [('read', 'default'), ('write', 'admin')]}
54
55 pod['other'] = 'none', 'override'
56
57 assert pod.perm_origin_stack == {
58 'other': [('write', 'default'), ('none', 'override')],
59 'thing': [('read', 'default'), ('write', 'admin')]}
60
61 with pytest.raises(ValueError):
62 pod['thing'] = 'read'
63
64
35 65 def test_cached_perms_data(user_regular, backend_random):
36 66 permissions = get_permissions(user_regular)
37 67 repo_name = backend_random.repo.repo_name
38 68 expected_global_permissions = {
39 69 'repository.read', 'group.read', 'usergroup.read'}
40 70 assert expected_global_permissions.issubset(permissions['global'])
41 71 assert permissions['repositories'][repo_name] == 'repository.read'
42 72
43 73
44 74 def test_cached_perms_data_with_admin_user(user_regular, backend_random):
45 75 permissions = get_permissions(user_regular, user_is_admin=True)
46 76 repo_name = backend_random.repo.repo_name
47 77 assert 'hg.admin' in permissions['global']
48 78 assert permissions['repositories'][repo_name] == 'repository.admin'
49 79
50 80
51 81 def test_cached_perms_data_user_group_global_permissions(user_util):
52 82 user, user_group = user_util.create_user_with_group()
53 83 user_group.inherit_default_permissions = False
54 84
55 85 granted_permission = 'repository.write'
56 86 UserGroupModel().grant_perm(user_group, granted_permission)
57 87
58 88 permissions = get_permissions(user)
59 89 assert granted_permission in permissions['global']
60 90
61 91
62 92 @pytest.mark.xfail(reason="Not implemented, see TODO note")
63 93 def test_cached_perms_data_user_group_global_permissions_(user_util):
64 94 user, user_group = user_util.create_user_with_group()
65 95
66 96 granted_permission = 'repository.write'
67 97 UserGroupModel().grant_perm(user_group, granted_permission)
68 98
69 99 permissions = get_permissions(user)
70 100 assert granted_permission in permissions['global']
71 101
72 102
73 103 def test_cached_perms_data_user_global_permissions(user_util):
74 104 user = user_util.create_user()
75 105 UserModel().grant_perm(user, 'repository.none')
76 106
77 107 permissions = get_permissions(user, user_inherit_default_permissions=True)
78 108 assert 'repository.read' in permissions['global']
79 109
80 110
81 111 def test_cached_perms_data_repository_permissions_on_private_repository(
82 112 backend_random, user_util):
83 113 user, user_group = user_util.create_user_with_group()
84 114
85 115 repo = backend_random.create_repo()
86 116 repo.private = True
87 117
88 118 granted_permission = 'repository.write'
89 119 RepoModel().grant_user_group_permission(
90 120 repo, user_group.users_group_name, granted_permission)
91 121
92 122 permissions = get_permissions(user)
93 123 assert permissions['repositories'][repo.repo_name] == granted_permission
94 124
95 125
96 126 def test_cached_perms_data_repository_permissions_for_owner(
97 127 backend_random, user_util):
98 128 user = user_util.create_user()
99 129
100 130 repo = backend_random.create_repo()
101 131 repo.user_id = user.user_id
102 132
103 133 permissions = get_permissions(user)
104 134 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
105 135
106 136 # TODO: johbo: Make cleanup in UserUtility smarter, then remove this hack
107 137 repo.user_id = User.get_default_user().user_id
108 138
109 139
110 140 def test_cached_perms_data_repository_permissions_not_inheriting_defaults(
111 141 backend_random, user_util):
112 142 user = user_util.create_user()
113 143 repo = backend_random.create_repo()
114 144
115 145 # Don't inherit default object permissions
116 146 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
117 147
118 148 permissions = get_permissions(user)
119 149 assert permissions['repositories'][repo.repo_name] == 'repository.none'
120 150
121 151
122 152 def test_cached_perms_data_default_permissions_on_repository_group(user_util):
123 153 # Have a repository group with default permissions set
124 154 repo_group = user_util.create_repo_group()
125 155 default_user = User.get_default_user()
126 156 user_util.grant_user_permission_to_repo_group(
127 157 repo_group, default_user, 'repository.write')
128 158 user = user_util.create_user()
129 159
130 160 permissions = get_permissions(user)
131 161 assert permissions['repositories_groups'][repo_group.group_name] == \
132 162 'repository.write'
133 163
134 164
135 165 def test_cached_perms_data_default_permissions_on_repository_group_owner(
136 166 user_util):
137 167 # Have a repository group
138 168 repo_group = user_util.create_repo_group()
139 169 default_user = User.get_default_user()
140 170
141 171 # Add a permission for the default user to hit the code path
142 172 user_util.grant_user_permission_to_repo_group(
143 173 repo_group, default_user, 'repository.write')
144 174
145 175 # Have an owner of the group
146 176 user = user_util.create_user()
147 177 repo_group.user_id = user.user_id
148 178
149 179 permissions = get_permissions(user)
150 180 assert permissions['repositories_groups'][repo_group.group_name] == \
151 181 'group.admin'
152 182
153 183
154 184 def test_cached_perms_data_default_permissions_on_repository_group_no_inherit(
155 185 user_util):
156 186 # Have a repository group
157 187 repo_group = user_util.create_repo_group()
158 188 default_user = User.get_default_user()
159 189
160 190 # Add a permission for the default user to hit the code path
161 191 user_util.grant_user_permission_to_repo_group(
162 192 repo_group, default_user, 'repository.write')
163 193
164 194 # Don't inherit default object permissions
165 195 user = user_util.create_user()
166 196 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
167 197
168 198 permissions = get_permissions(user)
169 199 assert permissions['repositories_groups'][repo_group.group_name] == \
170 200 'group.none'
171 201
172 202
173 203 def test_cached_perms_data_repository_permissions_from_user_group(
174 204 user_util, backend_random):
175 205 user, user_group = user_util.create_user_with_group()
176 206
177 207 # Needs a second user group to make sure that we select the right
178 208 # permissions.
179 209 user_group2 = user_util.create_user_group()
180 210 UserGroupModel().add_user_to_group(user_group2, user)
181 211
182 212 repo = backend_random.create_repo()
183 213
184 214 RepoModel().grant_user_group_permission(
185 215 repo, user_group.users_group_name, 'repository.read')
186 216 RepoModel().grant_user_group_permission(
187 217 repo, user_group2.users_group_name, 'repository.write')
188 218
189 219 permissions = get_permissions(user)
190 220 assert permissions['repositories'][repo.repo_name] == 'repository.write'
191 221
192 222
193 223 def test_cached_perms_data_repository_permissions_from_user_group_owner(
194 224 user_util, backend_random):
195 225 user, user_group = user_util.create_user_with_group()
196 226
197 227 repo = backend_random.create_repo()
198 228 repo.user_id = user.user_id
199 229
200 230 RepoModel().grant_user_group_permission(
201 231 repo, user_group.users_group_name, 'repository.write')
202 232
203 233 permissions = get_permissions(user)
204 234 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
205 235
206 236
207 237 def test_cached_perms_data_user_repository_permissions(
208 238 user_util, backend_random):
209 239 user = user_util.create_user()
210 240 repo = backend_random.create_repo()
211 241 granted_permission = 'repository.write'
212 242 RepoModel().grant_user_permission(repo, user, granted_permission)
213 243
214 244 permissions = get_permissions(user)
215 245 assert permissions['repositories'][repo.repo_name] == granted_permission
216 246
217 247
218 248 def test_cached_perms_data_user_repository_permissions_explicit(
219 249 user_util, backend_random):
220 250 user = user_util.create_user()
221 251 repo = backend_random.create_repo()
222 252 granted_permission = 'repository.none'
223 253 RepoModel().grant_user_permission(repo, user, granted_permission)
224 254
225 255 permissions = get_permissions(user, explicit=True)
226 256 assert permissions['repositories'][repo.repo_name] == granted_permission
227 257
228 258
229 259 def test_cached_perms_data_user_repository_permissions_owner(
230 260 user_util, backend_random):
231 261 user = user_util.create_user()
232 262 repo = backend_random.create_repo()
233 263 repo.user_id = user.user_id
234 264 RepoModel().grant_user_permission(repo, user, 'repository.write')
235 265
236 266 permissions = get_permissions(user)
237 267 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
238 268
239 269
240 270 def test_cached_perms_data_repository_groups_permissions_inherited(
241 271 user_util, backend_random):
242 272 user, user_group = user_util.create_user_with_group()
243 273
244 274 # Needs a second group to hit the last condition
245 275 user_group2 = user_util.create_user_group()
246 276 UserGroupModel().add_user_to_group(user_group2, user)
247 277
248 278 repo_group = user_util.create_repo_group()
249 279
250 280 user_util.grant_user_group_permission_to_repo_group(
251 281 repo_group, user_group, 'group.read')
252 282 user_util.grant_user_group_permission_to_repo_group(
253 283 repo_group, user_group2, 'group.write')
254 284
255 285 permissions = get_permissions(user)
256 286 assert permissions['repositories_groups'][repo_group.group_name] == \
257 287 'group.write'
258 288
259 289
260 290 def test_cached_perms_data_repository_groups_permissions_inherited_owner(
261 291 user_util, backend_random):
262 292 user, user_group = user_util.create_user_with_group()
263 293 repo_group = user_util.create_repo_group()
264 294 repo_group.user_id = user.user_id
265 295
266 296 granted_permission = 'group.write'
267 297 user_util.grant_user_group_permission_to_repo_group(
268 298 repo_group, user_group, granted_permission)
269 299
270 300 permissions = get_permissions(user)
271 301 assert permissions['repositories_groups'][repo_group.group_name] == \
272 302 'group.admin'
273 303
274 304
275 305 def test_cached_perms_data_repository_groups_permissions(
276 306 user_util, backend_random):
277 307 user = user_util.create_user()
278 308
279 309 repo_group = user_util.create_repo_group()
280 310
281 311 granted_permission = 'group.write'
282 312 user_util.grant_user_permission_to_repo_group(
283 313 repo_group, user, granted_permission)
284 314
285 315 permissions = get_permissions(user)
286 316 assert permissions['repositories_groups'][repo_group.group_name] == \
287 317 'group.write'
288 318
289 319
290 320 def test_cached_perms_data_repository_groups_permissions_explicit(
291 321 user_util, backend_random):
292 322 user = user_util.create_user()
293 323
294 324 repo_group = user_util.create_repo_group()
295 325
296 326 granted_permission = 'group.none'
297 327 user_util.grant_user_permission_to_repo_group(
298 328 repo_group, user, granted_permission)
299 329
300 330 permissions = get_permissions(user, explicit=True)
301 331 assert permissions['repositories_groups'][repo_group.group_name] == \
302 332 'group.none'
303 333
304 334
305 335 def test_cached_perms_data_repository_groups_permissions_owner(
306 336 user_util, backend_random):
307 337 user = user_util.create_user()
308 338
309 339 repo_group = user_util.create_repo_group()
310 340 repo_group.user_id = user.user_id
311 341
312 342 granted_permission = 'group.write'
313 343 user_util.grant_user_permission_to_repo_group(
314 344 repo_group, user, granted_permission)
315 345
316 346 permissions = get_permissions(user)
317 347 assert permissions['repositories_groups'][repo_group.group_name] == \
318 348 'group.admin'
319 349
320 350
321 351 def test_cached_perms_data_user_group_permissions_inherited(
322 352 user_util, backend_random):
323 353 user, user_group = user_util.create_user_with_group()
324 354 user_group2 = user_util.create_user_group()
325 355 UserGroupModel().add_user_to_group(user_group2, user)
326 356
327 357 target_user_group = user_util.create_user_group()
328 358
329 359 user_util.grant_user_group_permission_to_user_group(
330 360 target_user_group, user_group, 'usergroup.read')
331 361 user_util.grant_user_group_permission_to_user_group(
332 362 target_user_group, user_group2, 'usergroup.write')
333 363
334 364 permissions = get_permissions(user)
335 365 assert permissions['user_groups'][target_user_group.users_group_name] == \
336 366 'usergroup.write'
337 367
338 368
339 369 def test_cached_perms_data_user_group_permissions(
340 370 user_util, backend_random):
341 371 user = user_util.create_user()
342 372 user_group = user_util.create_user_group()
343 373 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.write')
344 374
345 375 permissions = get_permissions(user)
346 376 assert permissions['user_groups'][user_group.users_group_name] == \
347 377 'usergroup.write'
348 378
349 379
350 380 def test_cached_perms_data_user_group_permissions_explicit(
351 381 user_util, backend_random):
352 382 user = user_util.create_user()
353 383 user_group = user_util.create_user_group()
354 384 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.none')
355 385
356 386 permissions = get_permissions(user, explicit=True)
357 387 assert permissions['user_groups'][user_group.users_group_name] == \
358 388 'usergroup.none'
359 389
360 390
361 391 def test_cached_perms_data_user_group_permissions_not_inheriting_defaults(
362 392 user_util, backend_random):
363 393 user = user_util.create_user()
364 394 user_group = user_util.create_user_group()
365 395
366 396 # Don't inherit default object permissions
367 397 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
368 398
369 399 permissions = get_permissions(user)
370 400 assert permissions['user_groups'][user_group.users_group_name] == \
371 401 'usergroup.none'
372 402
373 403
374 404 def test_permission_calculator_admin_permissions(
375 405 user_util, backend_random):
376 406 user = user_util.create_user()
377 407 user_group = user_util.create_user_group()
378 408 repo = backend_random.repo
379 409 repo_group = user_util.create_repo_group()
380 410
381 411 calculator = auth.PermissionCalculator(
382 412 user.user_id, {}, False, False, True, 'higherwin')
383 413 permissions = calculator._admin_permissions()
384 414
385 415 assert permissions['repositories_groups'][repo_group.group_name] == \
386 416 'group.admin'
387 417 assert permissions['user_groups'][user_group.users_group_name] == \
388 418 'usergroup.admin'
389 419 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
390 420 assert 'hg.admin' in permissions['global']
391 421
392 422
393 423 def test_permission_calculator_repository_permissions_robustness_from_group(
394 424 user_util, backend_random):
395 425 user, user_group = user_util.create_user_with_group()
396 426
397 427 RepoModel().grant_user_group_permission(
398 428 backend_random.repo, user_group.users_group_name, 'repository.write')
399 429
400 430 calculator = auth.PermissionCalculator(
401 431 user.user_id, {}, False, False, False, 'higherwin')
402 432 calculator._calculate_repository_permissions()
403 433
404 434
405 435 def test_permission_calculator_repository_permissions_robustness_from_user(
406 436 user_util, backend_random):
407 437 user = user_util.create_user()
408 438
409 439 RepoModel().grant_user_permission(
410 440 backend_random.repo, user, 'repository.write')
411 441
412 442 calculator = auth.PermissionCalculator(
413 443 user.user_id, {}, False, False, False, 'higherwin')
414 444 calculator._calculate_repository_permissions()
415 445
416 446
417 447 def test_permission_calculator_repo_group_permissions_robustness_from_group(
418 448 user_util, backend_random):
419 449 user, user_group = user_util.create_user_with_group()
420 450 repo_group = user_util.create_repo_group()
421 451
422 452 user_util.grant_user_group_permission_to_repo_group(
423 453 repo_group, user_group, 'group.write')
424 454
425 455 calculator = auth.PermissionCalculator(
426 456 user.user_id, {}, False, False, False, 'higherwin')
427 457 calculator._calculate_repository_group_permissions()
428 458
429 459
430 460 def test_permission_calculator_repo_group_permissions_robustness_from_user(
431 461 user_util, backend_random):
432 462 user = user_util.create_user()
433 463 repo_group = user_util.create_repo_group()
434 464
435 465 user_util.grant_user_permission_to_repo_group(
436 466 repo_group, user, 'group.write')
437 467
438 468 calculator = auth.PermissionCalculator(
439 469 user.user_id, {}, False, False, False, 'higherwin')
440 470 calculator._calculate_repository_group_permissions()
441 471
442 472
443 473 def test_permission_calculator_user_group_permissions_robustness_from_group(
444 474 user_util, backend_random):
445 475 user, user_group = user_util.create_user_with_group()
446 476 target_user_group = user_util.create_user_group()
447 477
448 478 user_util.grant_user_group_permission_to_user_group(
449 479 target_user_group, user_group, 'usergroup.write')
450 480
451 481 calculator = auth.PermissionCalculator(
452 482 user.user_id, {}, False, False, False, 'higherwin')
453 483 calculator._calculate_user_group_permissions()
454 484
455 485
456 486 def test_permission_calculator_user_group_permissions_robustness_from_user(
457 487 user_util, backend_random):
458 488 user = user_util.create_user()
459 489 target_user_group = user_util.create_user_group()
460 490
461 491 user_util.grant_user_permission_to_user_group(
462 492 target_user_group, user, 'usergroup.write')
463 493
464 494 calculator = auth.PermissionCalculator(
465 495 user.user_id, {}, False, False, False, 'higherwin')
466 496 calculator._calculate_user_group_permissions()
467 497
468 498
469 499 @pytest.mark.parametrize("algo, new_permission, old_permission, expected", [
470 500 ('higherwin', 'repository.none', 'repository.none', 'repository.none'),
471 501 ('higherwin', 'repository.read', 'repository.none', 'repository.read'),
472 502 ('lowerwin', 'repository.write', 'repository.write', 'repository.write'),
473 503 ('lowerwin', 'repository.read', 'repository.write', 'repository.read'),
474 504 ])
475 505 def test_permission_calculator_choose_permission(
476 506 user_regular, algo, new_permission, old_permission, expected):
477 507 calculator = auth.PermissionCalculator(
478 508 user_regular.user_id, {}, False, False, False, algo)
479 509 result = calculator._choose_permission(new_permission, old_permission)
480 510 assert result == expected
481 511
482 512
483 513 def test_permission_calculator_choose_permission_raises_on_wrong_algo(
484 514 user_regular):
485 515 calculator = auth.PermissionCalculator(
486 516 user_regular.user_id, {}, False, False, False, 'invalid')
487 517 result = calculator._choose_permission(
488 518 'repository.read', 'repository.read')
489 519 # TODO: johbo: This documents the existing behavior. Think of an
490 520 # improvement.
491 521 assert result is None
492 522
493 523
494 524 def test_auth_user_get_cookie_store_for_normal_user(user_util):
495 525 user = user_util.create_user()
496 526 auth_user = auth.AuthUser(user_id=user.user_id)
497 527 expected_data = {
498 528 'username': user.username,
499 529 'user_id': user.user_id,
500 530 'password': md5(user.password),
501 531 'is_authenticated': False
502 532 }
503 533 assert auth_user.get_cookie_store() == expected_data
504 534
505 535
506 536 def test_auth_user_get_cookie_store_for_default_user():
507 537 default_user = User.get_default_user()
508 538 auth_user = auth.AuthUser()
509 539 expected_data = {
510 540 'username': User.DEFAULT_USER,
511 541 'user_id': default_user.user_id,
512 542 'password': md5(default_user.password),
513 543 'is_authenticated': True
514 544 }
515 545 assert auth_user.get_cookie_store() == expected_data
516 546
517 547
518 548 def get_permissions(user, **kwargs):
519 549 """
520 550 Utility filling in useful defaults into the call to `_cached_perms_data`.
521 551
522 552 Fill in `**kwargs` if specific values are needed for a test.
523 553 """
524 554 call_args = {
525 555 'user_id': user.user_id,
526 556 'scope': {},
527 557 'user_is_admin': False,
528 558 'user_inherit_default_permissions': False,
529 559 'explicit': False,
530 560 'algo': 'higherwin',
531 561 }
532 562 call_args.update(kwargs)
533 563 permissions = auth._cached_perms_data(**call_args)
534 564 return permissions
535 565
536 566
537 567 class TestGenerateAuthToken(object):
538 568 def test_salt_is_used_when_specified(self):
539 569 salt = 'abcde'
540 570 user_name = 'test_user'
541 571 result = auth.generate_auth_token(user_name, salt)
542 572 expected_result = sha1(user_name + salt).hexdigest()
543 573 assert result == expected_result
544 574
545 575 def test_salt_is_geneated_when_not_specified(self):
546 576 user_name = 'test_user'
547 577 random_salt = os.urandom(16)
548 578 with patch.object(auth, 'os') as os_mock:
549 579 os_mock.urandom.return_value = random_salt
550 580 result = auth.generate_auth_token(user_name)
551 581 expected_result = sha1(user_name + random_salt).hexdigest()
552 582 assert result == expected_result
General Comments 0
You need to be logged in to leave comments. Login now