##// END OF EJS Templates
permissions: fixed a case when a duplicate permission was making archive operation still display settings.
marcink -
r4414:92157ec6 default
parent child Browse files
Show More
@@ -1,2502 +1,2511 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 authentication and permission libraries
23 23 """
24 24
25 25 import os
26 26
27 27 import colander
28 28 import time
29 29 import collections
30 30 import fnmatch
31 31 import hashlib
32 32 import itertools
33 33 import logging
34 34 import random
35 35 import traceback
36 36 from functools import wraps
37 37
38 38 import ipaddress
39 39
40 40 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
41 41 from sqlalchemy.orm.exc import ObjectDeletedError
42 42 from sqlalchemy.orm import joinedload
43 43 from zope.cachedescriptors.property import Lazy as LazyProperty
44 44
45 45 import rhodecode
46 46 from rhodecode.model import meta
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.model.db import (
50 50 false, User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
51 51 UserIpMap, UserApiKeys, RepoGroup, UserGroup, UserNotice)
52 52 from rhodecode.lib import rc_cache
53 53 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
54 54 from rhodecode.lib.utils import (
55 55 get_repo_slug, get_repo_group_slug, get_user_group_slug)
56 56 from rhodecode.lib.caching_query import FromCache
57 57
58 58 if rhodecode.is_unix:
59 59 import bcrypt
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63 csrf_token_key = "csrf_token"
64 64
65 65
66 66 class PasswordGenerator(object):
67 67 """
68 68 This is a simple class for generating password from different sets of
69 69 characters
70 70 usage::
71 71 passwd_gen = PasswordGenerator()
72 72 #print 8-letter password containing only big and small letters
73 73 of alphabet
74 74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 75 """
76 76 ALPHABETS_NUM = r'''1234567890'''
77 77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 86
87 87 def __init__(self, passwd=''):
88 88 self.passwd = passwd
89 89
90 90 def gen_password(self, length, type_=None):
91 91 if type_ is None:
92 92 type_ = self.ALPHABETS_FULL
93 93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 94 return self.passwd
95 95
96 96
97 97 class _RhodeCodeCryptoBase(object):
98 98 ENC_PREF = None
99 99
100 100 def hash_create(self, str_):
101 101 """
102 102 hash the string using
103 103
104 104 :param str_: password to hash
105 105 """
106 106 raise NotImplementedError
107 107
108 108 def hash_check_with_upgrade(self, password, hashed):
109 109 """
110 110 Returns tuple in which first element is boolean that states that
111 111 given password matches it's hashed version, and the second is new hash
112 112 of the password, in case this password should be migrated to new
113 113 cipher.
114 114 """
115 115 checked_hash = self.hash_check(password, hashed)
116 116 return checked_hash, None
117 117
118 118 def hash_check(self, password, hashed):
119 119 """
120 120 Checks matching password with it's hashed value.
121 121
122 122 :param password: password
123 123 :param hashed: password in hashed form
124 124 """
125 125 raise NotImplementedError
126 126
127 127 def _assert_bytes(self, value):
128 128 """
129 129 Passing in an `unicode` object can lead to hard to detect issues
130 130 if passwords contain non-ascii characters. Doing a type check
131 131 during runtime, so that such mistakes are detected early on.
132 132 """
133 133 if not isinstance(value, str):
134 134 raise TypeError(
135 135 "Bytestring required as input, got %r." % (value, ))
136 136
137 137
138 138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 139 ENC_PREF = ('$2a$10', '$2b$10')
140 140
141 141 def hash_create(self, str_):
142 142 self._assert_bytes(str_)
143 143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 144
145 145 def hash_check_with_upgrade(self, password, hashed):
146 146 """
147 147 Returns tuple in which first element is boolean that states that
148 148 given password matches it's hashed version, and the second is new hash
149 149 of the password, in case this password should be migrated to new
150 150 cipher.
151 151
152 152 This implements special upgrade logic which works like that:
153 153 - check if the given password == bcrypted hash, if yes then we
154 154 properly used password and it was already in bcrypt. Proceed
155 155 without any changes
156 156 - if bcrypt hash check is not working try with sha256. If hash compare
157 157 is ok, it means we using correct but old hashed password. indicate
158 158 hash change and proceed
159 159 """
160 160
161 161 new_hash = None
162 162
163 163 # regular pw check
164 164 password_match_bcrypt = self.hash_check(password, hashed)
165 165
166 166 # now we want to know if the password was maybe from sha256
167 167 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 168 if not password_match_bcrypt:
169 169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 170 new_hash = self.hash_create(password) # make new bcrypt hash
171 171 password_match_bcrypt = True
172 172
173 173 return password_match_bcrypt, new_hash
174 174
175 175 def hash_check(self, password, hashed):
176 176 """
177 177 Checks matching password with it's hashed value.
178 178
179 179 :param password: password
180 180 :param hashed: password in hashed form
181 181 """
182 182 self._assert_bytes(password)
183 183 try:
184 184 return bcrypt.hashpw(password, hashed) == hashed
185 185 except ValueError as e:
186 186 # we're having a invalid salt here probably, we should not crash
187 187 # just return with False as it would be a wrong password.
188 188 log.debug('Failed to check password hash using bcrypt %s',
189 189 safe_str(e))
190 190
191 191 return False
192 192
193 193
194 194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 195 ENC_PREF = '_'
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 _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 213 ENC_PREF = '_'
214 214
215 215 def hash_create(self, str_):
216 216 self._assert_bytes(str_)
217 217 return sha1(str_)
218 218
219 219 def hash_check(self, password, hashed):
220 220 """
221 221 Checks matching password with it's hashed value.
222 222
223 223 :param password: password
224 224 :param hashed: password in hashed form
225 225 """
226 226 self._assert_bytes(password)
227 227 return sha1(password) == hashed
228 228
229 229
230 230 def crypto_backend():
231 231 """
232 232 Return the matching crypto backend.
233 233
234 234 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 235 tests faster since BCRYPT is expensive to calculate
236 236 """
237 237 if rhodecode.is_test:
238 238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 239 else:
240 240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 241
242 242 return RhodeCodeCrypto
243 243
244 244
245 245 def get_crypt_password(password):
246 246 """
247 247 Create the hash of `password` with the active crypto backend.
248 248
249 249 :param password: The cleartext password.
250 250 :type password: unicode
251 251 """
252 252 password = safe_str(password)
253 253 return crypto_backend().hash_create(password)
254 254
255 255
256 256 def check_password(password, hashed):
257 257 """
258 258 Check if the value in `password` matches the hash in `hashed`.
259 259
260 260 :param password: The cleartext password.
261 261 :type password: unicode
262 262
263 263 :param hashed: The expected hashed version of the password.
264 264 :type hashed: The hash has to be passed in in text representation.
265 265 """
266 266 password = safe_str(password)
267 267 return crypto_backend().hash_check(password, hashed)
268 268
269 269
270 270 def generate_auth_token(data, salt=None):
271 271 """
272 272 Generates API KEY from given string
273 273 """
274 274
275 275 if salt is None:
276 276 salt = os.urandom(16)
277 277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 278
279 279
280 280 def get_came_from(request):
281 281 """
282 282 get query_string+path from request sanitized after removing auth_token
283 283 """
284 284 _req = request
285 285
286 286 path = _req.path
287 287 if 'auth_token' in _req.GET:
288 288 # sanitize the request and remove auth_token for redirection
289 289 _req.GET.pop('auth_token')
290 290 qs = _req.query_string
291 291 if qs:
292 292 path += '?' + qs
293 293
294 294 return path
295 295
296 296
297 297 class CookieStoreWrapper(object):
298 298
299 299 def __init__(self, cookie_store):
300 300 self.cookie_store = cookie_store
301 301
302 302 def __repr__(self):
303 303 return 'CookieStore<%s>' % (self.cookie_store)
304 304
305 305 def get(self, key, other=None):
306 306 if isinstance(self.cookie_store, dict):
307 307 return self.cookie_store.get(key, other)
308 308 elif isinstance(self.cookie_store, AuthUser):
309 309 return self.cookie_store.__dict__.get(key, other)
310 310
311 311
312 312 def _cached_perms_data(user_id, scope, user_is_admin,
313 313 user_inherit_default_permissions, explicit, algo,
314 314 calculate_super_admin):
315 315
316 316 permissions = PermissionCalculator(
317 317 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 318 explicit, algo, calculate_super_admin)
319 319 return permissions.calculate()
320 320
321 321
322 322 class PermOrigin(object):
323 323 SUPER_ADMIN = 'superadmin'
324 324 ARCHIVED = 'archived'
325 325
326 326 REPO_USER = 'user:%s'
327 327 REPO_USERGROUP = 'usergroup:%s'
328 328 REPO_OWNER = 'repo.owner'
329 329 REPO_DEFAULT = 'repo.default'
330 330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 331 REPO_PRIVATE = 'repo.private'
332 332
333 333 REPOGROUP_USER = 'user:%s'
334 334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 335 REPOGROUP_OWNER = 'group.owner'
336 336 REPOGROUP_DEFAULT = 'group.default'
337 337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338 338
339 339 USERGROUP_USER = 'user:%s'
340 340 USERGROUP_USERGROUP = 'usergroup:%s'
341 341 USERGROUP_OWNER = 'usergroup.owner'
342 342 USERGROUP_DEFAULT = 'usergroup.default'
343 343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344 344
345 345
346 346 class PermOriginDict(dict):
347 347 """
348 348 A special dict used for tracking permissions along with their origins.
349 349
350 350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 351 `__getitem__` will return only the perm
352 352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353 353
354 354 >>> perms = PermOriginDict()
355 355 >>> perms['resource'] = 'read', 'default', 1
356 356 >>> perms['resource']
357 357 'read'
358 358 >>> perms['resource'] = 'write', 'admin', 2
359 359 >>> perms['resource']
360 360 'write'
361 361 >>> perms.perm_origin_stack
362 362 {'resource': [('read', 'default', 1), ('write', 'admin', 2)]}
363 363 """
364 364
365 365 def __init__(self, *args, **kw):
366 366 dict.__init__(self, *args, **kw)
367 367 self.perm_origin_stack = collections.OrderedDict()
368 368
369 369 def __setitem__(self, key, (perm, origin, obj_id)):
370 self.perm_origin_stack.setdefault(key, []).append(
371 (perm, origin, obj_id))
370 self.perm_origin_stack.setdefault(key, []).append((perm, origin, obj_id))
372 371 dict.__setitem__(self, key, perm)
373 372
374 373
375 374 class BranchPermOriginDict(PermOriginDict):
376 375 """
377 376 Dedicated branch permissions dict, with tracking of patterns and origins.
378 377
379 378 >>> perms = BranchPermOriginDict()
380 379 >>> perms['resource'] = '*pattern', 'read', 'default'
381 380 >>> perms['resource']
382 381 {'*pattern': 'read'}
383 382 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 383 >>> perms['resource']
385 384 {'*pattern': 'write'}
386 385 >>> perms.perm_origin_stack
387 386 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 387 """
389 388 def __setitem__(self, key, (pattern, perm, origin)):
390 389
391 390 self.perm_origin_stack.setdefault(key, {}) \
392 391 .setdefault(pattern, []).append((perm, origin))
393 392
394 393 if key in self:
395 394 self[key].__setitem__(pattern, perm)
396 395 else:
397 396 patterns = collections.OrderedDict()
398 397 patterns[pattern] = perm
399 398 dict.__setitem__(self, key, patterns)
400 399
401 400
402 401 class PermissionCalculator(object):
403 402
404 403 def __init__(
405 404 self, user_id, scope, user_is_admin,
406 405 user_inherit_default_permissions, explicit, algo,
407 406 calculate_super_admin_as_user=False):
408 407
409 408 self.user_id = user_id
410 409 self.user_is_admin = user_is_admin
411 410 self.inherit_default_permissions = user_inherit_default_permissions
412 411 self.explicit = explicit
413 412 self.algo = algo
414 413 self.calculate_super_admin_as_user = calculate_super_admin_as_user
415 414
416 415 scope = scope or {}
417 416 self.scope_repo_id = scope.get('repo_id')
418 417 self.scope_repo_group_id = scope.get('repo_group_id')
419 418 self.scope_user_group_id = scope.get('user_group_id')
420 419
421 420 self.default_user_id = User.get_default_user(cache=True).user_id
422 421
423 422 self.permissions_repositories = PermOriginDict()
424 423 self.permissions_repository_groups = PermOriginDict()
425 424 self.permissions_user_groups = PermOriginDict()
426 425 self.permissions_repository_branches = BranchPermOriginDict()
427 426 self.permissions_global = set()
428 427
429 428 self.default_repo_perms = Permission.get_default_repo_perms(
430 429 self.default_user_id, self.scope_repo_id)
431 430 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 431 self.default_user_id, self.scope_repo_group_id)
433 432 self.default_user_group_perms = \
434 433 Permission.get_default_user_group_perms(
435 434 self.default_user_id, self.scope_user_group_id)
436 435
437 436 # default branch perms
438 437 self.default_branch_repo_perms = \
439 438 Permission.get_default_repo_branch_perms(
440 439 self.default_user_id, self.scope_repo_id)
441 440
442 441 def calculate(self):
443 442 if self.user_is_admin and not self.calculate_super_admin_as_user:
444 return self._calculate_admin_permissions()
443 return self._calculate_super_admin_permissions()
445 444
446 445 self._calculate_global_default_permissions()
447 446 self._calculate_global_permissions()
448 447 self._calculate_default_permissions()
449 448 self._calculate_repository_permissions()
450 449 self._calculate_repository_branch_permissions()
451 450 self._calculate_repository_group_permissions()
452 451 self._calculate_user_group_permissions()
453 452 return self._permission_structure()
454 453
455 def _calculate_admin_permissions(self):
454 def _calculate_super_admin_permissions(self):
456 455 """
457 admin user have all default rights for repositories
456 super-admin user have all default rights for repositories
458 457 and groups set to admin
459 458 """
460 459 self.permissions_global.add('hg.admin')
461 460 self.permissions_global.add('hg.create.write_on_repogroup.true')
462 461
463 462 # repositories
464 463 for perm in self.default_repo_perms:
465 464 r_k = perm.UserRepoToPerm.repository.repo_name
466 465 obj_id = perm.UserRepoToPerm.repository.repo_id
467 466 archived = perm.UserRepoToPerm.repository.archived
468 467 p = 'repository.admin'
469 468 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN, obj_id
470 469 # special case for archived repositories, which we block still even for
471 470 # super admins
472 471 if archived:
473 472 p = 'repository.read'
474 473 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED, obj_id
475 474
476 475 # repository groups
477 476 for perm in self.default_repo_groups_perms:
478 477 rg_k = perm.UserRepoGroupToPerm.group.group_name
479 478 obj_id = perm.UserRepoGroupToPerm.group.group_id
480 479 p = 'group.admin'
481 480 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN, obj_id
482 481
483 482 # user groups
484 483 for perm in self.default_user_group_perms:
485 484 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
486 485 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
487 486 p = 'usergroup.admin'
488 487 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN, obj_id
489 488
490 489 # branch permissions
491 490 # since super-admin also can have custom rule permissions
492 491 # we *always* need to calculate those inherited from default, and also explicit
493 492 self._calculate_default_permissions_repository_branches(
494 493 user_inherit_object_permissions=False)
495 494 self._calculate_repository_branch_permissions()
496 495
497 496 return self._permission_structure()
498 497
499 498 def _calculate_global_default_permissions(self):
500 499 """
501 500 global permissions taken from the default user
502 501 """
503 502 default_global_perms = UserToPerm.query()\
504 503 .filter(UserToPerm.user_id == self.default_user_id)\
505 504 .options(joinedload(UserToPerm.permission))
506 505
507 506 for perm in default_global_perms:
508 507 self.permissions_global.add(perm.permission.permission_name)
509 508
510 509 if self.user_is_admin:
511 510 self.permissions_global.add('hg.admin')
512 511 self.permissions_global.add('hg.create.write_on_repogroup.true')
513 512
514 513 def _calculate_global_permissions(self):
515 514 """
516 515 Set global system permissions with user permissions or permissions
517 516 taken from the user groups of the current user.
518 517
519 518 The permissions include repo creating, repo group creating, forking
520 519 etc.
521 520 """
522 521
523 522 # now we read the defined permissions and overwrite what we have set
524 523 # before those can be configured from groups or users explicitly.
525 524
526 525 # In case we want to extend this list we should make sure
527 526 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
528 527 _configurable = frozenset([
529 528 'hg.fork.none', 'hg.fork.repository',
530 529 'hg.create.none', 'hg.create.repository',
531 530 'hg.usergroup.create.false', 'hg.usergroup.create.true',
532 531 'hg.repogroup.create.false', 'hg.repogroup.create.true',
533 532 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
534 533 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
535 534 ])
536 535
537 536 # USER GROUPS comes first user group global permissions
538 537 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
539 538 .options(joinedload(UserGroupToPerm.permission))\
540 539 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
541 540 UserGroupMember.users_group_id))\
542 541 .filter(UserGroupMember.user_id == self.user_id)\
543 542 .order_by(UserGroupToPerm.users_group_id)\
544 543 .all()
545 544
546 545 # need to group here by groups since user can be in more than
547 546 # one group, so we get all groups
548 547 _explicit_grouped_perms = [
549 548 [x, list(y)] for x, y in
550 549 itertools.groupby(user_perms_from_users_groups,
551 550 lambda _x: _x.users_group)]
552 551
553 552 for gr, perms in _explicit_grouped_perms:
554 553 # since user can be in multiple groups iterate over them and
555 554 # select the lowest permissions first (more explicit)
556 555 # TODO(marcink): do this^^
557 556
558 557 # group doesn't inherit default permissions so we actually set them
559 558 if not gr.inherit_default_permissions:
560 559 # NEED TO IGNORE all previously set configurable permissions
561 560 # and replace them with explicitly set from this user
562 561 # group permissions
563 562 self.permissions_global = self.permissions_global.difference(
564 563 _configurable)
565 564 for perm in perms:
566 565 self.permissions_global.add(perm.permission.permission_name)
567 566
568 567 # user explicit global permissions
569 568 user_perms = Session().query(UserToPerm)\
570 569 .options(joinedload(UserToPerm.permission))\
571 570 .filter(UserToPerm.user_id == self.user_id).all()
572 571
573 572 if not self.inherit_default_permissions:
574 573 # NEED TO IGNORE all configurable permissions and
575 574 # replace them with explicitly set from this user permissions
576 575 self.permissions_global = self.permissions_global.difference(
577 576 _configurable)
578 577 for perm in user_perms:
579 578 self.permissions_global.add(perm.permission.permission_name)
580 579
581 580 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
582 581 for perm in self.default_repo_perms:
583 582 r_k = perm.UserRepoToPerm.repository.repo_name
584 583 obj_id = perm.UserRepoToPerm.repository.repo_id
585 584 archived = perm.UserRepoToPerm.repository.archived
586 585 p = perm.Permission.permission_name
587 586 o = PermOrigin.REPO_DEFAULT
588 587 self.permissions_repositories[r_k] = p, o, obj_id
589 588
590 589 # if we decide this user isn't inheriting permissions from
591 590 # default user we set him to .none so only explicit
592 591 # permissions work
593 592 if not user_inherit_object_permissions:
594 593 p = 'repository.none'
595 594 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
596 595 self.permissions_repositories[r_k] = p, o, obj_id
597 596
598 597 if perm.Repository.private and not (
599 598 perm.Repository.user_id == self.user_id):
600 599 # disable defaults for private repos,
601 600 p = 'repository.none'
602 601 o = PermOrigin.REPO_PRIVATE
603 602 self.permissions_repositories[r_k] = p, o, obj_id
604 603
605 604 elif perm.Repository.user_id == self.user_id:
606 605 # set admin if owner
607 606 p = 'repository.admin'
608 607 o = PermOrigin.REPO_OWNER
609 608 self.permissions_repositories[r_k] = p, o, obj_id
610 609
611 610 if self.user_is_admin:
612 611 p = 'repository.admin'
613 612 o = PermOrigin.SUPER_ADMIN
614 613 self.permissions_repositories[r_k] = p, o, obj_id
615 614
616 615 # finally in case of archived repositories, we downgrade higher
617 616 # permissions to read
618 617 if archived:
619 618 current_perm = self.permissions_repositories[r_k]
620 619 if current_perm in ['repository.write', 'repository.admin']:
621 620 p = 'repository.read'
622 621 o = PermOrigin.ARCHIVED
623 622 self.permissions_repositories[r_k] = p, o, obj_id
624 623
625 624 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
626 625 for perm in self.default_branch_repo_perms:
627 626
628 627 r_k = perm.UserRepoToPerm.repository.repo_name
629 628 p = perm.Permission.permission_name
630 629 pattern = perm.UserToRepoBranchPermission.branch_pattern
631 630 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
632 631
633 632 if not self.explicit:
634 633 cur_perm = self.permissions_repository_branches.get(r_k)
635 634 if cur_perm:
636 635 cur_perm = cur_perm[pattern]
637 636 cur_perm = cur_perm or 'branch.none'
638 637
639 638 p = self._choose_permission(p, cur_perm)
640 639
641 640 # NOTE(marcink): register all pattern/perm instances in this
642 641 # special dict that aggregates entries
643 642 self.permissions_repository_branches[r_k] = pattern, p, o
644 643
645 644 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
646 645 for perm in self.default_repo_groups_perms:
647 646 rg_k = perm.UserRepoGroupToPerm.group.group_name
648 647 obj_id = perm.UserRepoGroupToPerm.group.group_id
649 648 p = perm.Permission.permission_name
650 649 o = PermOrigin.REPOGROUP_DEFAULT
651 650 self.permissions_repository_groups[rg_k] = p, o, obj_id
652 651
653 652 # if we decide this user isn't inheriting permissions from default
654 653 # user we set him to .none so only explicit permissions work
655 654 if not user_inherit_object_permissions:
656 655 p = 'group.none'
657 656 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
658 657 self.permissions_repository_groups[rg_k] = p, o, obj_id
659 658
660 659 if perm.RepoGroup.user_id == self.user_id:
661 660 # set admin if owner
662 661 p = 'group.admin'
663 662 o = PermOrigin.REPOGROUP_OWNER
664 663 self.permissions_repository_groups[rg_k] = p, o, obj_id
665 664
666 665 if self.user_is_admin:
667 666 p = 'group.admin'
668 667 o = PermOrigin.SUPER_ADMIN
669 668 self.permissions_repository_groups[rg_k] = p, o, obj_id
670 669
671 670 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
672 671 for perm in self.default_user_group_perms:
673 672 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
674 673 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
675 674 p = perm.Permission.permission_name
676 675 o = PermOrigin.USERGROUP_DEFAULT
677 676 self.permissions_user_groups[u_k] = p, o, obj_id
678 677
679 678 # if we decide this user isn't inheriting permissions from default
680 679 # user we set him to .none so only explicit permissions work
681 680 if not user_inherit_object_permissions:
682 681 p = 'usergroup.none'
683 682 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
684 683 self.permissions_user_groups[u_k] = p, o, obj_id
685 684
686 685 if perm.UserGroup.user_id == self.user_id:
687 686 # set admin if owner
688 687 p = 'usergroup.admin'
689 688 o = PermOrigin.USERGROUP_OWNER
690 689 self.permissions_user_groups[u_k] = p, o, obj_id
691 690
692 691 if self.user_is_admin:
693 692 p = 'usergroup.admin'
694 693 o = PermOrigin.SUPER_ADMIN
695 694 self.permissions_user_groups[u_k] = p, o, obj_id
696 695
697 696 def _calculate_default_permissions(self):
698 697 """
699 698 Set default user permissions for repositories, repository branches,
700 699 repository groups, user groups taken from the default user.
701 700
702 701 Calculate inheritance of object permissions based on what we have now
703 702 in GLOBAL permissions. We check if .false is in GLOBAL since this is
704 703 explicitly set. Inherit is the opposite of .false being there.
705 704
706 705 .. note::
707 706
708 707 the syntax is little bit odd but what we need to check here is
709 708 the opposite of .false permission being in the list so even for
710 709 inconsistent state when both .true/.false is there
711 710 .false is more important
712 711
713 712 """
714 713 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
715 714 in self.permissions_global)
716 715
717 716 # default permissions inherited from `default` user permissions
718 717 self._calculate_default_permissions_repositories(
719 718 user_inherit_object_permissions)
720 719
721 720 self._calculate_default_permissions_repository_branches(
722 721 user_inherit_object_permissions)
723 722
724 723 self._calculate_default_permissions_repository_groups(
725 724 user_inherit_object_permissions)
726 725
727 726 self._calculate_default_permissions_user_groups(
728 727 user_inherit_object_permissions)
729 728
730 729 def _calculate_repository_permissions(self):
731 730 """
732 731 Repository access permissions for the current user.
733 732
734 733 Check if the user is part of user groups for this repository and
735 734 fill in the permission from it. `_choose_permission` decides of which
736 735 permission should be selected based on selected method.
737 736 """
738 737
739 738 # user group for repositories permissions
740 739 user_repo_perms_from_user_group = Permission\
741 740 .get_default_repo_perms_from_user_group(
742 741 self.user_id, self.scope_repo_id)
743 742
744 743 multiple_counter = collections.defaultdict(int)
745 744 for perm in user_repo_perms_from_user_group:
746 745 r_k = perm.UserGroupRepoToPerm.repository.repo_name
747 746 obj_id = perm.UserGroupRepoToPerm.repository.repo_id
748 747 multiple_counter[r_k] += 1
749 748 p = perm.Permission.permission_name
750 749 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
751 750 .users_group.users_group_name
752 751
753 752 if multiple_counter[r_k] > 1:
754 753 cur_perm = self.permissions_repositories[r_k]
755 754 p = self._choose_permission(p, cur_perm)
756 755
757 756 self.permissions_repositories[r_k] = p, o, obj_id
758 757
759 758 if perm.Repository.user_id == self.user_id:
760 759 # set admin if owner
761 760 p = 'repository.admin'
762 761 o = PermOrigin.REPO_OWNER
763 762 self.permissions_repositories[r_k] = p, o, obj_id
764 763
765 764 if self.user_is_admin:
766 765 p = 'repository.admin'
767 766 o = PermOrigin.SUPER_ADMIN
768 767 self.permissions_repositories[r_k] = p, o, obj_id
769 768
770 769 # user explicit permissions for repositories, overrides any specified
771 770 # by the group permission
772 771 user_repo_perms = Permission.get_default_repo_perms(
773 772 self.user_id, self.scope_repo_id)
774 773 for perm in user_repo_perms:
775 774 r_k = perm.UserRepoToPerm.repository.repo_name
776 775 obj_id = perm.UserRepoToPerm.repository.repo_id
776 archived = perm.UserRepoToPerm.repository.archived
777 777 p = perm.Permission.permission_name
778 778 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
779 779
780 780 if not self.explicit:
781 781 cur_perm = self.permissions_repositories.get(
782 782 r_k, 'repository.none')
783 783 p = self._choose_permission(p, cur_perm)
784 784
785 785 self.permissions_repositories[r_k] = p, o, obj_id
786 786
787 787 if perm.Repository.user_id == self.user_id:
788 788 # set admin if owner
789 789 p = 'repository.admin'
790 790 o = PermOrigin.REPO_OWNER
791 791 self.permissions_repositories[r_k] = p, o, obj_id
792 792
793 793 if self.user_is_admin:
794 794 p = 'repository.admin'
795 795 o = PermOrigin.SUPER_ADMIN
796 796 self.permissions_repositories[r_k] = p, o, obj_id
797 797
798 # finally in case of archived repositories, we downgrade higher
799 # permissions to read
800 if archived:
801 current_perm = self.permissions_repositories[r_k]
802 if current_perm in ['repository.write', 'repository.admin']:
803 p = 'repository.read'
804 o = PermOrigin.ARCHIVED
805 self.permissions_repositories[r_k] = p, o, obj_id
806
798 807 def _calculate_repository_branch_permissions(self):
799 808 # user group for repositories permissions
800 809 user_repo_branch_perms_from_user_group = Permission\
801 810 .get_default_repo_branch_perms_from_user_group(
802 811 self.user_id, self.scope_repo_id)
803 812
804 813 multiple_counter = collections.defaultdict(int)
805 814 for perm in user_repo_branch_perms_from_user_group:
806 815 r_k = perm.UserGroupRepoToPerm.repository.repo_name
807 816 p = perm.Permission.permission_name
808 817 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
809 818 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
810 819 .users_group.users_group_name
811 820
812 821 multiple_counter[r_k] += 1
813 822 if multiple_counter[r_k] > 1:
814 823 cur_perm = self.permissions_repository_branches[r_k][pattern]
815 824 p = self._choose_permission(p, cur_perm)
816 825
817 826 self.permissions_repository_branches[r_k] = pattern, p, o
818 827
819 828 # user explicit branch permissions for repositories, overrides
820 829 # any specified by the group permission
821 830 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
822 831 self.user_id, self.scope_repo_id)
823 832
824 833 for perm in user_repo_branch_perms:
825 834
826 835 r_k = perm.UserRepoToPerm.repository.repo_name
827 836 p = perm.Permission.permission_name
828 837 pattern = perm.UserToRepoBranchPermission.branch_pattern
829 838 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
830 839
831 840 if not self.explicit:
832 841 cur_perm = self.permissions_repository_branches.get(r_k)
833 842 if cur_perm:
834 843 cur_perm = cur_perm[pattern]
835 844 cur_perm = cur_perm or 'branch.none'
836 845 p = self._choose_permission(p, cur_perm)
837 846
838 847 # NOTE(marcink): register all pattern/perm instances in this
839 848 # special dict that aggregates entries
840 849 self.permissions_repository_branches[r_k] = pattern, p, o
841 850
842 851 def _calculate_repository_group_permissions(self):
843 852 """
844 853 Repository group permissions for the current user.
845 854
846 855 Check if the user is part of user groups for repository groups and
847 856 fill in the permissions from it. `_choose_permission` decides of which
848 857 permission should be selected based on selected method.
849 858 """
850 859 # user group for repo groups permissions
851 860 user_repo_group_perms_from_user_group = Permission\
852 861 .get_default_group_perms_from_user_group(
853 862 self.user_id, self.scope_repo_group_id)
854 863
855 864 multiple_counter = collections.defaultdict(int)
856 865 for perm in user_repo_group_perms_from_user_group:
857 866 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
858 867 obj_id = perm.UserGroupRepoGroupToPerm.group.group_id
859 868 multiple_counter[rg_k] += 1
860 869 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
861 870 .users_group.users_group_name
862 871 p = perm.Permission.permission_name
863 872
864 873 if multiple_counter[rg_k] > 1:
865 874 cur_perm = self.permissions_repository_groups[rg_k]
866 875 p = self._choose_permission(p, cur_perm)
867 876 self.permissions_repository_groups[rg_k] = p, o, obj_id
868 877
869 878 if perm.RepoGroup.user_id == self.user_id:
870 879 # set admin if owner, even for member of other user group
871 880 p = 'group.admin'
872 881 o = PermOrigin.REPOGROUP_OWNER
873 882 self.permissions_repository_groups[rg_k] = p, o, obj_id
874 883
875 884 if self.user_is_admin:
876 885 p = 'group.admin'
877 886 o = PermOrigin.SUPER_ADMIN
878 887 self.permissions_repository_groups[rg_k] = p, o, obj_id
879 888
880 889 # user explicit permissions for repository groups
881 890 user_repo_groups_perms = Permission.get_default_group_perms(
882 891 self.user_id, self.scope_repo_group_id)
883 892 for perm in user_repo_groups_perms:
884 893 rg_k = perm.UserRepoGroupToPerm.group.group_name
885 894 obj_id = perm.UserRepoGroupToPerm.group.group_id
886 895 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
887 896 .user.username
888 897 p = perm.Permission.permission_name
889 898
890 899 if not self.explicit:
891 900 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
892 901 p = self._choose_permission(p, cur_perm)
893 902
894 903 self.permissions_repository_groups[rg_k] = p, o, obj_id
895 904
896 905 if perm.RepoGroup.user_id == self.user_id:
897 906 # set admin if owner
898 907 p = 'group.admin'
899 908 o = PermOrigin.REPOGROUP_OWNER
900 909 self.permissions_repository_groups[rg_k] = p, o, obj_id
901 910
902 911 if self.user_is_admin:
903 912 p = 'group.admin'
904 913 o = PermOrigin.SUPER_ADMIN
905 914 self.permissions_repository_groups[rg_k] = p, o, obj_id
906 915
907 916 def _calculate_user_group_permissions(self):
908 917 """
909 918 User group permissions for the current user.
910 919 """
911 920 # user group for user group permissions
912 921 user_group_from_user_group = Permission\
913 922 .get_default_user_group_perms_from_user_group(
914 923 self.user_id, self.scope_user_group_id)
915 924
916 925 multiple_counter = collections.defaultdict(int)
917 926 for perm in user_group_from_user_group:
918 927 ug_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
919 928 obj_id = perm.UserGroupUserGroupToPerm.target_user_group.users_group_id
920 929 multiple_counter[ug_k] += 1
921 930 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
922 931 .user_group.users_group_name
923 932 p = perm.Permission.permission_name
924 933
925 934 if multiple_counter[ug_k] > 1:
926 935 cur_perm = self.permissions_user_groups[ug_k]
927 936 p = self._choose_permission(p, cur_perm)
928 937
929 938 self.permissions_user_groups[ug_k] = p, o, obj_id
930 939
931 940 if perm.UserGroup.user_id == self.user_id:
932 941 # set admin if owner, even for member of other user group
933 942 p = 'usergroup.admin'
934 943 o = PermOrigin.USERGROUP_OWNER
935 944 self.permissions_user_groups[ug_k] = p, o, obj_id
936 945
937 946 if self.user_is_admin:
938 947 p = 'usergroup.admin'
939 948 o = PermOrigin.SUPER_ADMIN
940 949 self.permissions_user_groups[ug_k] = p, o, obj_id
941 950
942 951 # user explicit permission for user groups
943 952 user_user_groups_perms = Permission.get_default_user_group_perms(
944 953 self.user_id, self.scope_user_group_id)
945 954 for perm in user_user_groups_perms:
946 955 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
947 956 obj_id = perm.UserUserGroupToPerm.user_group.users_group_id
948 957 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
949 958 .user.username
950 959 p = perm.Permission.permission_name
951 960
952 961 if not self.explicit:
953 962 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
954 963 p = self._choose_permission(p, cur_perm)
955 964
956 965 self.permissions_user_groups[ug_k] = p, o, obj_id
957 966
958 967 if perm.UserGroup.user_id == self.user_id:
959 968 # set admin if owner
960 969 p = 'usergroup.admin'
961 970 o = PermOrigin.USERGROUP_OWNER
962 971 self.permissions_user_groups[ug_k] = p, o, obj_id
963 972
964 973 if self.user_is_admin:
965 974 p = 'usergroup.admin'
966 975 o = PermOrigin.SUPER_ADMIN
967 976 self.permissions_user_groups[ug_k] = p, o, obj_id
968 977
969 978 def _choose_permission(self, new_perm, cur_perm):
970 979 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
971 980 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
972 981 if self.algo == 'higherwin':
973 982 if new_perm_val > cur_perm_val:
974 983 return new_perm
975 984 return cur_perm
976 985 elif self.algo == 'lowerwin':
977 986 if new_perm_val < cur_perm_val:
978 987 return new_perm
979 988 return cur_perm
980 989
981 990 def _permission_structure(self):
982 991 return {
983 992 'global': self.permissions_global,
984 993 'repositories': self.permissions_repositories,
985 994 'repository_branches': self.permissions_repository_branches,
986 995 'repositories_groups': self.permissions_repository_groups,
987 996 'user_groups': self.permissions_user_groups,
988 997 }
989 998
990 999
991 1000 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
992 1001 """
993 1002 Check if given controller_name is in whitelist of auth token access
994 1003 """
995 1004 if not whitelist:
996 1005 from rhodecode import CONFIG
997 1006 whitelist = aslist(
998 1007 CONFIG.get('api_access_controllers_whitelist'), sep=',')
999 1008 # backward compat translation
1000 1009 compat = {
1001 1010 # old controller, new VIEW
1002 1011 'ChangesetController:*': 'RepoCommitsView:*',
1003 1012 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
1004 1013 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
1005 1014 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
1006 1015 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
1007 1016 'GistsController:*': 'GistView:*',
1008 1017 }
1009 1018
1010 1019 log.debug(
1011 1020 'Allowed views for AUTH TOKEN access: %s', whitelist)
1012 1021 auth_token_access_valid = False
1013 1022
1014 1023 for entry in whitelist:
1015 1024 token_match = True
1016 1025 if entry in compat:
1017 1026 # translate from old Controllers to Pyramid Views
1018 1027 entry = compat[entry]
1019 1028
1020 1029 if '@' in entry:
1021 1030 # specific AuthToken
1022 1031 entry, allowed_token = entry.split('@', 1)
1023 1032 token_match = auth_token == allowed_token
1024 1033
1025 1034 if fnmatch.fnmatch(view_name, entry) and token_match:
1026 1035 auth_token_access_valid = True
1027 1036 break
1028 1037
1029 1038 if auth_token_access_valid:
1030 1039 log.debug('view: `%s` matches entry in whitelist: %s',
1031 1040 view_name, whitelist)
1032 1041
1033 1042 else:
1034 1043 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1035 1044 % (view_name, whitelist))
1036 1045 if auth_token:
1037 1046 # if we use auth token key and don't have access it's a warning
1038 1047 log.warning(msg)
1039 1048 else:
1040 1049 log.debug(msg)
1041 1050
1042 1051 return auth_token_access_valid
1043 1052
1044 1053
1045 1054 class AuthUser(object):
1046 1055 """
1047 1056 A simple object that handles all attributes of user in RhodeCode
1048 1057
1049 1058 It does lookup based on API key,given user, or user present in session
1050 1059 Then it fills all required information for such user. It also checks if
1051 1060 anonymous access is enabled and if so, it returns default user as logged in
1052 1061 """
1053 1062 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1054 1063 repo_read_perms = ['repository.read', 'repository.admin', 'repository.write']
1055 1064 repo_group_read_perms = ['group.read', 'group.write', 'group.admin']
1056 1065 user_group_read_perms = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
1057 1066
1058 1067 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1059 1068
1060 1069 self.user_id = user_id
1061 1070 self._api_key = api_key
1062 1071
1063 1072 self.api_key = None
1064 1073 self.username = username
1065 1074 self.ip_addr = ip_addr
1066 1075 self.name = ''
1067 1076 self.lastname = ''
1068 1077 self.first_name = ''
1069 1078 self.last_name = ''
1070 1079 self.email = ''
1071 1080 self.is_authenticated = False
1072 1081 self.admin = False
1073 1082 self.inherit_default_permissions = False
1074 1083 self.password = ''
1075 1084
1076 1085 self.anonymous_user = None # propagated on propagate_data
1077 1086 self.propagate_data()
1078 1087 self._instance = None
1079 1088 self._permissions_scoped_cache = {} # used to bind scoped calculation
1080 1089
1081 1090 @LazyProperty
1082 1091 def permissions(self):
1083 1092 return self.get_perms(user=self, cache=None)
1084 1093
1085 1094 @LazyProperty
1086 1095 def permissions_safe(self):
1087 1096 """
1088 1097 Filtered permissions excluding not allowed repositories
1089 1098 """
1090 1099 perms = self.get_perms(user=self, cache=None)
1091 1100
1092 1101 perms['repositories'] = {
1093 1102 k: v for k, v in perms['repositories'].items()
1094 1103 if v != 'repository.none'}
1095 1104 perms['repositories_groups'] = {
1096 1105 k: v for k, v in perms['repositories_groups'].items()
1097 1106 if v != 'group.none'}
1098 1107 perms['user_groups'] = {
1099 1108 k: v for k, v in perms['user_groups'].items()
1100 1109 if v != 'usergroup.none'}
1101 1110 perms['repository_branches'] = {
1102 1111 k: v for k, v in perms['repository_branches'].iteritems()
1103 1112 if v != 'branch.none'}
1104 1113 return perms
1105 1114
1106 1115 @LazyProperty
1107 1116 def permissions_full_details(self):
1108 1117 return self.get_perms(
1109 1118 user=self, cache=None, calculate_super_admin=True)
1110 1119
1111 1120 def permissions_with_scope(self, scope):
1112 1121 """
1113 1122 Call the get_perms function with scoped data. The scope in that function
1114 1123 narrows the SQL calls to the given ID of objects resulting in fetching
1115 1124 Just particular permission we want to obtain. If scope is an empty dict
1116 1125 then it basically narrows the scope to GLOBAL permissions only.
1117 1126
1118 1127 :param scope: dict
1119 1128 """
1120 1129 if 'repo_name' in scope:
1121 1130 obj = Repository.get_by_repo_name(scope['repo_name'])
1122 1131 if obj:
1123 1132 scope['repo_id'] = obj.repo_id
1124 1133 _scope = collections.OrderedDict()
1125 1134 _scope['repo_id'] = -1
1126 1135 _scope['user_group_id'] = -1
1127 1136 _scope['repo_group_id'] = -1
1128 1137
1129 1138 for k in sorted(scope.keys()):
1130 1139 _scope[k] = scope[k]
1131 1140
1132 1141 # store in cache to mimic how the @LazyProperty works,
1133 1142 # the difference here is that we use the unique key calculated
1134 1143 # from params and values
1135 1144 return self.get_perms(user=self, cache=None, scope=_scope)
1136 1145
1137 1146 def get_instance(self):
1138 1147 return User.get(self.user_id)
1139 1148
1140 1149 def propagate_data(self):
1141 1150 """
1142 1151 Fills in user data and propagates values to this instance. Maps fetched
1143 1152 user attributes to this class instance attributes
1144 1153 """
1145 1154 log.debug('AuthUser: starting data propagation for new potential user')
1146 1155 user_model = UserModel()
1147 1156 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1148 1157 is_user_loaded = False
1149 1158
1150 1159 # lookup by userid
1151 1160 if self.user_id is not None and self.user_id != anon_user.user_id:
1152 1161 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1153 1162 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1154 1163
1155 1164 # try go get user by api key
1156 1165 elif self._api_key and self._api_key != anon_user.api_key:
1157 1166 log.debug('Trying Auth User lookup by API KEY: `...%s`', self._api_key[-4:])
1158 1167 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1159 1168
1160 1169 # lookup by username
1161 1170 elif self.username:
1162 1171 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1163 1172 is_user_loaded = user_model.fill_data(self, username=self.username)
1164 1173 else:
1165 1174 log.debug('No data in %s that could been used to log in', self)
1166 1175
1167 1176 if not is_user_loaded:
1168 1177 log.debug(
1169 1178 'Failed to load user. Fallback to default user %s', anon_user)
1170 1179 # if we cannot authenticate user try anonymous
1171 1180 if anon_user.active:
1172 1181 log.debug('default user is active, using it as a session user')
1173 1182 user_model.fill_data(self, user_id=anon_user.user_id)
1174 1183 # then we set this user is logged in
1175 1184 self.is_authenticated = True
1176 1185 else:
1177 1186 log.debug('default user is NOT active')
1178 1187 # in case of disabled anonymous user we reset some of the
1179 1188 # parameters so such user is "corrupted", skipping the fill_data
1180 1189 for attr in ['user_id', 'username', 'admin', 'active']:
1181 1190 setattr(self, attr, None)
1182 1191 self.is_authenticated = False
1183 1192
1184 1193 if not self.username:
1185 1194 self.username = 'None'
1186 1195
1187 1196 log.debug('AuthUser: propagated user is now %s', self)
1188 1197
1189 1198 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1190 1199 calculate_super_admin=False, cache=None):
1191 1200 """
1192 1201 Fills user permission attribute with permissions taken from database
1193 1202 works for permissions given for repositories, and for permissions that
1194 1203 are granted to groups
1195 1204
1196 1205 :param user: instance of User object from database
1197 1206 :param explicit: In case there are permissions both for user and a group
1198 1207 that user is part of, explicit flag will defiine if user will
1199 1208 explicitly override permissions from group, if it's False it will
1200 1209 make decision based on the algo
1201 1210 :param algo: algorithm to decide what permission should be choose if
1202 1211 it's multiple defined, eg user in two different groups. It also
1203 1212 decides if explicit flag is turned off how to specify the permission
1204 1213 for case when user is in a group + have defined separate permission
1205 1214 :param calculate_super_admin: calculate permissions for super-admin in the
1206 1215 same way as for regular user without speedups
1207 1216 :param cache: Use caching for calculation, None = let the cache backend decide
1208 1217 """
1209 1218 user_id = user.user_id
1210 1219 user_is_admin = user.is_admin
1211 1220
1212 1221 # inheritance of global permissions like create repo/fork repo etc
1213 1222 user_inherit_default_permissions = user.inherit_default_permissions
1214 1223
1215 1224 cache_seconds = safe_int(
1216 1225 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1217 1226
1218 1227 if cache is None:
1219 1228 # let the backend cache decide
1220 1229 cache_on = cache_seconds > 0
1221 1230 else:
1222 1231 cache_on = cache
1223 1232
1224 1233 log.debug(
1225 1234 'Computing PERMISSION tree for user %s scope `%s` '
1226 1235 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1227 1236
1228 1237 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1229 1238 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1230 1239
1231 1240 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1232 1241 condition=cache_on)
1233 1242 def compute_perm_tree(cache_name, cache_ver,
1234 1243 user_id, scope, user_is_admin,user_inherit_default_permissions,
1235 1244 explicit, algo, calculate_super_admin):
1236 1245 return _cached_perms_data(
1237 1246 user_id, scope, user_is_admin, user_inherit_default_permissions,
1238 1247 explicit, algo, calculate_super_admin)
1239 1248
1240 1249 start = time.time()
1241 1250 result = compute_perm_tree(
1242 1251 'permissions', 'v1', user_id, scope, user_is_admin,
1243 1252 user_inherit_default_permissions, explicit, algo,
1244 1253 calculate_super_admin)
1245 1254
1246 1255 result_repr = []
1247 1256 for k in result:
1248 1257 result_repr.append((k, len(result[k])))
1249 1258 total = time.time() - start
1250 1259 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1251 1260 user, total, result_repr)
1252 1261
1253 1262 return result
1254 1263
1255 1264 @property
1256 1265 def is_default(self):
1257 1266 return self.username == User.DEFAULT_USER
1258 1267
1259 1268 @property
1260 1269 def is_admin(self):
1261 1270 return self.admin
1262 1271
1263 1272 @property
1264 1273 def is_user_object(self):
1265 1274 return self.user_id is not None
1266 1275
1267 1276 @property
1268 1277 def repositories_admin(self):
1269 1278 """
1270 1279 Returns list of repositories you're an admin of
1271 1280 """
1272 1281 return [
1273 1282 x[0] for x in self.permissions['repositories'].items()
1274 1283 if x[1] == 'repository.admin']
1275 1284
1276 1285 @property
1277 1286 def repository_groups_admin(self):
1278 1287 """
1279 1288 Returns list of repository groups you're an admin of
1280 1289 """
1281 1290 return [
1282 1291 x[0] for x in self.permissions['repositories_groups'].items()
1283 1292 if x[1] == 'group.admin']
1284 1293
1285 1294 @property
1286 1295 def user_groups_admin(self):
1287 1296 """
1288 1297 Returns list of user groups you're an admin of
1289 1298 """
1290 1299 return [
1291 1300 x[0] for x in self.permissions['user_groups'].items()
1292 1301 if x[1] == 'usergroup.admin']
1293 1302
1294 1303 def repo_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1295 1304 if not perms:
1296 1305 perms = AuthUser.repo_read_perms
1297 1306 allowed_ids = []
1298 1307 for k, stack_data in self.permissions['repositories'].perm_origin_stack.items():
1299 1308 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1300 1309 if prefix_filter and not k.startswith(prefix_filter):
1301 1310 continue
1302 1311 if perm in perms:
1303 1312 allowed_ids.append(obj_id)
1304 1313 return allowed_ids
1305 1314
1306 1315 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1307 1316 """
1308 1317 Returns list of repository ids that user have access to based on given
1309 1318 perms. The cache flag should be only used in cases that are used for
1310 1319 display purposes, NOT IN ANY CASE for permission checks.
1311 1320 """
1312 1321 from rhodecode.model.scm import RepoList
1313 1322 if not perms:
1314 1323 perms = AuthUser.repo_read_perms
1315 1324
1316 1325 if not isinstance(perms, list):
1317 1326 raise ValueError('perms parameter must be a list got {} instead'.format(perms))
1318 1327
1319 1328 def _cached_repo_acl(perm_def, _name_filter):
1320 1329 qry = Repository.query()
1321 1330 if _name_filter:
1322 1331 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1323 1332 qry = qry.filter(
1324 1333 Repository.repo_name.ilike(ilike_expression))
1325 1334
1326 1335 return [x.repo_id for x in
1327 1336 RepoList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1328 1337
1329 1338 log.debug('Computing REPO ACL IDS user %s', self)
1330 1339
1331 1340 cache_namespace_uid = 'cache_user_repo_acl_ids.{}'.format(self.user_id)
1332 1341 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1333 1342
1334 1343 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache)
1335 1344 def compute_repo_acl_ids(cache_ver, user_id, perm_def, _name_filter):
1336 1345 return _cached_repo_acl(perm_def, _name_filter)
1337 1346
1338 1347 start = time.time()
1339 1348 result = compute_repo_acl_ids('v1', self.user_id, perms, name_filter)
1340 1349 total = time.time() - start
1341 1350 log.debug('REPO ACL IDS for user %s computed in %.4fs', self, total)
1342 1351
1343 1352 return result
1344 1353
1345 1354 def repo_group_acl_ids_from_stack(self, perms=None, prefix_filter=None, cache=False):
1346 1355 if not perms:
1347 1356 perms = AuthUser.repo_group_read_perms
1348 1357 allowed_ids = []
1349 1358 for k, stack_data in self.permissions['repositories_groups'].perm_origin_stack.items():
1350 1359 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1351 1360 if prefix_filter and not k.startswith(prefix_filter):
1352 1361 continue
1353 1362 if perm in perms:
1354 1363 allowed_ids.append(obj_id)
1355 1364 return allowed_ids
1356 1365
1357 1366 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1358 1367 """
1359 1368 Returns list of repository group ids that user have access to based on given
1360 1369 perms. The cache flag should be only used in cases that are used for
1361 1370 display purposes, NOT IN ANY CASE for permission checks.
1362 1371 """
1363 1372 from rhodecode.model.scm import RepoGroupList
1364 1373 if not perms:
1365 1374 perms = AuthUser.repo_group_read_perms
1366 1375
1367 1376 if not isinstance(perms, list):
1368 1377 raise ValueError('perms parameter must be a list got {} instead'.format(perms))
1369 1378
1370 1379 def _cached_repo_group_acl(perm_def, _name_filter):
1371 1380 qry = RepoGroup.query()
1372 1381 if _name_filter:
1373 1382 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1374 1383 qry = qry.filter(
1375 1384 RepoGroup.group_name.ilike(ilike_expression))
1376 1385
1377 1386 return [x.group_id for x in
1378 1387 RepoGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1379 1388
1380 1389 log.debug('Computing REPO GROUP ACL IDS user %s', self)
1381 1390
1382 1391 cache_namespace_uid = 'cache_user_repo_group_acl_ids.{}'.format(self.user_id)
1383 1392 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1384 1393
1385 1394 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache)
1386 1395 def compute_repo_group_acl_ids(cache_ver, user_id, perm_def, _name_filter):
1387 1396 return _cached_repo_group_acl(perm_def, _name_filter)
1388 1397
1389 1398 start = time.time()
1390 1399 result = compute_repo_group_acl_ids('v1', self.user_id, perms, name_filter)
1391 1400 total = time.time() - start
1392 1401 log.debug('REPO GROUP ACL IDS for user %s computed in %.4fs', self, total)
1393 1402
1394 1403 return result
1395 1404
1396 1405 def user_group_acl_ids_from_stack(self, perms=None, cache=False):
1397 1406 if not perms:
1398 1407 perms = AuthUser.user_group_read_perms
1399 1408 allowed_ids = []
1400 1409 for k, stack_data in self.permissions['user_groups'].perm_origin_stack.items():
1401 1410 perm, origin, obj_id = stack_data[-1] # last item is the current permission
1402 1411 if perm in perms:
1403 1412 allowed_ids.append(obj_id)
1404 1413 return allowed_ids
1405 1414
1406 1415 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1407 1416 """
1408 1417 Returns list of user group ids that user have access to based on given
1409 1418 perms. The cache flag should be only used in cases that are used for
1410 1419 display purposes, NOT IN ANY CASE for permission checks.
1411 1420 """
1412 1421 from rhodecode.model.scm import UserGroupList
1413 1422 if not perms:
1414 1423 perms = AuthUser.user_group_read_perms
1415 1424
1416 1425 if not isinstance(perms, list):
1417 1426 raise ValueError('perms parameter must be a list got {} instead'.format(perms))
1418 1427
1419 1428 def _cached_user_group_acl(perm_def, _name_filter):
1420 1429 qry = UserGroup.query()
1421 1430 if _name_filter:
1422 1431 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1423 1432 qry = qry.filter(
1424 1433 UserGroup.users_group_name.ilike(ilike_expression))
1425 1434
1426 1435 return [x.users_group_id for x in
1427 1436 UserGroupList(qry, perm_set=perm_def, extra_kwargs={'user': self})]
1428 1437
1429 1438 log.debug('Computing USER GROUP ACL IDS user %s', self)
1430 1439
1431 1440 cache_namespace_uid = 'cache_user_user_group_acl_ids.{}'.format(self.user_id)
1432 1441 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1433 1442
1434 1443 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache)
1435 1444 def compute_user_group_acl_ids(cache_ver, user_id, perm_def, _name_filter):
1436 1445 return _cached_user_group_acl(perm_def, _name_filter)
1437 1446
1438 1447 start = time.time()
1439 1448 result = compute_user_group_acl_ids('v1', self.user_id, perms, name_filter)
1440 1449 total = time.time() - start
1441 1450 log.debug('USER GROUP ACL IDS for user %s computed in %.4fs', self, total)
1442 1451
1443 1452 return result
1444 1453
1445 1454 @property
1446 1455 def ip_allowed(self):
1447 1456 """
1448 1457 Checks if ip_addr used in constructor is allowed from defined list of
1449 1458 allowed ip_addresses for user
1450 1459
1451 1460 :returns: boolean, True if ip is in allowed ip range
1452 1461 """
1453 1462 # check IP
1454 1463 inherit = self.inherit_default_permissions
1455 1464 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1456 1465 inherit_from_default=inherit)
1457 1466
1458 1467 @property
1459 1468 def personal_repo_group(self):
1460 1469 return RepoGroup.get_user_personal_repo_group(self.user_id)
1461 1470
1462 1471 @LazyProperty
1463 1472 def feed_token(self):
1464 1473 return self.get_instance().feed_token
1465 1474
1466 1475 @LazyProperty
1467 1476 def artifact_token(self):
1468 1477 return self.get_instance().artifact_token
1469 1478
1470 1479 @classmethod
1471 1480 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1472 1481 allowed_ips = AuthUser.get_allowed_ips(
1473 1482 user_id, cache=True, inherit_from_default=inherit_from_default)
1474 1483 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1475 1484 log.debug('IP:%s for user %s is in range of %s',
1476 1485 ip_addr, user_id, allowed_ips)
1477 1486 return True
1478 1487 else:
1479 1488 log.info('Access for IP:%s forbidden for user %s, '
1480 1489 'not in %s', ip_addr, user_id, allowed_ips)
1481 1490 return False
1482 1491
1483 1492 def get_branch_permissions(self, repo_name, perms=None):
1484 1493 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1485 1494 branch_perms = perms.get('repository_branches', {})
1486 1495 if not branch_perms:
1487 1496 return {}
1488 1497 repo_branch_perms = branch_perms.get(repo_name)
1489 1498 return repo_branch_perms or {}
1490 1499
1491 1500 def get_rule_and_branch_permission(self, repo_name, branch_name):
1492 1501 """
1493 1502 Check if this AuthUser has defined any permissions for branches. If any of
1494 1503 the rules match in order, we return the matching permissions
1495 1504 """
1496 1505
1497 1506 rule = default_perm = ''
1498 1507
1499 1508 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1500 1509 if not repo_branch_perms:
1501 1510 return rule, default_perm
1502 1511
1503 1512 # now calculate the permissions
1504 1513 for pattern, branch_perm in repo_branch_perms.items():
1505 1514 if fnmatch.fnmatch(branch_name, pattern):
1506 1515 rule = '`{}`=>{}'.format(pattern, branch_perm)
1507 1516 return rule, branch_perm
1508 1517
1509 1518 return rule, default_perm
1510 1519
1511 1520 def get_notice_messages(self):
1512 1521
1513 1522 notice_level = 'notice-error'
1514 1523 notice_messages = []
1515 1524 if self.is_default:
1516 1525 return [], notice_level
1517 1526
1518 1527 notices = UserNotice.query()\
1519 1528 .filter(UserNotice.user_id == self.user_id)\
1520 1529 .filter(UserNotice.notice_read == false())\
1521 1530 .all()
1522 1531
1523 1532 try:
1524 1533 for entry in notices:
1525 1534
1526 1535 msg = {
1527 1536 'msg_id': entry.user_notice_id,
1528 1537 'level': entry.notification_level,
1529 1538 'subject': entry.notice_subject,
1530 1539 'body': entry.notice_body,
1531 1540 }
1532 1541 notice_messages.append(msg)
1533 1542
1534 1543 log.debug('Got user %s %s messages', self, len(notice_messages))
1535 1544
1536 1545 levels = [x['level'] for x in notice_messages]
1537 1546 notice_level = 'notice-error' if 'error' in levels else 'notice-warning'
1538 1547 except Exception:
1539 1548 pass
1540 1549
1541 1550 return notice_messages, notice_level
1542 1551
1543 1552 def __repr__(self):
1544 1553 return self.repr_user(self.user_id, self.username, self.ip_addr, self.is_authenticated)
1545 1554
1546 1555 def set_authenticated(self, authenticated=True):
1547 1556 if self.user_id != self.anonymous_user.user_id:
1548 1557 self.is_authenticated = authenticated
1549 1558
1550 1559 def get_cookie_store(self):
1551 1560 return {
1552 1561 'username': self.username,
1553 1562 'password': md5(self.password or ''),
1554 1563 'user_id': self.user_id,
1555 1564 'is_authenticated': self.is_authenticated
1556 1565 }
1557 1566
1558 1567 @classmethod
1559 1568 def repr_user(cls, user_id=0, username='ANONYMOUS', ip='0.0.0.0', is_authenticated=False):
1560 1569 tmpl = "<AuthUser('id:{}[{}] ip:{} auth:{}')>"
1561 1570 return tmpl.format(user_id, username, ip, is_authenticated)
1562 1571
1563 1572 @classmethod
1564 1573 def from_cookie_store(cls, cookie_store):
1565 1574 """
1566 1575 Creates AuthUser from a cookie store
1567 1576
1568 1577 :param cls:
1569 1578 :param cookie_store:
1570 1579 """
1571 1580 user_id = cookie_store.get('user_id')
1572 1581 username = cookie_store.get('username')
1573 1582 api_key = cookie_store.get('api_key')
1574 1583 return AuthUser(user_id, api_key, username)
1575 1584
1576 1585 @classmethod
1577 1586 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1578 1587 _set = set()
1579 1588
1580 1589 if inherit_from_default:
1581 1590 def_user_id = User.get_default_user(cache=True).user_id
1582 1591 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1583 1592 if cache:
1584 1593 default_ips = default_ips.options(
1585 1594 FromCache("sql_cache_short", "get_user_ips_default"))
1586 1595
1587 1596 # populate from default user
1588 1597 for ip in default_ips:
1589 1598 try:
1590 1599 _set.add(ip.ip_addr)
1591 1600 except ObjectDeletedError:
1592 1601 # since we use heavy caching sometimes it happens that
1593 1602 # we get deleted objects here, we just skip them
1594 1603 pass
1595 1604
1596 1605 # NOTE:(marcink) we don't want to load any rules for empty
1597 1606 # user_id which is the case of access of non logged users when anonymous
1598 1607 # access is disabled
1599 1608 user_ips = []
1600 1609 if user_id:
1601 1610 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1602 1611 if cache:
1603 1612 user_ips = user_ips.options(
1604 1613 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1605 1614
1606 1615 for ip in user_ips:
1607 1616 try:
1608 1617 _set.add(ip.ip_addr)
1609 1618 except ObjectDeletedError:
1610 1619 # since we use heavy caching sometimes it happens that we get
1611 1620 # deleted objects here, we just skip them
1612 1621 pass
1613 1622 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1614 1623
1615 1624
1616 1625 def set_available_permissions(settings):
1617 1626 """
1618 1627 This function will propagate pyramid settings with all available defined
1619 1628 permission given in db. We don't want to check each time from db for new
1620 1629 permissions since adding a new permission also requires application restart
1621 1630 ie. to decorate new views with the newly created permission
1622 1631
1623 1632 :param settings: current pyramid registry.settings
1624 1633
1625 1634 """
1626 1635 log.debug('auth: getting information about all available permissions')
1627 1636 try:
1628 1637 sa = meta.Session
1629 1638 all_perms = sa.query(Permission).all()
1630 1639 settings.setdefault('available_permissions',
1631 1640 [x.permission_name for x in all_perms])
1632 1641 log.debug('auth: set available permissions')
1633 1642 except Exception:
1634 1643 log.exception('Failed to fetch permissions from the database.')
1635 1644 raise
1636 1645
1637 1646
1638 1647 def get_csrf_token(session, force_new=False, save_if_missing=True):
1639 1648 """
1640 1649 Return the current authentication token, creating one if one doesn't
1641 1650 already exist and the save_if_missing flag is present.
1642 1651
1643 1652 :param session: pass in the pyramid session, else we use the global ones
1644 1653 :param force_new: force to re-generate the token and store it in session
1645 1654 :param save_if_missing: save the newly generated token if it's missing in
1646 1655 session
1647 1656 """
1648 1657 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1649 1658 # from pyramid.csrf import get_csrf_token
1650 1659
1651 1660 if (csrf_token_key not in session and save_if_missing) or force_new:
1652 1661 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1653 1662 session[csrf_token_key] = token
1654 1663 if hasattr(session, 'save'):
1655 1664 session.save()
1656 1665 return session.get(csrf_token_key)
1657 1666
1658 1667
1659 1668 def get_request(perm_class_instance):
1660 1669 from pyramid.threadlocal import get_current_request
1661 1670 pyramid_request = get_current_request()
1662 1671 return pyramid_request
1663 1672
1664 1673
1665 1674 # CHECK DECORATORS
1666 1675 class CSRFRequired(object):
1667 1676 """
1668 1677 Decorator for authenticating a form
1669 1678
1670 1679 This decorator uses an authorization token stored in the client's
1671 1680 session for prevention of certain Cross-site request forgery (CSRF)
1672 1681 attacks (See
1673 1682 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1674 1683 information).
1675 1684
1676 1685 For use with the ``secure_form`` helper functions.
1677 1686
1678 1687 """
1679 1688 def __init__(self, token=csrf_token_key, header='X-CSRF-Token', except_methods=None):
1680 1689 self.token = token
1681 1690 self.header = header
1682 1691 self.except_methods = except_methods or []
1683 1692
1684 1693 def __call__(self, func):
1685 1694 return get_cython_compat_decorator(self.__wrapper, func)
1686 1695
1687 1696 def _get_csrf(self, _request):
1688 1697 return _request.POST.get(self.token, _request.headers.get(self.header))
1689 1698
1690 1699 def check_csrf(self, _request, cur_token):
1691 1700 supplied_token = self._get_csrf(_request)
1692 1701 return supplied_token and supplied_token == cur_token
1693 1702
1694 1703 def _get_request(self):
1695 1704 return get_request(self)
1696 1705
1697 1706 def __wrapper(self, func, *fargs, **fkwargs):
1698 1707 request = self._get_request()
1699 1708
1700 1709 if request.method in self.except_methods:
1701 1710 return func(*fargs, **fkwargs)
1702 1711
1703 1712 cur_token = get_csrf_token(request.session, save_if_missing=False)
1704 1713 if self.check_csrf(request, cur_token):
1705 1714 if request.POST.get(self.token):
1706 1715 del request.POST[self.token]
1707 1716 return func(*fargs, **fkwargs)
1708 1717 else:
1709 1718 reason = 'token-missing'
1710 1719 supplied_token = self._get_csrf(request)
1711 1720 if supplied_token and cur_token != supplied_token:
1712 1721 reason = 'token-mismatch [%s:%s]' % (
1713 1722 cur_token or ''[:6], supplied_token or ''[:6])
1714 1723
1715 1724 csrf_message = \
1716 1725 ("Cross-site request forgery detected, request denied. See "
1717 1726 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1718 1727 "more information.")
1719 1728 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1720 1729 'REMOTE_ADDR:%s, HEADERS:%s' % (
1721 1730 request, reason, request.remote_addr, request.headers))
1722 1731
1723 1732 raise HTTPForbidden(explanation=csrf_message)
1724 1733
1725 1734
1726 1735 class LoginRequired(object):
1727 1736 """
1728 1737 Must be logged in to execute this function else
1729 1738 redirect to login page
1730 1739
1731 1740 :param api_access: if enabled this checks only for valid auth token
1732 1741 and grants access based on valid token
1733 1742 """
1734 1743 def __init__(self, auth_token_access=None):
1735 1744 self.auth_token_access = auth_token_access
1736 1745 if self.auth_token_access:
1737 1746 valid_type = set(auth_token_access).intersection(set(UserApiKeys.ROLES))
1738 1747 if not valid_type:
1739 1748 raise ValueError('auth_token_access must be on of {}, got {}'.format(
1740 1749 UserApiKeys.ROLES, auth_token_access))
1741 1750
1742 1751 def __call__(self, func):
1743 1752 return get_cython_compat_decorator(self.__wrapper, func)
1744 1753
1745 1754 def _get_request(self):
1746 1755 return get_request(self)
1747 1756
1748 1757 def __wrapper(self, func, *fargs, **fkwargs):
1749 1758 from rhodecode.lib import helpers as h
1750 1759 cls = fargs[0]
1751 1760 user = cls._rhodecode_user
1752 1761 request = self._get_request()
1753 1762 _ = request.translate
1754 1763
1755 1764 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1756 1765 log.debug('Starting login restriction checks for user: %s', user)
1757 1766 # check if our IP is allowed
1758 1767 ip_access_valid = True
1759 1768 if not user.ip_allowed:
1760 1769 h.flash(h.literal(_('IP {} not allowed'.format(user.ip_addr))),
1761 1770 category='warning')
1762 1771 ip_access_valid = False
1763 1772
1764 1773 # we used stored token that is extract from GET or URL param (if any)
1765 1774 _auth_token = request.user_auth_token
1766 1775
1767 1776 # check if we used an AUTH_TOKEN and it's a valid one
1768 1777 # defined white-list of controllers which API access will be enabled
1769 1778 whitelist = None
1770 1779 if self.auth_token_access:
1771 1780 # since this location is allowed by @LoginRequired decorator it's our
1772 1781 # only whitelist
1773 1782 whitelist = [loc]
1774 1783 auth_token_access_valid = allowed_auth_token_access(
1775 1784 loc, whitelist=whitelist, auth_token=_auth_token)
1776 1785
1777 1786 # explicit controller is enabled or API is in our whitelist
1778 1787 if auth_token_access_valid:
1779 1788 log.debug('Checking AUTH TOKEN access for %s', cls)
1780 1789 db_user = user.get_instance()
1781 1790
1782 1791 if db_user:
1783 1792 if self.auth_token_access:
1784 1793 roles = self.auth_token_access
1785 1794 else:
1786 1795 roles = [UserApiKeys.ROLE_HTTP]
1787 1796 log.debug('AUTH TOKEN: checking auth for user %s and roles %s',
1788 1797 db_user, roles)
1789 1798 token_match = db_user.authenticate_by_token(
1790 1799 _auth_token, roles=roles)
1791 1800 else:
1792 1801 log.debug('Unable to fetch db instance for auth user: %s', user)
1793 1802 token_match = False
1794 1803
1795 1804 if _auth_token and token_match:
1796 1805 auth_token_access_valid = True
1797 1806 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1798 1807 else:
1799 1808 auth_token_access_valid = False
1800 1809 if not _auth_token:
1801 1810 log.debug("AUTH TOKEN *NOT* present in request")
1802 1811 else:
1803 1812 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1804 1813
1805 1814 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1806 1815 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1807 1816 else 'AUTH_TOKEN_AUTH'
1808 1817
1809 1818 if ip_access_valid and (
1810 1819 user.is_authenticated or auth_token_access_valid):
1811 1820 log.info('user %s authenticating with:%s IS authenticated on func %s',
1812 1821 user, reason, loc)
1813 1822
1814 1823 return func(*fargs, **fkwargs)
1815 1824 else:
1816 1825 log.warning(
1817 1826 'user %s authenticating with:%s NOT authenticated on '
1818 1827 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1819 1828 user, reason, loc, ip_access_valid, auth_token_access_valid)
1820 1829 # we preserve the get PARAM
1821 1830 came_from = get_came_from(request)
1822 1831
1823 1832 log.debug('redirecting to login page with %s', came_from)
1824 1833 raise HTTPFound(
1825 1834 h.route_path('login', _query={'came_from': came_from}))
1826 1835
1827 1836
1828 1837 class NotAnonymous(object):
1829 1838 """
1830 1839 Must be logged in to execute this function else
1831 1840 redirect to login page
1832 1841 """
1833 1842
1834 1843 def __call__(self, func):
1835 1844 return get_cython_compat_decorator(self.__wrapper, func)
1836 1845
1837 1846 def _get_request(self):
1838 1847 return get_request(self)
1839 1848
1840 1849 def __wrapper(self, func, *fargs, **fkwargs):
1841 1850 import rhodecode.lib.helpers as h
1842 1851 cls = fargs[0]
1843 1852 self.user = cls._rhodecode_user
1844 1853 request = self._get_request()
1845 1854 _ = request.translate
1846 1855 log.debug('Checking if user is not anonymous @%s', cls)
1847 1856
1848 1857 anonymous = self.user.username == User.DEFAULT_USER
1849 1858
1850 1859 if anonymous:
1851 1860 came_from = get_came_from(request)
1852 1861 h.flash(_('You need to be a registered user to '
1853 1862 'perform this action'),
1854 1863 category='warning')
1855 1864 raise HTTPFound(
1856 1865 h.route_path('login', _query={'came_from': came_from}))
1857 1866 else:
1858 1867 return func(*fargs, **fkwargs)
1859 1868
1860 1869
1861 1870 class PermsDecorator(object):
1862 1871 """
1863 1872 Base class for controller decorators, we extract the current user from
1864 1873 the class itself, which has it stored in base controllers
1865 1874 """
1866 1875
1867 1876 def __init__(self, *required_perms):
1868 1877 self.required_perms = set(required_perms)
1869 1878
1870 1879 def __call__(self, func):
1871 1880 return get_cython_compat_decorator(self.__wrapper, func)
1872 1881
1873 1882 def _get_request(self):
1874 1883 return get_request(self)
1875 1884
1876 1885 def __wrapper(self, func, *fargs, **fkwargs):
1877 1886 import rhodecode.lib.helpers as h
1878 1887 cls = fargs[0]
1879 1888 _user = cls._rhodecode_user
1880 1889 request = self._get_request()
1881 1890 _ = request.translate
1882 1891
1883 1892 log.debug('checking %s permissions %s for %s %s',
1884 1893 self.__class__.__name__, self.required_perms, cls, _user)
1885 1894
1886 1895 if self.check_permissions(_user):
1887 1896 log.debug('Permission granted for %s %s', cls, _user)
1888 1897 return func(*fargs, **fkwargs)
1889 1898
1890 1899 else:
1891 1900 log.debug('Permission denied for %s %s', cls, _user)
1892 1901 anonymous = _user.username == User.DEFAULT_USER
1893 1902
1894 1903 if anonymous:
1895 1904 came_from = get_came_from(self._get_request())
1896 1905 h.flash(_('You need to be signed in to view this page'),
1897 1906 category='warning')
1898 1907 raise HTTPFound(
1899 1908 h.route_path('login', _query={'came_from': came_from}))
1900 1909
1901 1910 else:
1902 1911 # redirect with 404 to prevent resource discovery
1903 1912 raise HTTPNotFound()
1904 1913
1905 1914 def check_permissions(self, user):
1906 1915 """Dummy function for overriding"""
1907 1916 raise NotImplementedError(
1908 1917 'You have to write this function in child class')
1909 1918
1910 1919
1911 1920 class HasPermissionAllDecorator(PermsDecorator):
1912 1921 """
1913 1922 Checks for access permission for all given predicates. All of them
1914 1923 have to be meet in order to fulfill the request
1915 1924 """
1916 1925
1917 1926 def check_permissions(self, user):
1918 1927 perms = user.permissions_with_scope({})
1919 1928 if self.required_perms.issubset(perms['global']):
1920 1929 return True
1921 1930 return False
1922 1931
1923 1932
1924 1933 class HasPermissionAnyDecorator(PermsDecorator):
1925 1934 """
1926 1935 Checks for access permission for any of given predicates. In order to
1927 1936 fulfill the request any of predicates must be meet
1928 1937 """
1929 1938
1930 1939 def check_permissions(self, user):
1931 1940 perms = user.permissions_with_scope({})
1932 1941 if self.required_perms.intersection(perms['global']):
1933 1942 return True
1934 1943 return False
1935 1944
1936 1945
1937 1946 class HasRepoPermissionAllDecorator(PermsDecorator):
1938 1947 """
1939 1948 Checks for access permission for all given predicates for specific
1940 1949 repository. All of them have to be meet in order to fulfill the request
1941 1950 """
1942 1951 def _get_repo_name(self):
1943 1952 _request = self._get_request()
1944 1953 return get_repo_slug(_request)
1945 1954
1946 1955 def check_permissions(self, user):
1947 1956 perms = user.permissions
1948 1957 repo_name = self._get_repo_name()
1949 1958
1950 1959 try:
1951 1960 user_perms = {perms['repositories'][repo_name]}
1952 1961 except KeyError:
1953 1962 log.debug('cannot locate repo with name: `%s` in permissions defs',
1954 1963 repo_name)
1955 1964 return False
1956 1965
1957 1966 log.debug('checking `%s` permissions for repo `%s`',
1958 1967 user_perms, repo_name)
1959 1968 if self.required_perms.issubset(user_perms):
1960 1969 return True
1961 1970 return False
1962 1971
1963 1972
1964 1973 class HasRepoPermissionAnyDecorator(PermsDecorator):
1965 1974 """
1966 1975 Checks for access permission for any of given predicates for specific
1967 1976 repository. In order to fulfill the request any of predicates must be meet
1968 1977 """
1969 1978 def _get_repo_name(self):
1970 1979 _request = self._get_request()
1971 1980 return get_repo_slug(_request)
1972 1981
1973 1982 def check_permissions(self, user):
1974 1983 perms = user.permissions
1975 1984 repo_name = self._get_repo_name()
1976 1985
1977 1986 try:
1978 1987 user_perms = {perms['repositories'][repo_name]}
1979 1988 except KeyError:
1980 1989 log.debug(
1981 1990 'cannot locate repo with name: `%s` in permissions defs',
1982 1991 repo_name)
1983 1992 return False
1984 1993
1985 1994 log.debug('checking `%s` permissions for repo `%s`',
1986 1995 user_perms, repo_name)
1987 1996 if self.required_perms.intersection(user_perms):
1988 1997 return True
1989 1998 return False
1990 1999
1991 2000
1992 2001 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1993 2002 """
1994 2003 Checks for access permission for all given predicates for specific
1995 2004 repository group. All of them have to be meet in order to
1996 2005 fulfill the request
1997 2006 """
1998 2007 def _get_repo_group_name(self):
1999 2008 _request = self._get_request()
2000 2009 return get_repo_group_slug(_request)
2001 2010
2002 2011 def check_permissions(self, user):
2003 2012 perms = user.permissions
2004 2013 group_name = self._get_repo_group_name()
2005 2014 try:
2006 2015 user_perms = {perms['repositories_groups'][group_name]}
2007 2016 except KeyError:
2008 2017 log.debug(
2009 2018 'cannot locate repo group with name: `%s` in permissions defs',
2010 2019 group_name)
2011 2020 return False
2012 2021
2013 2022 log.debug('checking `%s` permissions for repo group `%s`',
2014 2023 user_perms, group_name)
2015 2024 if self.required_perms.issubset(user_perms):
2016 2025 return True
2017 2026 return False
2018 2027
2019 2028
2020 2029 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
2021 2030 """
2022 2031 Checks for access permission for any of given predicates for specific
2023 2032 repository group. In order to fulfill the request any
2024 2033 of predicates must be met
2025 2034 """
2026 2035 def _get_repo_group_name(self):
2027 2036 _request = self._get_request()
2028 2037 return get_repo_group_slug(_request)
2029 2038
2030 2039 def check_permissions(self, user):
2031 2040 perms = user.permissions
2032 2041 group_name = self._get_repo_group_name()
2033 2042
2034 2043 try:
2035 2044 user_perms = {perms['repositories_groups'][group_name]}
2036 2045 except KeyError:
2037 2046 log.debug(
2038 2047 'cannot locate repo group with name: `%s` in permissions defs',
2039 2048 group_name)
2040 2049 return False
2041 2050
2042 2051 log.debug('checking `%s` permissions for repo group `%s`',
2043 2052 user_perms, group_name)
2044 2053 if self.required_perms.intersection(user_perms):
2045 2054 return True
2046 2055 return False
2047 2056
2048 2057
2049 2058 class HasUserGroupPermissionAllDecorator(PermsDecorator):
2050 2059 """
2051 2060 Checks for access permission for all given predicates for specific
2052 2061 user group. All of them have to be meet in order to fulfill the request
2053 2062 """
2054 2063 def _get_user_group_name(self):
2055 2064 _request = self._get_request()
2056 2065 return get_user_group_slug(_request)
2057 2066
2058 2067 def check_permissions(self, user):
2059 2068 perms = user.permissions
2060 2069 group_name = self._get_user_group_name()
2061 2070 try:
2062 2071 user_perms = {perms['user_groups'][group_name]}
2063 2072 except KeyError:
2064 2073 return False
2065 2074
2066 2075 if self.required_perms.issubset(user_perms):
2067 2076 return True
2068 2077 return False
2069 2078
2070 2079
2071 2080 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
2072 2081 """
2073 2082 Checks for access permission for any of given predicates for specific
2074 2083 user group. In order to fulfill the request any of predicates must be meet
2075 2084 """
2076 2085 def _get_user_group_name(self):
2077 2086 _request = self._get_request()
2078 2087 return get_user_group_slug(_request)
2079 2088
2080 2089 def check_permissions(self, user):
2081 2090 perms = user.permissions
2082 2091 group_name = self._get_user_group_name()
2083 2092 try:
2084 2093 user_perms = {perms['user_groups'][group_name]}
2085 2094 except KeyError:
2086 2095 return False
2087 2096
2088 2097 if self.required_perms.intersection(user_perms):
2089 2098 return True
2090 2099 return False
2091 2100
2092 2101
2093 2102 # CHECK FUNCTIONS
2094 2103 class PermsFunction(object):
2095 2104 """Base function for other check functions"""
2096 2105
2097 2106 def __init__(self, *perms):
2098 2107 self.required_perms = set(perms)
2099 2108 self.repo_name = None
2100 2109 self.repo_group_name = None
2101 2110 self.user_group_name = None
2102 2111
2103 2112 def __bool__(self):
2104 2113 import inspect
2105 2114 frame = inspect.currentframe()
2106 2115 stack_trace = traceback.format_stack(frame)
2107 2116 log.error('Checking bool value on a class instance of perm '
2108 2117 'function is not allowed: %s', ''.join(stack_trace))
2109 2118 # rather than throwing errors, here we always return False so if by
2110 2119 # accident someone checks truth for just an instance it will always end
2111 2120 # up in returning False
2112 2121 return False
2113 2122 __nonzero__ = __bool__
2114 2123
2115 2124 def __call__(self, check_location='', user=None):
2116 2125 if not user:
2117 2126 log.debug('Using user attribute from global request')
2118 2127 request = self._get_request()
2119 2128 user = request.user
2120 2129
2121 2130 # init auth user if not already given
2122 2131 if not isinstance(user, AuthUser):
2123 2132 log.debug('Wrapping user %s into AuthUser', user)
2124 2133 user = AuthUser(user.user_id)
2125 2134
2126 2135 cls_name = self.__class__.__name__
2127 2136 check_scope = self._get_check_scope(cls_name)
2128 2137 check_location = check_location or 'unspecified location'
2129 2138
2130 2139 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
2131 2140 self.required_perms, user, check_scope, check_location)
2132 2141 if not user:
2133 2142 log.warning('Empty user given for permission check')
2134 2143 return False
2135 2144
2136 2145 if self.check_permissions(user):
2137 2146 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2138 2147 check_scope, user, check_location)
2139 2148 return True
2140 2149
2141 2150 else:
2142 2151 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2143 2152 check_scope, user, check_location)
2144 2153 return False
2145 2154
2146 2155 def _get_request(self):
2147 2156 return get_request(self)
2148 2157
2149 2158 def _get_check_scope(self, cls_name):
2150 2159 return {
2151 2160 'HasPermissionAll': 'GLOBAL',
2152 2161 'HasPermissionAny': 'GLOBAL',
2153 2162 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2154 2163 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2155 2164 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2156 2165 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2157 2166 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2158 2167 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2159 2168 }.get(cls_name, '?:%s' % cls_name)
2160 2169
2161 2170 def check_permissions(self, user):
2162 2171 """Dummy function for overriding"""
2163 2172 raise Exception('You have to write this function in child class')
2164 2173
2165 2174
2166 2175 class HasPermissionAll(PermsFunction):
2167 2176 def check_permissions(self, user):
2168 2177 perms = user.permissions_with_scope({})
2169 2178 if self.required_perms.issubset(perms.get('global')):
2170 2179 return True
2171 2180 return False
2172 2181
2173 2182
2174 2183 class HasPermissionAny(PermsFunction):
2175 2184 def check_permissions(self, user):
2176 2185 perms = user.permissions_with_scope({})
2177 2186 if self.required_perms.intersection(perms.get('global')):
2178 2187 return True
2179 2188 return False
2180 2189
2181 2190
2182 2191 class HasRepoPermissionAll(PermsFunction):
2183 2192 def __call__(self, repo_name=None, check_location='', user=None):
2184 2193 self.repo_name = repo_name
2185 2194 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2186 2195
2187 2196 def _get_repo_name(self):
2188 2197 if not self.repo_name:
2189 2198 _request = self._get_request()
2190 2199 self.repo_name = get_repo_slug(_request)
2191 2200 return self.repo_name
2192 2201
2193 2202 def check_permissions(self, user):
2194 2203 self.repo_name = self._get_repo_name()
2195 2204 perms = user.permissions
2196 2205 try:
2197 2206 user_perms = {perms['repositories'][self.repo_name]}
2198 2207 except KeyError:
2199 2208 return False
2200 2209 if self.required_perms.issubset(user_perms):
2201 2210 return True
2202 2211 return False
2203 2212
2204 2213
2205 2214 class HasRepoPermissionAny(PermsFunction):
2206 2215 def __call__(self, repo_name=None, check_location='', user=None):
2207 2216 self.repo_name = repo_name
2208 2217 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2209 2218
2210 2219 def _get_repo_name(self):
2211 2220 if not self.repo_name:
2212 2221 _request = self._get_request()
2213 2222 self.repo_name = get_repo_slug(_request)
2214 2223 return self.repo_name
2215 2224
2216 2225 def check_permissions(self, user):
2217 2226 self.repo_name = self._get_repo_name()
2218 2227 perms = user.permissions
2219 2228 try:
2220 2229 user_perms = {perms['repositories'][self.repo_name]}
2221 2230 except KeyError:
2222 2231 return False
2223 2232 if self.required_perms.intersection(user_perms):
2224 2233 return True
2225 2234 return False
2226 2235
2227 2236
2228 2237 class HasRepoGroupPermissionAny(PermsFunction):
2229 2238 def __call__(self, group_name=None, check_location='', user=None):
2230 2239 self.repo_group_name = group_name
2231 2240 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2232 2241
2233 2242 def check_permissions(self, user):
2234 2243 perms = user.permissions
2235 2244 try:
2236 2245 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2237 2246 except KeyError:
2238 2247 return False
2239 2248 if self.required_perms.intersection(user_perms):
2240 2249 return True
2241 2250 return False
2242 2251
2243 2252
2244 2253 class HasRepoGroupPermissionAll(PermsFunction):
2245 2254 def __call__(self, group_name=None, check_location='', user=None):
2246 2255 self.repo_group_name = group_name
2247 2256 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2248 2257
2249 2258 def check_permissions(self, user):
2250 2259 perms = user.permissions
2251 2260 try:
2252 2261 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2253 2262 except KeyError:
2254 2263 return False
2255 2264 if self.required_perms.issubset(user_perms):
2256 2265 return True
2257 2266 return False
2258 2267
2259 2268
2260 2269 class HasUserGroupPermissionAny(PermsFunction):
2261 2270 def __call__(self, user_group_name=None, check_location='', user=None):
2262 2271 self.user_group_name = user_group_name
2263 2272 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2264 2273
2265 2274 def check_permissions(self, user):
2266 2275 perms = user.permissions
2267 2276 try:
2268 2277 user_perms = {perms['user_groups'][self.user_group_name]}
2269 2278 except KeyError:
2270 2279 return False
2271 2280 if self.required_perms.intersection(user_perms):
2272 2281 return True
2273 2282 return False
2274 2283
2275 2284
2276 2285 class HasUserGroupPermissionAll(PermsFunction):
2277 2286 def __call__(self, user_group_name=None, check_location='', user=None):
2278 2287 self.user_group_name = user_group_name
2279 2288 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2280 2289
2281 2290 def check_permissions(self, user):
2282 2291 perms = user.permissions
2283 2292 try:
2284 2293 user_perms = {perms['user_groups'][self.user_group_name]}
2285 2294 except KeyError:
2286 2295 return False
2287 2296 if self.required_perms.issubset(user_perms):
2288 2297 return True
2289 2298 return False
2290 2299
2291 2300
2292 2301 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2293 2302 class HasPermissionAnyMiddleware(object):
2294 2303 def __init__(self, *perms):
2295 2304 self.required_perms = set(perms)
2296 2305
2297 2306 def __call__(self, auth_user, repo_name):
2298 2307 # repo_name MUST be unicode, since we handle keys in permission
2299 2308 # dict by unicode
2300 2309 repo_name = safe_unicode(repo_name)
2301 2310 log.debug(
2302 2311 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2303 2312 self.required_perms, auth_user, repo_name)
2304 2313
2305 2314 if self.check_permissions(auth_user, repo_name):
2306 2315 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2307 2316 repo_name, auth_user, 'PermissionMiddleware')
2308 2317 return True
2309 2318
2310 2319 else:
2311 2320 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2312 2321 repo_name, auth_user, 'PermissionMiddleware')
2313 2322 return False
2314 2323
2315 2324 def check_permissions(self, user, repo_name):
2316 2325 perms = user.permissions_with_scope({'repo_name': repo_name})
2317 2326
2318 2327 try:
2319 2328 user_perms = {perms['repositories'][repo_name]}
2320 2329 except Exception:
2321 2330 log.exception('Error while accessing user permissions')
2322 2331 return False
2323 2332
2324 2333 if self.required_perms.intersection(user_perms):
2325 2334 return True
2326 2335 return False
2327 2336
2328 2337
2329 2338 # SPECIAL VERSION TO HANDLE API AUTH
2330 2339 class _BaseApiPerm(object):
2331 2340 def __init__(self, *perms):
2332 2341 self.required_perms = set(perms)
2333 2342
2334 2343 def __call__(self, check_location=None, user=None, repo_name=None,
2335 2344 group_name=None, user_group_name=None):
2336 2345 cls_name = self.__class__.__name__
2337 2346 check_scope = 'global:%s' % (self.required_perms,)
2338 2347 if repo_name:
2339 2348 check_scope += ', repo_name:%s' % (repo_name,)
2340 2349
2341 2350 if group_name:
2342 2351 check_scope += ', repo_group_name:%s' % (group_name,)
2343 2352
2344 2353 if user_group_name:
2345 2354 check_scope += ', user_group_name:%s' % (user_group_name,)
2346 2355
2347 2356 log.debug('checking cls:%s %s %s @ %s',
2348 2357 cls_name, self.required_perms, check_scope, check_location)
2349 2358 if not user:
2350 2359 log.debug('Empty User passed into arguments')
2351 2360 return False
2352 2361
2353 2362 # process user
2354 2363 if not isinstance(user, AuthUser):
2355 2364 user = AuthUser(user.user_id)
2356 2365 if not check_location:
2357 2366 check_location = 'unspecified'
2358 2367 if self.check_permissions(user.permissions, repo_name, group_name,
2359 2368 user_group_name):
2360 2369 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2361 2370 check_scope, user, check_location)
2362 2371 return True
2363 2372
2364 2373 else:
2365 2374 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2366 2375 check_scope, user, check_location)
2367 2376 return False
2368 2377
2369 2378 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2370 2379 user_group_name=None):
2371 2380 """
2372 2381 implement in child class should return True if permissions are ok,
2373 2382 False otherwise
2374 2383
2375 2384 :param perm_defs: dict with permission definitions
2376 2385 :param repo_name: repo name
2377 2386 """
2378 2387 raise NotImplementedError()
2379 2388
2380 2389
2381 2390 class HasPermissionAllApi(_BaseApiPerm):
2382 2391 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2383 2392 user_group_name=None):
2384 2393 if self.required_perms.issubset(perm_defs.get('global')):
2385 2394 return True
2386 2395 return False
2387 2396
2388 2397
2389 2398 class HasPermissionAnyApi(_BaseApiPerm):
2390 2399 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2391 2400 user_group_name=None):
2392 2401 if self.required_perms.intersection(perm_defs.get('global')):
2393 2402 return True
2394 2403 return False
2395 2404
2396 2405
2397 2406 class HasRepoPermissionAllApi(_BaseApiPerm):
2398 2407 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2399 2408 user_group_name=None):
2400 2409 try:
2401 2410 _user_perms = {perm_defs['repositories'][repo_name]}
2402 2411 except KeyError:
2403 2412 log.warning(traceback.format_exc())
2404 2413 return False
2405 2414 if self.required_perms.issubset(_user_perms):
2406 2415 return True
2407 2416 return False
2408 2417
2409 2418
2410 2419 class HasRepoPermissionAnyApi(_BaseApiPerm):
2411 2420 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2412 2421 user_group_name=None):
2413 2422 try:
2414 2423 _user_perms = {perm_defs['repositories'][repo_name]}
2415 2424 except KeyError:
2416 2425 log.warning(traceback.format_exc())
2417 2426 return False
2418 2427 if self.required_perms.intersection(_user_perms):
2419 2428 return True
2420 2429 return False
2421 2430
2422 2431
2423 2432 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2424 2433 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2425 2434 user_group_name=None):
2426 2435 try:
2427 2436 _user_perms = {perm_defs['repositories_groups'][group_name]}
2428 2437 except KeyError:
2429 2438 log.warning(traceback.format_exc())
2430 2439 return False
2431 2440 if self.required_perms.intersection(_user_perms):
2432 2441 return True
2433 2442 return False
2434 2443
2435 2444
2436 2445 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2437 2446 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2438 2447 user_group_name=None):
2439 2448 try:
2440 2449 _user_perms = {perm_defs['repositories_groups'][group_name]}
2441 2450 except KeyError:
2442 2451 log.warning(traceback.format_exc())
2443 2452 return False
2444 2453 if self.required_perms.issubset(_user_perms):
2445 2454 return True
2446 2455 return False
2447 2456
2448 2457
2449 2458 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2450 2459 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2451 2460 user_group_name=None):
2452 2461 try:
2453 2462 _user_perms = {perm_defs['user_groups'][user_group_name]}
2454 2463 except KeyError:
2455 2464 log.warning(traceback.format_exc())
2456 2465 return False
2457 2466 if self.required_perms.intersection(_user_perms):
2458 2467 return True
2459 2468 return False
2460 2469
2461 2470
2462 2471 def check_ip_access(source_ip, allowed_ips=None):
2463 2472 """
2464 2473 Checks if source_ip is a subnet of any of allowed_ips.
2465 2474
2466 2475 :param source_ip:
2467 2476 :param allowed_ips: list of allowed ips together with mask
2468 2477 """
2469 2478 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2470 2479 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2471 2480 if isinstance(allowed_ips, (tuple, list, set)):
2472 2481 for ip in allowed_ips:
2473 2482 ip = safe_unicode(ip)
2474 2483 try:
2475 2484 network_address = ipaddress.ip_network(ip, strict=False)
2476 2485 if source_ip_address in network_address:
2477 2486 log.debug('IP %s is network %s', source_ip_address, network_address)
2478 2487 return True
2479 2488 # for any case we cannot determine the IP, don't crash just
2480 2489 # skip it and log as error, we want to say forbidden still when
2481 2490 # sending bad IP
2482 2491 except Exception:
2483 2492 log.error(traceback.format_exc())
2484 2493 continue
2485 2494 return False
2486 2495
2487 2496
2488 2497 def get_cython_compat_decorator(wrapper, func):
2489 2498 """
2490 2499 Creates a cython compatible decorator. The previously used
2491 2500 decorator.decorator() function seems to be incompatible with cython.
2492 2501
2493 2502 :param wrapper: __wrapper method of the decorator class
2494 2503 :param func: decorated function
2495 2504 """
2496 2505 @wraps(func)
2497 2506 def local_wrapper(*args, **kwds):
2498 2507 return wrapper(func, *args, **kwds)
2499 2508 local_wrapper.__wrapped__ = func
2500 2509 return local_wrapper
2501 2510
2502 2511
@@ -1,299 +1,304 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <%
4 4 elems = [
5 5 (_('Repository ID'), c.rhodecode_db_repo.repo_id, '', ''),
6 6 (_('Owner'), lambda:base.gravatar_with_user(c.rhodecode_db_repo.user.email, tooltip=True), '', ''),
7 7 (_('Created on'), h.format_date(c.rhodecode_db_repo.created_on), '', ''),
8 8 (_('Updated on'), h.format_date(c.rhodecode_db_repo.updated_on), '', ''),
9 9 (_('Cached Commit id'), lambda: h.link_to(c.rhodecode_db_repo.changeset_cache.get('short_id'), h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.changeset_cache.get('raw_id'))), '', ''),
10 10 (_('Cached Commit date'), c.rhodecode_db_repo.changeset_cache.get('date'), '', ''),
11 11 (_('Cached Commit data'), lambda: h.link_to('refresh now', h.current_route_path(request, update_commit_cache=1)), '', ''),
12 12 (_('Attached scoped tokens'), len(c.rhodecode_db_repo.scoped_tokens), '', [x.user for x in c.rhodecode_db_repo.scoped_tokens]),
13 13 (_('Pull requests source'), len(c.rhodecode_db_repo.pull_requests_source), '', ['pr_id:{}, repo:{}'.format(x.pull_request_id,x.source_repo.repo_name) for x in c.rhodecode_db_repo.pull_requests_source]),
14 14 (_('Pull requests target'), len(c.rhodecode_db_repo.pull_requests_target), '', ['pr_id:{}, repo:{}'.format(x.pull_request_id,x.target_repo.repo_name) for x in c.rhodecode_db_repo.pull_requests_target]),
15 15 (_('Attached Artifacts'), len(c.rhodecode_db_repo.artifacts), '', ''),
16 16 ]
17 17 %>
18 18
19 19 <div class="panel panel-default">
20 20 <div class="panel-heading" id="advanced-info" >
21 21 <h3 class="panel-title">${_('Repository: %s') % c.rhodecode_db_repo.repo_name} <a class="permalink" href="#advanced-info"> ΒΆ</a></h3>
22 22 </div>
23 23 <div class="panel-body">
24 24 ${base.dt_info_panel(elems)}
25 25 </div>
26 26 </div>
27 27
28 28
29 29 <div class="panel panel-default">
30 30 <div class="panel-heading" id="advanced-fork">
31 31 <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"> ΒΆ</a></h3>
32 32 </div>
33 33 <div class="panel-body">
34 34 ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
35 35
36 36 % if c.rhodecode_db_repo.fork:
37 37 <div class="panel-body-title-text">${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.rhodecode_db_repo.fork.repo_name, h.route_path('repo_summary', repo_name=c.rhodecode_db_repo.fork.repo_name))})}
38 38 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
39 39 % endif
40 40
41 41 <div class="field">
42 42 ${h.hidden('id_fork_of')}
43 43 ${h.submit('set_as_fork_%s' % c.rhodecode_db_repo.repo_name,_('Set'),class_="btn btn-small",)}
44 44 </div>
45 45 <div class="field">
46 46 <span class="help-block">${_('Manually set this repository as a fork of another from the list')}</span>
47 47 </div>
48 48 ${h.end_form()}
49 49 </div>
50 50 </div>
51 51
52 52
53 53 <div class="panel panel-default">
54 54 <div class="panel-heading" id="advanced-journal">
55 55 <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"> ΒΆ</a></h3>
56 56 </div>
57 57 <div class="panel-body">
58 58 ${h.secure_form(h.route_path('edit_repo_advanced_journal', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
59 59 <div class="field">
60 60 %if c.in_public_journal:
61 61 <button class="btn btn-small" type="submit">
62 62 ${_('Remove from Public Journal')}
63 63 </button>
64 64 %else:
65 65 <button class="btn btn-small" type="submit">
66 66 ${_('Add to Public Journal')}
67 67 </button>
68 68 %endif
69 69 </div>
70 70 <div class="field" >
71 71 <span class="help-block">${_('All actions made on this repository will be visible to everyone following the public journal.')}</span>
72 72 </div>
73 73 ${h.end_form()}
74 74 </div>
75 75 </div>
76 76
77 77
78 78 <div class="panel panel-default">
79 79 <div class="panel-heading" id="advanced-locking">
80 80 <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"> ΒΆ</a></h3>
81 81 </div>
82 82 <div class="panel-body">
83 83 ${h.secure_form(h.route_path('edit_repo_advanced_locking', repo_name=c.rhodecode_db_repo.repo_name), request=request)}
84 84
85 85 %if c.rhodecode_db_repo.locked[0]:
86 86 <div class="panel-body-title-text">${'Locked by %s on %s. Lock reason: %s' % (h.person_by_id(c.rhodecode_db_repo.locked[0]),
87 87 h.format_date(h. time_to_datetime(c.rhodecode_db_repo.locked[1])), c.rhodecode_db_repo.locked[2])}</div>
88 88 %else:
89 89 <div class="panel-body-title-text">${_('This Repository is not currently locked.')}</div>
90 90 %endif
91 91
92 92 <div class="field" >
93 93 %if c.rhodecode_db_repo.locked[0]:
94 94 ${h.hidden('set_unlock', '1')}
95 95 <button class="btn btn-small" type="submit"
96 96 onclick="submitConfirm(event, this, _gettext('Confirm to unlock this repository'), _gettext('Unlock'), '${c.rhodecode_db_repo.repo_name}')"
97 97 >
98 98 <i class="icon-unlock"></i>
99 99 ${_('Unlock repository')}
100 100 </button>
101 101 %else:
102 102 ${h.hidden('set_lock', '1')}
103 103 <button class="btn btn-small" type="submit"
104 104 onclick="submitConfirm(event, this, _gettext('Confirm to lock this repository'), _gettext('lock'), '${c.rhodecode_db_repo.repo_name}')"
105 105 >
106 106 <i class="icon-lock"></i>
107 107 ${_('Lock repository')}
108 108 </button>
109 109 %endif
110 110 </div>
111 111 <div class="field" >
112 112 <span class="help-block">
113 113 ${_('Force repository locking. This only works when anonymous access is disabled. Pulling from the repository locks the repository to that user until the same user pushes to that repository again.')}
114 114 </span>
115 115 </div>
116 116 ${h.end_form()}
117 117 </div>
118 118 </div>
119 119
120 120
121 121 <div class="panel panel-default">
122 122 <div class="panel-heading" id="advanced-hooks">
123 123 <h3 class="panel-title">${_('Hooks')} <a class="permalink" href="#advanced-hooks"> ΒΆ</a></h3>
124 124 </div>
125 125 <div class="panel-body">
126 126 <table class="rctable">
127 127 <th>${_('Hook type')}</th>
128 128 <th>${_('Hook version')}</th>
129 129 <th>${_('Current version')}</th>
130 130 % if c.ver_info_dict:
131 131 <tr>
132 132 <td>${_('PRE HOOK')}</td>
133 133 <td>${c.ver_info_dict['pre_version']}</td>
134 134 <td>${c.rhodecode_version}</td>
135 135 </tr>
136 136 <tr>
137 137 <td>${_('POST HOOK')}</td>
138 138 <td>${c.ver_info_dict['post_version']}</td>
139 139 <td>${c.rhodecode_version}</td>
140 140 </tr>
141 141 % else:
142 142 <tr>
143 143 <td>${_('Unable to read hook information from VCS Server')}</td>
144 144 </tr>
145 145 % endif
146 146 </table>
147 147
148 148 <a class="btn btn-primary" href="${h.route_path('edit_repo_advanced_hooks', repo_name=c.repo_name)}"
149 149 onclick="return confirm('${_('Confirm to reinstall hooks for this repository.')}');">
150 150 ${_('Update Hooks')}
151 151 </a>
152 152 % if c.hooks_outdated:
153 153 <span class="alert-error" style="padding: 10px">
154 154 ${_('Outdated hooks detected, please update hooks using `Update Hooks` action.')}
155 155 </span>
156 156 % endif
157 157 </div>
158 158 </div>
159 159
160 160 <div class="panel panel-warning">
161 161 <div class="panel-heading" id="advanced-archive">
162 162 <h3 class="panel-title">${_('Archive repository')} <a class="permalink" href="#advanced-archive"> ΒΆ</a></h3>
163 163 </div>
164 164 <div class="panel-body">
165 165 ${h.secure_form(h.route_path('edit_repo_advanced_archive', repo_name=c.repo_name), request=request)}
166 166
167 167 <div style="margin: 0 0 20px 0" class="fake-space"></div>
168 168
169 169 <div class="field">
170 % if c.rhodecode_db_repo.archived:
171 This repository is already archived. Only super-admin users can un-archive this repository.
172 % else:
170 173 <button class="btn btn-small btn-warning" type="submit"
171 174 onclick="submitConfirm(event, this, _gettext('Confirm to archive this repository'), _gettext('Archive'), '${c.rhodecode_db_repo.repo_name}')"
172 175 >
173 176 ${_('Archive this repository')}
174 177 </button>
178 % endif
179
175 180 </div>
176 181 <div class="field">
177 182 <span class="help-block">
178 183 ${_('Archiving the repository will make it entirely read-only. The repository cannot be committed to.'
179 184 'It is hidden from the search results and dashboard. ')}
180 185 </span>
181 186 </div>
182 187
183 188 ${h.end_form()}
184 189 </div>
185 190 </div>
186 191
187 192
188 193 <div class="panel panel-danger">
189 194 <div class="panel-heading" id="advanced-delete">
190 195 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"> ΒΆ</a></h3>
191 196 </div>
192 197 <div class="panel-body">
193 198 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), request=request)}
194 199 <table class="display">
195 200 <tr>
196 201 <td>
197 202 ${_ungettext('This repository has %s fork.', 'This repository has %s forks.', c.rhodecode_db_repo.forks.count()) % c.rhodecode_db_repo.forks.count()}
198 203 </td>
199 204 <td>
200 205 %if c.rhodecode_db_repo.forks.count():
201 206 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
202 207 %endif
203 208 </td>
204 209 <td>
205 210 %if c.rhodecode_db_repo.forks.count():
206 211 <input type="radio" name="forks" value="delete_forks"/> <label for="forks">${_('Delete forks')}</label>
207 212 %endif
208 213 </td>
209 214 </tr>
210 215 <% attached_prs = len(c.rhodecode_db_repo.pull_requests_source + c.rhodecode_db_repo.pull_requests_target) %>
211 216 % if c.rhodecode_db_repo.pull_requests_source or c.rhodecode_db_repo.pull_requests_target:
212 217 <tr>
213 218 <td>
214 219 ${_ungettext('This repository has %s attached pull request.', 'This repository has %s attached pull requests.', attached_prs) % attached_prs}
215 220 <br/>
216 221 ${_('Consider to archive this repository instead.')}
217 222 </td>
218 223 <td></td>
219 224 <td></td>
220 225 </tr>
221 226 % endif
222 227 </table>
223 228 <div style="margin: 0 0 20px 0" class="fake-space"></div>
224 229
225 230 <div class="field">
226 231 <button class="btn btn-small btn-danger" type="submit"
227 232 onclick="submitConfirm(event, this, _gettext('Confirm to delete this repository'), _gettext('Delete'), '${c.rhodecode_db_repo.repo_name}')"
228 233 >
229 234 ${_('Delete this repository')}
230 235 </button>
231 236 </div>
232 237 <div class="field">
233 238 <span class="help-block">
234 239 ${_('This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command available in rhodecode-tools.')}
235 240 </span>
236 241 </div>
237 242
238 243 ${h.end_form()}
239 244 </div>
240 245 </div>
241 246
242 247
243 248 <script>
244 249
245 250 var currentRepoId = ${c.rhodecode_db_repo.repo_id};
246 251
247 252 var repoTypeFilter = function(data) {
248 253 var results = [];
249 254
250 255 if (!data.results[0]) {
251 256 return data
252 257 }
253 258
254 259 $.each(data.results[0].children, function() {
255 260 // filter out the SAME repo, it cannot be used as fork of itself
256 261 if (this.repo_id != currentRepoId) {
257 262 this.id = this.repo_id;
258 263 results.push(this)
259 264 }
260 265 });
261 266 data.results[0].children = results;
262 267 return data;
263 268 };
264 269
265 270 $("#id_fork_of").select2({
266 271 cachedDataSource: {},
267 272 minimumInputLength: 2,
268 273 placeholder: "${_('Change repository') if c.rhodecode_db_repo.fork else _('Pick repository')}",
269 274 dropdownAutoWidth: true,
270 275 containerCssClass: "drop-menu",
271 276 dropdownCssClass: "drop-menu-dropdown",
272 277 formatResult: formatRepoResult,
273 278 query: $.debounce(250, function(query){
274 279 self = this;
275 280 var cacheKey = query.term;
276 281 var cachedData = self.cachedDataSource[cacheKey];
277 282
278 283 if (cachedData) {
279 284 query.callback({results: cachedData.results});
280 285 } else {
281 286 $.ajax({
282 287 url: pyroutes.url('repo_list_data'),
283 288 data: {'query': query.term, repo_type: '${c.rhodecode_db_repo.repo_type}'},
284 289 dataType: 'json',
285 290 type: 'GET',
286 291 success: function(data) {
287 292 data = repoTypeFilter(data);
288 293 self.cachedDataSource[cacheKey] = data;
289 294 query.callback({results: data.results});
290 295 },
291 296 error: function(data, textStatus, errorThrown) {
292 297 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
293 298 }
294 299 })
295 300 }
296 301 })
297 302 });
298 303 </script>
299 304
@@ -1,1206 +1,1206 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%!
4 4 from rhodecode.lib import html_filters
5 5 %>
6 6
7 7 <%inherit file="root.mako"/>
8 8
9 9 <%include file="/ejs_templates/templates.html"/>
10 10
11 11 <div class="outerwrapper">
12 12 <!-- HEADER -->
13 13 <div class="header">
14 14 <div id="header-inner" class="wrapper">
15 15 <div id="logo">
16 16 <div class="logo-wrapper">
17 17 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
18 18 </div>
19 19 % if c.rhodecode_name:
20 20 <div class="branding">
21 21 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
22 22 </div>
23 23 % endif
24 24 </div>
25 25 <!-- MENU BAR NAV -->
26 26 ${self.menu_bar_nav()}
27 27 <!-- END MENU BAR NAV -->
28 28 </div>
29 29 </div>
30 30 ${self.menu_bar_subnav()}
31 31 <!-- END HEADER -->
32 32
33 33 <!-- CONTENT -->
34 34 <div id="content" class="wrapper">
35 35
36 36 <rhodecode-toast id="notifications"></rhodecode-toast>
37 37
38 38 <div class="main">
39 39 ${next.main()}
40 40 </div>
41 41 </div>
42 42 <!-- END CONTENT -->
43 43
44 44 </div>
45 45 <!-- FOOTER -->
46 46 <div id="footer">
47 47 <div id="footer-inner" class="title wrapper">
48 48 <div>
49 49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 50
51 51 <p class="footer-link-right">
52 52 <a class="grey-link-action" href="${h.route_path('home', _query={'showrcid': 1})}">
53 53 RhodeCode
54 54 % if c.visual.show_version:
55 55 ${c.rhodecode_version}
56 56 % endif
57 57 ${c.rhodecode_edition}
58 58 </a> |
59 59
60 60 % if c.visual.rhodecode_support_url:
61 61 <a class="grey-link-action" href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a> |
62 62 <a class="grey-link-action" href="https://docs.rhodecode.com" target="_blank">${_('Documentation')}</a>
63 63 % endif
64 64
65 65 </p>
66 66
67 67 <p class="server-instance" style="display:${sid}">
68 68 ## display hidden instance ID if specially defined
69 69 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
70 70 % if c.rhodecode_instanceid:
71 71 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
72 72 % endif
73 73 </p>
74 74 </div>
75 75 </div>
76 76 </div>
77 77
78 78 <!-- END FOOTER -->
79 79
80 80 ### MAKO DEFS ###
81 81
82 82 <%def name="menu_bar_subnav()">
83 83 </%def>
84 84
85 85 <%def name="breadcrumbs(class_='breadcrumbs')">
86 86 <div class="${class_}">
87 87 ${self.breadcrumbs_links()}
88 88 </div>
89 89 </%def>
90 90
91 91 <%def name="admin_menu(active=None)">
92 92
93 93 <div id="context-bar">
94 94 <div class="wrapper">
95 95 <div class="title">
96 96 <div class="title-content">
97 97 <div class="title-main">
98 98 % if c.is_super_admin:
99 99 ${_('Super-admin Panel')}
100 100 % else:
101 101 ${_('Delegated Admin Panel')}
102 102 % endif
103 103 </div>
104 104 </div>
105 105 </div>
106 106
107 107 <ul id="context-pages" class="navigation horizontal-list">
108 108
109 109 ## super-admin case
110 110 % if c.is_super_admin:
111 111 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
112 112 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
113 113 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
114 114 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
115 115 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
116 116 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
117 117 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
118 118 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
119 119 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
120 120 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
121 121
122 122 ## delegated admin
123 123 % elif c.is_delegated_admin:
124 124 <%
125 125 repositories=c.auth_user.repositories_admin or c.can_create_repo
126 126 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
127 127 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
128 128 %>
129 129
130 130 %if repositories:
131 131 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
132 132 %endif
133 133 %if repository_groups:
134 134 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
135 135 %endif
136 136 %if user_groups:
137 137 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
138 138 %endif
139 139 % endif
140 140 </ul>
141 141
142 142 </div>
143 143 <div class="clear"></div>
144 144 </div>
145 145 </%def>
146 146
147 147 <%def name="dt_info_panel(elements)">
148 148 <dl class="dl-horizontal">
149 149 %for dt, dd, title, show_items in elements:
150 150 <dt>${dt}:</dt>
151 151 <dd title="${h.tooltip(title)}">
152 152 %if callable(dd):
153 153 ## allow lazy evaluation of elements
154 154 ${dd()}
155 155 %else:
156 156 ${dd}
157 157 %endif
158 158 %if show_items:
159 159 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
160 160 %endif
161 161 </dd>
162 162
163 163 %if show_items:
164 164 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
165 165 %for item in show_items:
166 166 <dt></dt>
167 167 <dd>${item}</dd>
168 168 %endfor
169 169 </div>
170 170 %endif
171 171
172 172 %endfor
173 173 </dl>
174 174 </%def>
175 175
176 176 <%def name="tr_info_entry(element)">
177 177 <% key, val, title, show_items = element %>
178 178
179 179 <tr>
180 180 <td style="vertical-align: top">${key}</td>
181 181 <td title="${h.tooltip(title)}">
182 182 %if callable(val):
183 183 ## allow lazy evaluation of elements
184 184 ${val()}
185 185 %else:
186 186 ${val}
187 187 %endif
188 188 %if show_items:
189 189 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
190 190 % for item in show_items:
191 191 <dt></dt>
192 192 <dd>${item}</dd>
193 193 % endfor
194 194 </div>
195 195 %endif
196 196 </td>
197 197 <td style="vertical-align: top">
198 198 %if show_items:
199 199 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
200 200 %endif
201 201 </td>
202 202 </tr>
203 203
204 204 </%def>
205 205
206 206 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
207 207 <%
208 208 if size > 16:
209 209 gravatar_class = ['gravatar','gravatar-large']
210 210 else:
211 211 gravatar_class = ['gravatar']
212 212
213 213 data_hovercard_url = ''
214 214 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
215 215
216 216 if tooltip:
217 217 gravatar_class += ['tooltip-hovercard']
218 218 if extra_class:
219 219 gravatar_class += extra_class
220 220 if tooltip and user:
221 221 if user.username == h.DEFAULT_USER:
222 222 gravatar_class.pop(-1)
223 223 else:
224 224 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
225 225 gravatar_class = ' '.join(gravatar_class)
226 226
227 227 %>
228 228 <%doc>
229 229 TODO: johbo: For now we serve double size images to make it smooth
230 230 for retina. This is how it worked until now. Should be replaced
231 231 with a better solution at some point.
232 232 </%doc>
233 233
234 234 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
235 235 </%def>
236 236
237 237
238 238 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
239 239 <%
240 240 email = h.email_or_none(contact)
241 241 rc_user = h.discover_user(contact)
242 242 %>
243 243
244 244 <div class="${_class}">
245 245 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
246 246 <span class="${('user user-disabled' if show_disabled else 'user')}"> ${h.link_to_user(rc_user or contact)}</span>
247 247 </div>
248 248 </%def>
249 249
250 250
251 251 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
252 252 <%
253 253 if (size > 16):
254 254 gravatar_class = 'icon-user-group-alt'
255 255 else:
256 256 gravatar_class = 'icon-user-group-alt'
257 257
258 258 if tooltip:
259 259 gravatar_class += ' tooltip-hovercard'
260 260
261 261 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
262 262 %>
263 263 <%doc>
264 264 TODO: johbo: For now we serve double size images to make it smooth
265 265 for retina. This is how it worked until now. Should be replaced
266 266 with a better solution at some point.
267 267 </%doc>
268 268
269 269 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
270 270 </%def>
271 271
272 272 <%def name="repo_page_title(repo_instance)">
273 273 <div class="title-content repo-title">
274 274
275 275 <div class="title-main">
276 276 ## SVN/HG/GIT icons
277 277 %if h.is_hg(repo_instance):
278 278 <i class="icon-hg"></i>
279 279 %endif
280 280 %if h.is_git(repo_instance):
281 281 <i class="icon-git"></i>
282 282 %endif
283 283 %if h.is_svn(repo_instance):
284 284 <i class="icon-svn"></i>
285 285 %endif
286 286
287 287 ## public/private
288 288 %if repo_instance.private:
289 289 <i class="icon-repo-private"></i>
290 290 %else:
291 291 <i class="icon-repo-public"></i>
292 292 %endif
293 293
294 294 ## repo name with group name
295 295 ${h.breadcrumb_repo_link(repo_instance)}
296 296
297 297 ## Context Actions
298 298 <div class="pull-right">
299 299 %if c.rhodecode_user.username != h.DEFAULT_USER:
300 300 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
301 301
302 302 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
303 303 % if c.repository_is_user_following:
304 304 <i class="icon-eye-off"></i>${_('Unwatch')}
305 305 % else:
306 306 <i class="icon-eye"></i>${_('Watch')}
307 307 % endif
308 308
309 309 </a>
310 310 %else:
311 311 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
312 312 %endif
313 313 </div>
314 314
315 315 </div>
316 316
317 317 ## FORKED
318 318 %if repo_instance.fork:
319 319 <p class="discreet">
320 320 <i class="icon-code-fork"></i> ${_('Fork of')}
321 321 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
322 322 </p>
323 323 %endif
324 324
325 325 ## IMPORTED FROM REMOTE
326 326 %if repo_instance.clone_uri:
327 327 <p class="discreet">
328 328 <i class="icon-code-fork"></i> ${_('Clone from')}
329 329 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
330 330 </p>
331 331 %endif
332 332
333 333 ## LOCKING STATUS
334 334 %if repo_instance.locked[0]:
335 335 <p class="locking_locked discreet">
336 336 <i class="icon-repo-lock"></i>
337 337 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
338 338 </p>
339 339 %elif repo_instance.enable_locking:
340 340 <p class="locking_unlocked discreet">
341 341 <i class="icon-repo-unlock"></i>
342 342 ${_('Repository not locked. Pull repository to lock it.')}
343 343 </p>
344 344 %endif
345 345
346 346 </div>
347 347 </%def>
348 348
349 349 <%def name="repo_menu(active=None)">
350 350 <%
351 351 ## determine if we have "any" option available
352 352 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
353 353 has_actions = can_lock
354 354
355 355 %>
356 356 % if c.rhodecode_db_repo.archived:
357 357 <div class="alert alert-warning text-center">
358 358 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
359 359 </div>
360 360 % endif
361 361
362 362 <!--- REPO CONTEXT BAR -->
363 363 <div id="context-bar">
364 364 <div class="wrapper">
365 365
366 366 <div class="title">
367 367 ${self.repo_page_title(c.rhodecode_db_repo)}
368 368 </div>
369 369
370 370 <ul id="context-pages" class="navigation horizontal-list">
371 371 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary_explicit', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
372 372 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
373 373 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.repo_files_by_ref_url(c.repo_name, c.rhodecode_db_repo.repo_type, f_path='', ref_name=c.rhodecode_db_repo.landing_ref_name, commit_id='tip', query={'at':c.rhodecode_db_repo.landing_ref_name})}"><div class="menulabel">${_('Files')}</div></a></li>
374 374 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
375 375
376 376 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
377 377 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
378 378 <li class="${h.is_active('showpullrequest', active)}">
379 379 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
380 380 <div class="menulabel">
381 381 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
382 382 </div>
383 383 </a>
384 384 </li>
385 385 %endif
386 386
387 387 <li class="${h.is_active('artifacts', active)}">
388 388 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
389 389 <div class="menulabel">
390 390 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
391 391 </div>
392 392 </a>
393 393 </li>
394 394
395 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
395 %if not c.rhodecode_db_repo.archived and h.HasRepoPermissionAll('repository.admin')(c.repo_name):
396 396 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
397 397 %endif
398 398
399 399 <li class="${h.is_active('options', active)}">
400 400 % if has_actions:
401 401 <a class="menulink dropdown">
402 402 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
403 403 </a>
404 404 <ul class="submenu">
405 405 %if can_lock:
406 406 %if c.rhodecode_db_repo.locked[0]:
407 407 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
408 408 %else:
409 409 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
410 410 %endif
411 411 %endif
412 412 </ul>
413 413 % endif
414 414 </li>
415 415
416 416 </ul>
417 417 </div>
418 418 <div class="clear"></div>
419 419 </div>
420 420
421 421 <!--- REPO END CONTEXT BAR -->
422 422
423 423 </%def>
424 424
425 425 <%def name="repo_group_page_title(repo_group_instance)">
426 426 <div class="title-content">
427 427 <div class="title-main">
428 428 ## Repository Group icon
429 429 <i class="icon-repo-group"></i>
430 430
431 431 ## repo name with group name
432 432 ${h.breadcrumb_repo_group_link(repo_group_instance)}
433 433 </div>
434 434
435 435 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
436 436 <div class="repo-group-desc discreet">
437 437 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
438 438 </div>
439 439
440 440 </div>
441 441 </%def>
442 442
443 443
444 444 <%def name="repo_group_menu(active=None)">
445 445 <%
446 446 gr_name = c.repo_group.group_name if c.repo_group else None
447 447 # create repositories with write permission on group is set to true
448 448 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
449 449
450 450 %>
451 451
452 452
453 453 <!--- REPO GROUP CONTEXT BAR -->
454 454 <div id="context-bar">
455 455 <div class="wrapper">
456 456 <div class="title">
457 457 ${self.repo_group_page_title(c.repo_group)}
458 458 </div>
459 459
460 460 <ul id="context-pages" class="navigation horizontal-list">
461 461 <li class="${h.is_active('home', active)}">
462 462 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
463 463 </li>
464 464 % if c.is_super_admin or group_admin:
465 465 <li class="${h.is_active('settings', active)}">
466 466 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
467 467 </li>
468 468 % endif
469 469
470 470 </ul>
471 471 </div>
472 472 <div class="clear"></div>
473 473 </div>
474 474
475 475 <!--- REPO GROUP CONTEXT BAR -->
476 476
477 477 </%def>
478 478
479 479
480 480 <%def name="usermenu(active=False)">
481 481 <%
482 482 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
483 483
484 484 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
485 485 # create repositories with write permission on group is set to true
486 486
487 487 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
488 488 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
489 489 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
490 490 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
491 491
492 492 can_create_repos = c.is_super_admin or c.can_create_repo
493 493 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
494 494
495 495 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
496 496 can_create_repo_groups_in_group = c.is_super_admin or group_admin
497 497 %>
498 498
499 499 % if not_anonymous:
500 500 <%
501 501 default_target_group = dict()
502 502 if c.rhodecode_user.personal_repo_group:
503 503 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
504 504 %>
505 505
506 506 ## create action
507 507 <li>
508 508 <a href="#create-actions" onclick="return false;" class="menulink childs">
509 509 <i class="tooltip icon-plus-circled" title="${_('Create')}"></i>
510 510 </a>
511 511
512 512 <div class="action-menu submenu">
513 513
514 514 <ol>
515 515 ## scope of within a repository
516 516 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
517 517 <li class="submenu-title">${_('This Repository')}</li>
518 518 <li>
519 519 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
520 520 </li>
521 521 % if can_fork:
522 522 <li>
523 523 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
524 524 </li>
525 525 % endif
526 526 % endif
527 527
528 528 ## scope of within repository groups
529 529 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
530 530 <li class="submenu-title">${_('This Repository Group')}</li>
531 531
532 532 % if can_create_repos_in_group:
533 533 <li>
534 534 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
535 535 </li>
536 536 % endif
537 537
538 538 % if can_create_repo_groups_in_group:
539 539 <li>
540 540 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
541 541 </li>
542 542 % endif
543 543 % endif
544 544
545 545 ## personal group
546 546 % if c.rhodecode_user.personal_repo_group:
547 547 <li class="submenu-title">Personal Group</li>
548 548
549 549 <li>
550 550 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
551 551 </li>
552 552
553 553 <li>
554 554 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
555 555 </li>
556 556 % endif
557 557
558 558 ## Global actions
559 559 <li class="submenu-title">RhodeCode</li>
560 560 % if can_create_repos:
561 561 <li>
562 562 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
563 563 </li>
564 564 % endif
565 565
566 566 % if can_create_repo_groups:
567 567 <li>
568 568 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
569 569 </li>
570 570 % endif
571 571
572 572 <li>
573 573 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
574 574 </li>
575 575
576 576 </ol>
577 577
578 578 </div>
579 579 </li>
580 580
581 581 ## notifications
582 582 <li>
583 583 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
584 584 ${c.unread_notifications}
585 585 </a>
586 586 </li>
587 587 % endif
588 588
589 589 ## USER MENU
590 590 <li id="quick_login_li" class="${'active' if active else ''}">
591 591 % if c.rhodecode_user.username == h.DEFAULT_USER:
592 592 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
593 593 ${gravatar(c.rhodecode_user.email, 20)}
594 594 <span class="user">
595 595 <span>${_('Sign in')}</span>
596 596 </span>
597 597 </a>
598 598 % else:
599 599 ## logged in user
600 600 <a id="quick_login_link" class="menulink childs">
601 601 ${gravatar(c.rhodecode_user.email, 20)}
602 602 <span class="user">
603 603 <span class="menu_link_user">${c.rhodecode_user.username}</span>
604 604 <div class="show_more"></div>
605 605 </span>
606 606 </a>
607 607 ## subnav with menu for logged in user
608 608 <div class="user-menu submenu">
609 609 <div id="quick_login">
610 610 %if c.rhodecode_user.username != h.DEFAULT_USER:
611 611 <div class="">
612 612 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
613 613 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
614 614 <div class="email">${c.rhodecode_user.email}</div>
615 615 </div>
616 616 <div class="">
617 617 <ol class="links">
618 618 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
619 619 % if c.rhodecode_user.personal_repo_group:
620 620 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
621 621 % endif
622 622 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
623 623
624 624 % if c.debug_style:
625 625 <li>
626 626 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
627 627 <div class="menulabel">${_('[Style]')}</div>
628 628 </a>
629 629 </li>
630 630 % endif
631 631
632 632 ## bookmark-items
633 633 <li class="bookmark-items">
634 634 ${_('Bookmarks')}
635 635 <div class="pull-right">
636 636 <a href="${h.route_path('my_account_bookmarks')}">
637 637
638 638 <i class="icon-cog"></i>
639 639 </a>
640 640 </div>
641 641 </li>
642 642 % if not c.bookmark_items:
643 643 <li>
644 644 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
645 645 </li>
646 646 % endif
647 647 % for item in c.bookmark_items:
648 648 <li>
649 649 % if item.repository:
650 650 <div>
651 651 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
652 652 <code>${item.position}</code>
653 653 % if item.repository.repo_type == 'hg':
654 654 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
655 655 % elif item.repository.repo_type == 'git':
656 656 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
657 657 % elif item.repository.repo_type == 'svn':
658 658 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
659 659 % endif
660 660 ${(item.title or h.shorter(item.repository.repo_name, 30))}
661 661 </a>
662 662 </div>
663 663 % elif item.repository_group:
664 664 <div>
665 665 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
666 666 <code>${item.position}</code>
667 667 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
668 668 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
669 669 </a>
670 670 </div>
671 671 % else:
672 672 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
673 673 <code>${item.position}</code>
674 674 ${item.title}
675 675 </a>
676 676 % endif
677 677 </li>
678 678 % endfor
679 679
680 680 <li class="logout">
681 681 ${h.secure_form(h.route_path('logout'), request=request)}
682 682 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
683 683 ${h.end_form()}
684 684 </li>
685 685 </ol>
686 686 </div>
687 687 %endif
688 688 </div>
689 689 </div>
690 690
691 691 % endif
692 692 </li>
693 693 </%def>
694 694
695 695 <%def name="menu_items(active=None)">
696 696 <%
697 697 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
698 698 notice_display = 'none' if len(notice_messages) == 0 else ''
699 699 %>
700 700 <style>
701 701
702 702 </style>
703 703
704 704 <ul id="quick" class="main_nav navigation horizontal-list">
705 705 ## notice box for important system messages
706 706 <li style="display: ${notice_display}">
707 707 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
708 708 <div class="menulabel-notice ${notice_level}" >
709 709 ${len(notice_messages)}
710 710 </div>
711 711 </a>
712 712 </li>
713 713 <div class="notice-messages-container" style="display: none">
714 714 <div class="notice-messages">
715 715 <table class="rctable">
716 716 % for notice in notice_messages:
717 717 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
718 718 <td style="vertical-align: text-top; width: 20px">
719 719 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
720 720 </td>
721 721 <td>
722 722 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
723 723 ${notice['subject']}
724 724
725 725 <div id="notice-${notice['msg_id']}" style="display: none">
726 726 ${h.render(notice['body'], renderer='markdown')}
727 727 </div>
728 728 </td>
729 729 <td style="vertical-align: text-top; width: 35px;">
730 730 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
731 731 <i class="icon-remove icon-filled-red"></i>
732 732 </a>
733 733 </td>
734 734 </tr>
735 735
736 736 % endfor
737 737 </table>
738 738 </div>
739 739 </div>
740 740 ## Main filter
741 741 <li>
742 742 <div class="menulabel main_filter_box">
743 743 <div class="main_filter_input_box">
744 744 <ul class="searchItems">
745 745
746 746 <li class="searchTag searchTagIcon">
747 747 <i class="icon-search"></i>
748 748 </li>
749 749
750 750 % if c.template_context['search_context']['repo_id']:
751 751 <li class="searchTag searchTagFilter searchTagHidable" >
752 752 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
753 753 <span class="tag">
754 754 This repo
755 755 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
756 756 </span>
757 757 ##</a>
758 758 </li>
759 759 % elif c.template_context['search_context']['repo_group_id']:
760 760 <li class="searchTag searchTagFilter searchTagHidable">
761 761 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
762 762 <span class="tag">
763 763 This group
764 764 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
765 765 </span>
766 766 ##</a>
767 767 </li>
768 768 % endif
769 769
770 770 <li class="searchTagInput">
771 771 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
772 772 </li>
773 773 <li class="searchTag searchTagHelp">
774 774 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
775 775 </li>
776 776 </ul>
777 777 </div>
778 778 </div>
779 779
780 780 <div id="main_filter_help" style="display: none">
781 781 - Use '/' key to quickly access this field.
782 782
783 783 - Enter a name of repository, or repository group for quick search.
784 784
785 785 - Prefix query to allow special search:
786 786
787 787 user:admin, to search for usernames, always global
788 788
789 789 user_group:devops, to search for user groups, always global
790 790
791 791 pr:303, to search for pull request number, title, or description, always global
792 792
793 793 commit:efced4, to search for commits, scoped to repositories or groups
794 794
795 795 file:models.py, to search for file paths, scoped to repositories or groups
796 796
797 797 % if c.template_context['search_context']['repo_id']:
798 798 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
799 799 % elif c.template_context['search_context']['repo_group_id']:
800 800 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
801 801 % else:
802 802 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
803 803 % endif
804 804 </div>
805 805 </li>
806 806
807 807 ## ROOT MENU
808 808 <li class="${h.is_active('home', active)}">
809 809 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
810 810 <div class="menulabel">${_('Home')}</div>
811 811 </a>
812 812 </li>
813 813
814 814 %if c.rhodecode_user.username != h.DEFAULT_USER:
815 815 <li class="${h.is_active('journal', active)}">
816 816 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
817 817 <div class="menulabel">${_('Journal')}</div>
818 818 </a>
819 819 </li>
820 820 %else:
821 821 <li class="${h.is_active('journal', active)}">
822 822 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
823 823 <div class="menulabel">${_('Public journal')}</div>
824 824 </a>
825 825 </li>
826 826 %endif
827 827
828 828 <li class="${h.is_active('gists', active)}">
829 829 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
830 830 <div class="menulabel">${_('Gists')}</div>
831 831 </a>
832 832 </li>
833 833
834 834 % if c.is_super_admin or c.is_delegated_admin:
835 835 <li class="${h.is_active('admin', active)}">
836 836 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
837 837 <div class="menulabel">${_('Admin')} </div>
838 838 </a>
839 839 </li>
840 840 % endif
841 841
842 842 ## render extra user menu
843 843 ${usermenu(active=(active=='my_account'))}
844 844
845 845 </ul>
846 846
847 847 <script type="text/javascript">
848 848 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
849 849
850 850 var formatRepoResult = function(result, container, query, escapeMarkup) {
851 851 return function(data, escapeMarkup) {
852 852 if (!data.repo_id){
853 853 return data.text; // optgroup text Repositories
854 854 }
855 855
856 856 var tmpl = '';
857 857 var repoType = data['repo_type'];
858 858 var repoName = data['text'];
859 859
860 860 if(data && data.type == 'repo'){
861 861 if(repoType === 'hg'){
862 862 tmpl += '<i class="icon-hg"></i> ';
863 863 }
864 864 else if(repoType === 'git'){
865 865 tmpl += '<i class="icon-git"></i> ';
866 866 }
867 867 else if(repoType === 'svn'){
868 868 tmpl += '<i class="icon-svn"></i> ';
869 869 }
870 870 if(data['private']){
871 871 tmpl += '<i class="icon-lock" ></i> ';
872 872 }
873 873 else if(visualShowPublicIcon){
874 874 tmpl += '<i class="icon-unlock-alt"></i> ';
875 875 }
876 876 }
877 877 tmpl += escapeMarkup(repoName);
878 878 return tmpl;
879 879
880 880 }(result, escapeMarkup);
881 881 };
882 882
883 883 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
884 884 return function(data, escapeMarkup) {
885 885 if (!data.repo_group_id){
886 886 return data.text; // optgroup text Repositories
887 887 }
888 888
889 889 var tmpl = '';
890 890 var repoGroupName = data['text'];
891 891
892 892 if(data){
893 893
894 894 tmpl += '<i class="icon-repo-group"></i> ';
895 895
896 896 }
897 897 tmpl += escapeMarkup(repoGroupName);
898 898 return tmpl;
899 899
900 900 }(result, escapeMarkup);
901 901 };
902 902
903 903 var escapeRegExChars = function (value) {
904 904 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
905 905 };
906 906
907 907 var getRepoIcon = function(repo_type) {
908 908 if (repo_type === 'hg') {
909 909 return '<i class="icon-hg"></i> ';
910 910 }
911 911 else if (repo_type === 'git') {
912 912 return '<i class="icon-git"></i> ';
913 913 }
914 914 else if (repo_type === 'svn') {
915 915 return '<i class="icon-svn"></i> ';
916 916 }
917 917 return ''
918 918 };
919 919
920 920 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
921 921
922 922 if (value.split(':').length === 2) {
923 923 value = value.split(':')[1]
924 924 }
925 925
926 926 var searchType = data['type'];
927 927 var searchSubType = data['subtype'];
928 928 var valueDisplay = data['value_display'];
929 929 var valueIcon = data['value_icon'];
930 930
931 931 var pattern = '(' + escapeRegExChars(value) + ')';
932 932
933 933 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
934 934
935 935 // highlight match
936 936 if (searchType != 'text') {
937 937 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
938 938 }
939 939
940 940 var icon = '';
941 941
942 942 if (searchType === 'hint') {
943 943 icon += '<i class="icon-repo-group"></i> ';
944 944 }
945 945 // full text search/hints
946 946 else if (searchType === 'search') {
947 947 if (valueIcon === undefined) {
948 948 icon += '<i class="icon-more"></i> ';
949 949 } else {
950 950 icon += valueIcon + ' ';
951 951 }
952 952
953 953 if (searchSubType !== undefined && searchSubType == 'repo') {
954 954 valueDisplay += '<div class="pull-right tag">repository</div>';
955 955 }
956 956 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
957 957 valueDisplay += '<div class="pull-right tag">repo group</div>';
958 958 }
959 959 }
960 960 // repository
961 961 else if (searchType === 'repo') {
962 962
963 963 var repoIcon = getRepoIcon(data['repo_type']);
964 964 icon += repoIcon;
965 965
966 966 if (data['private']) {
967 967 icon += '<i class="icon-lock" ></i> ';
968 968 }
969 969 else if (visualShowPublicIcon) {
970 970 icon += '<i class="icon-unlock-alt"></i> ';
971 971 }
972 972 }
973 973 // repository groups
974 974 else if (searchType === 'repo_group') {
975 975 icon += '<i class="icon-repo-group"></i> ';
976 976 }
977 977 // user group
978 978 else if (searchType === 'user_group') {
979 979 icon += '<i class="icon-group"></i> ';
980 980 }
981 981 // user
982 982 else if (searchType === 'user') {
983 983 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
984 984 }
985 985 // pull request
986 986 else if (searchType === 'pull_request') {
987 987 icon += '<i class="icon-merge"></i> ';
988 988 }
989 989 // commit
990 990 else if (searchType === 'commit') {
991 991 var repo_data = data['repo_data'];
992 992 var repoIcon = getRepoIcon(repo_data['repository_type']);
993 993 if (repoIcon) {
994 994 icon += repoIcon;
995 995 } else {
996 996 icon += '<i class="icon-tag"></i>';
997 997 }
998 998 }
999 999 // file
1000 1000 else if (searchType === 'file') {
1001 1001 var repo_data = data['repo_data'];
1002 1002 var repoIcon = getRepoIcon(repo_data['repository_type']);
1003 1003 if (repoIcon) {
1004 1004 icon += repoIcon;
1005 1005 } else {
1006 1006 icon += '<i class="icon-tag"></i>';
1007 1007 }
1008 1008 }
1009 1009 // generic text
1010 1010 else if (searchType === 'text') {
1011 1011 icon = '';
1012 1012 }
1013 1013
1014 1014 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1015 1015 return tmpl.format(icon, valueDisplay);
1016 1016 };
1017 1017
1018 1018 var handleSelect = function(element, suggestion) {
1019 1019 if (suggestion.type === "hint") {
1020 1020 // we skip action
1021 1021 $('#main_filter').focus();
1022 1022 }
1023 1023 else if (suggestion.type === "text") {
1024 1024 // we skip action
1025 1025 $('#main_filter').focus();
1026 1026
1027 1027 } else {
1028 1028 window.location = suggestion['url'];
1029 1029 }
1030 1030 };
1031 1031
1032 1032 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1033 1033 if (queryLowerCase.split(':').length === 2) {
1034 1034 queryLowerCase = queryLowerCase.split(':')[1]
1035 1035 }
1036 1036 if (suggestion.type === "text") {
1037 1037 // special case we don't want to "skip" display for
1038 1038 return true
1039 1039 }
1040 1040 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1041 1041 };
1042 1042
1043 1043 var cleanContext = {
1044 1044 repo_view_type: null,
1045 1045
1046 1046 repo_id: null,
1047 1047 repo_name: "",
1048 1048
1049 1049 repo_group_id: null,
1050 1050 repo_group_name: null
1051 1051 };
1052 1052 var removeGoToFilter = function () {
1053 1053 $('.searchTagHidable').hide();
1054 1054 $('#main_filter').autocomplete(
1055 1055 'setOptions', {params:{search_context: cleanContext}});
1056 1056 };
1057 1057
1058 1058 $('#main_filter').autocomplete({
1059 1059 serviceUrl: pyroutes.url('goto_switcher_data'),
1060 1060 params: {
1061 1061 "search_context": templateContext.search_context
1062 1062 },
1063 1063 minChars:2,
1064 1064 maxHeight:400,
1065 1065 deferRequestBy: 300, //miliseconds
1066 1066 tabDisabled: true,
1067 1067 autoSelectFirst: false,
1068 1068 containerClass: 'autocomplete-qfilter-suggestions',
1069 1069 formatResult: autocompleteMainFilterFormatResult,
1070 1070 lookupFilter: autocompleteMainFilterResult,
1071 1071 onSelect: function (element, suggestion) {
1072 1072 handleSelect(element, suggestion);
1073 1073 return false;
1074 1074 },
1075 1075 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1076 1076 if (jqXHR !== 'abort') {
1077 1077 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
1078 1078 SwalNoAnimation.fire({
1079 1079 icon: 'error',
1080 1080 title: _gettext('Error during search operation'),
1081 1081 html: '<span style="white-space: pre-line">{0}</span>'.format(message),
1082 1082 }).then(function(result) {
1083 1083 window.location.reload();
1084 1084 })
1085 1085 }
1086 1086 },
1087 1087 onSearchStart: function (params) {
1088 1088 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1089 1089 },
1090 1090 onSearchComplete: function (query, suggestions) {
1091 1091 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1092 1092 },
1093 1093 });
1094 1094
1095 1095 showMainFilterBox = function () {
1096 1096 $('#main_filter_help').toggle();
1097 1097 };
1098 1098
1099 1099 $('#main_filter').on('keydown.autocomplete', function (e) {
1100 1100
1101 1101 var BACKSPACE = 8;
1102 1102 var el = $(e.currentTarget);
1103 1103 if(e.which === BACKSPACE){
1104 1104 var inputVal = el.val();
1105 1105 if (inputVal === ""){
1106 1106 removeGoToFilter()
1107 1107 }
1108 1108 }
1109 1109 });
1110 1110
1111 1111 var dismissNotice = function(noticeId) {
1112 1112
1113 1113 var url = pyroutes.url('user_notice_dismiss',
1114 1114 {"user_id": templateContext.rhodecode_user.user_id});
1115 1115
1116 1116 var postData = {
1117 1117 'csrf_token': CSRF_TOKEN,
1118 1118 'notice_id': noticeId,
1119 1119 };
1120 1120
1121 1121 var success = function(response) {
1122 1122 $('#notice-message-' + noticeId).remove();
1123 1123 return false;
1124 1124 };
1125 1125 var failure = function(data, textStatus, xhr) {
1126 1126 alert("error processing request: " + textStatus);
1127 1127 return false;
1128 1128 };
1129 1129 ajaxPOST(url, postData, success, failure);
1130 1130 }
1131 1131 </script>
1132 1132 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1133 1133 </%def>
1134 1134
1135 1135 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1136 1136 <div class="modal-dialog">
1137 1137 <div class="modal-content">
1138 1138 <div class="modal-header">
1139 1139 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1140 1140 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1141 1141 </div>
1142 1142 <div class="modal-body">
1143 1143 <div class="block-left">
1144 1144 <table class="keyboard-mappings">
1145 1145 <tbody>
1146 1146 <tr>
1147 1147 <th></th>
1148 1148 <th>${_('Site-wide shortcuts')}</th>
1149 1149 </tr>
1150 1150 <%
1151 1151 elems = [
1152 1152 ('/', 'Use quick search box'),
1153 1153 ('g h', 'Goto home page'),
1154 1154 ('g g', 'Goto my private gists page'),
1155 1155 ('g G', 'Goto my public gists page'),
1156 1156 ('g 0-9', 'Goto bookmarked items from 0-9'),
1157 1157 ('n r', 'New repository page'),
1158 1158 ('n g', 'New gist page'),
1159 1159 ]
1160 1160 %>
1161 1161 %for key, desc in elems:
1162 1162 <tr>
1163 1163 <td class="keys">
1164 1164 <span class="key tag">${key}</span>
1165 1165 </td>
1166 1166 <td>${desc}</td>
1167 1167 </tr>
1168 1168 %endfor
1169 1169 </tbody>
1170 1170 </table>
1171 1171 </div>
1172 1172 <div class="block-left">
1173 1173 <table class="keyboard-mappings">
1174 1174 <tbody>
1175 1175 <tr>
1176 1176 <th></th>
1177 1177 <th>${_('Repositories')}</th>
1178 1178 </tr>
1179 1179 <%
1180 1180 elems = [
1181 1181 ('g s', 'Goto summary page'),
1182 1182 ('g c', 'Goto changelog page'),
1183 1183 ('g f', 'Goto files page'),
1184 1184 ('g F', 'Goto files page with file search activated'),
1185 1185 ('g p', 'Goto pull requests page'),
1186 1186 ('g o', 'Goto repository settings'),
1187 1187 ('g O', 'Goto repository access permissions settings'),
1188 1188 ]
1189 1189 %>
1190 1190 %for key, desc in elems:
1191 1191 <tr>
1192 1192 <td class="keys">
1193 1193 <span class="key tag">${key}</span>
1194 1194 </td>
1195 1195 <td>${desc}</td>
1196 1196 </tr>
1197 1197 %endfor
1198 1198 </tbody>
1199 1199 </table>
1200 1200 </div>
1201 1201 </div>
1202 1202 <div class="modal-footer">
1203 1203 </div>
1204 1204 </div><!-- /.modal-content -->
1205 1205 </div><!-- /.modal-dialog -->
1206 1206 </div><!-- /.modal -->
@@ -1,632 +1,632 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 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.auth_token import AuthTokenModel
30 30 from rhodecode.model.db import Session, User
31 31 from rhodecode.model.repo import RepoModel
32 32 from rhodecode.model.user import UserModel
33 33 from rhodecode.model.user_group import UserGroupModel
34 34
35 35
36 36 def test_perm_origin_dict():
37 37 pod = auth.PermOriginDict()
38 38 pod['thing'] = 'read', 'default', 1
39 39 assert pod['thing'] == 'read'
40 40
41 41 assert pod.perm_origin_stack == {
42 42 'thing': [('read', 'default', 1)]}
43 43
44 44 pod['thing'] = 'write', 'admin', 1
45 45 assert pod['thing'] == 'write'
46 46
47 47 assert pod.perm_origin_stack == {
48 48 'thing': [('read', 'default', 1), ('write', 'admin', 1)]}
49 49
50 50 pod['other'] = 'write', 'default', 8
51 51
52 52 assert pod.perm_origin_stack == {
53 53 'other': [('write', 'default', 8)],
54 54 'thing': [('read', 'default', 1), ('write', 'admin', 1)]}
55 55
56 56 pod['other'] = 'none', 'override', 8
57 57
58 58 assert pod.perm_origin_stack == {
59 59 'other': [('write', 'default', 8), ('none', 'override', 8)],
60 60 'thing': [('read', 'default', 1), ('write', 'admin', 1)]}
61 61
62 62 with pytest.raises(ValueError):
63 63 pod['thing'] = 'read'
64 64
65 65
66 66 def test_cached_perms_data(user_regular, backend_random):
67 67 permissions = get_permissions(user_regular)
68 68 repo_name = backend_random.repo.repo_name
69 69 expected_global_permissions = {
70 70 'repository.read', 'group.read', 'usergroup.read'}
71 71 assert expected_global_permissions.issubset(permissions['global'])
72 72 assert permissions['repositories'][repo_name] == 'repository.read'
73 73
74 74
75 75 def test_cached_perms_data_with_admin_user(user_regular, backend_random):
76 76 permissions = get_permissions(user_regular, user_is_admin=True)
77 77 repo_name = backend_random.repo.repo_name
78 78 assert 'hg.admin' in permissions['global']
79 79 assert permissions['repositories'][repo_name] == 'repository.admin'
80 80
81 81
82 82 def test_cached_perms_data_with_admin_user_extended_calculation(user_regular, backend_random):
83 83 permissions = get_permissions(user_regular, user_is_admin=True,
84 84 calculate_super_admin=True)
85 85 repo_name = backend_random.repo.repo_name
86 86 assert 'hg.admin' in permissions['global']
87 87 assert permissions['repositories'][repo_name] == 'repository.admin'
88 88
89 89
90 90 def test_cached_perms_data_user_group_global_permissions(user_util):
91 91 user, user_group = user_util.create_user_with_group()
92 92 user_group.inherit_default_permissions = False
93 93
94 94 granted_permission = 'repository.write'
95 95 UserGroupModel().grant_perm(user_group, granted_permission)
96 96 Session().commit()
97 97
98 98 permissions = get_permissions(user)
99 99 assert granted_permission in permissions['global']
100 100
101 101
102 102 @pytest.mark.xfail(reason="Not implemented, see TODO note")
103 103 def test_cached_perms_data_user_group_global_permissions_(user_util):
104 104 user, user_group = user_util.create_user_with_group()
105 105
106 106 granted_permission = 'repository.write'
107 107 UserGroupModel().grant_perm(user_group, granted_permission)
108 108 Session().commit()
109 109
110 110 permissions = get_permissions(user)
111 111 assert granted_permission in permissions['global']
112 112
113 113
114 114 def test_cached_perms_data_user_global_permissions(user_util):
115 115 user = user_util.create_user()
116 116 UserModel().grant_perm(user, 'repository.none')
117 117 Session().commit()
118 118
119 119 permissions = get_permissions(user, user_inherit_default_permissions=True)
120 120 assert 'repository.read' in permissions['global']
121 121
122 122
123 123 def test_cached_perms_data_repository_permissions_on_private_repository(
124 124 backend_random, user_util):
125 125 user, user_group = user_util.create_user_with_group()
126 126
127 127 repo = backend_random.create_repo()
128 128 repo.private = True
129 129
130 130 granted_permission = 'repository.write'
131 131 RepoModel().grant_user_group_permission(
132 132 repo, user_group.users_group_name, granted_permission)
133 133 Session().commit()
134 134
135 135 permissions = get_permissions(user)
136 136 assert permissions['repositories'][repo.repo_name] == granted_permission
137 137
138 138
139 139 def test_cached_perms_data_repository_permissions_for_owner(
140 140 backend_random, user_util):
141 141 user = user_util.create_user()
142 142
143 143 repo = backend_random.create_repo()
144 144 repo.user_id = user.user_id
145 145
146 146 permissions = get_permissions(user)
147 147 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
148 148
149 149 # TODO: johbo: Make cleanup in UserUtility smarter, then remove this hack
150 150 repo.user_id = User.get_default_user_id()
151 151
152 152
153 153 def test_cached_perms_data_repository_permissions_not_inheriting_defaults(
154 154 backend_random, user_util):
155 155 user = user_util.create_user()
156 156 repo = backend_random.create_repo()
157 157
158 158 # Don't inherit default object permissions
159 159 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
160 160 Session().commit()
161 161
162 162 permissions = get_permissions(user)
163 163 assert permissions['repositories'][repo.repo_name] == 'repository.none'
164 164
165 165
166 166 def test_cached_perms_data_default_permissions_on_repository_group(user_util):
167 167 # Have a repository group with default permissions set
168 168 repo_group = user_util.create_repo_group()
169 169 default_user = User.get_default_user()
170 170 user_util.grant_user_permission_to_repo_group(
171 171 repo_group, default_user, 'repository.write')
172 172 user = user_util.create_user()
173 173
174 174 permissions = get_permissions(user)
175 175 assert permissions['repositories_groups'][repo_group.group_name] == \
176 176 'repository.write'
177 177
178 178
179 179 def test_cached_perms_data_default_permissions_on_repository_group_owner(
180 180 user_util):
181 181 # Have a repository group
182 182 repo_group = user_util.create_repo_group()
183 183 default_user = User.get_default_user()
184 184
185 185 # Add a permission for the default user to hit the code path
186 186 user_util.grant_user_permission_to_repo_group(
187 187 repo_group, default_user, 'repository.write')
188 188
189 189 # Have an owner of the group
190 190 user = user_util.create_user()
191 191 repo_group.user_id = user.user_id
192 192
193 193 permissions = get_permissions(user)
194 194 assert permissions['repositories_groups'][repo_group.group_name] == \
195 195 'group.admin'
196 196
197 197
198 198 def test_cached_perms_data_default_permissions_on_repository_group_no_inherit(
199 199 user_util):
200 200 # Have a repository group
201 201 repo_group = user_util.create_repo_group()
202 202 default_user = User.get_default_user()
203 203
204 204 # Add a permission for the default user to hit the code path
205 205 user_util.grant_user_permission_to_repo_group(
206 206 repo_group, default_user, 'repository.write')
207 207
208 208 # Don't inherit default object permissions
209 209 user = user_util.create_user()
210 210 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
211 211 Session().commit()
212 212
213 213 permissions = get_permissions(user)
214 214 assert permissions['repositories_groups'][repo_group.group_name] == \
215 215 'group.none'
216 216
217 217
218 218 def test_cached_perms_data_repository_permissions_from_user_group(
219 219 user_util, backend_random):
220 220 user, user_group = user_util.create_user_with_group()
221 221
222 222 # Needs a second user group to make sure that we select the right
223 223 # permissions.
224 224 user_group2 = user_util.create_user_group()
225 225 UserGroupModel().add_user_to_group(user_group2, user)
226 226
227 227 repo = backend_random.create_repo()
228 228
229 229 RepoModel().grant_user_group_permission(
230 230 repo, user_group.users_group_name, 'repository.read')
231 231 RepoModel().grant_user_group_permission(
232 232 repo, user_group2.users_group_name, 'repository.write')
233 233 Session().commit()
234 234
235 235 permissions = get_permissions(user)
236 236 assert permissions['repositories'][repo.repo_name] == 'repository.write'
237 237
238 238
239 239 def test_cached_perms_data_repository_permissions_from_user_group_owner(
240 240 user_util, backend_random):
241 241 user, user_group = user_util.create_user_with_group()
242 242
243 243 repo = backend_random.create_repo()
244 244 repo.user_id = user.user_id
245 245
246 246 RepoModel().grant_user_group_permission(
247 247 repo, user_group.users_group_name, 'repository.write')
248 248 Session().commit()
249 249
250 250 permissions = get_permissions(user)
251 251 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
252 252
253 253
254 254 def test_cached_perms_data_user_repository_permissions(
255 255 user_util, backend_random):
256 256 user = user_util.create_user()
257 257 repo = backend_random.create_repo()
258 258 granted_permission = 'repository.write'
259 259 RepoModel().grant_user_permission(repo, user, granted_permission)
260 260 Session().commit()
261 261
262 262 permissions = get_permissions(user)
263 263 assert permissions['repositories'][repo.repo_name] == granted_permission
264 264
265 265
266 266 def test_cached_perms_data_user_repository_permissions_explicit(
267 267 user_util, backend_random):
268 268 user = user_util.create_user()
269 269 repo = backend_random.create_repo()
270 270 granted_permission = 'repository.none'
271 271 RepoModel().grant_user_permission(repo, user, granted_permission)
272 272 Session().commit()
273 273
274 274 permissions = get_permissions(user, explicit=True)
275 275 assert permissions['repositories'][repo.repo_name] == granted_permission
276 276
277 277
278 278 def test_cached_perms_data_user_repository_permissions_owner(
279 279 user_util, backend_random):
280 280 user = user_util.create_user()
281 281 repo = backend_random.create_repo()
282 282 repo.user_id = user.user_id
283 283 RepoModel().grant_user_permission(repo, user, 'repository.write')
284 284 Session().commit()
285 285
286 286 permissions = get_permissions(user)
287 287 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
288 288
289 289
290 290 def test_cached_perms_data_repository_groups_permissions_inherited(
291 291 user_util, backend_random):
292 292 user, user_group = user_util.create_user_with_group()
293 293
294 294 # Needs a second group to hit the last condition
295 295 user_group2 = user_util.create_user_group()
296 296 UserGroupModel().add_user_to_group(user_group2, user)
297 297
298 298 repo_group = user_util.create_repo_group()
299 299
300 300 user_util.grant_user_group_permission_to_repo_group(
301 301 repo_group, user_group, 'group.read')
302 302 user_util.grant_user_group_permission_to_repo_group(
303 303 repo_group, user_group2, 'group.write')
304 304
305 305 permissions = get_permissions(user)
306 306 assert permissions['repositories_groups'][repo_group.group_name] == \
307 307 'group.write'
308 308
309 309
310 310 def test_cached_perms_data_repository_groups_permissions_inherited_owner(
311 311 user_util, backend_random):
312 312 user, user_group = user_util.create_user_with_group()
313 313 repo_group = user_util.create_repo_group()
314 314 repo_group.user_id = user.user_id
315 315
316 316 granted_permission = 'group.write'
317 317 user_util.grant_user_group_permission_to_repo_group(
318 318 repo_group, user_group, granted_permission)
319 319
320 320 permissions = get_permissions(user)
321 321 assert permissions['repositories_groups'][repo_group.group_name] == \
322 322 'group.admin'
323 323
324 324
325 325 def test_cached_perms_data_repository_groups_permissions(
326 326 user_util, backend_random):
327 327 user = user_util.create_user()
328 328
329 329 repo_group = user_util.create_repo_group()
330 330
331 331 granted_permission = 'group.write'
332 332 user_util.grant_user_permission_to_repo_group(
333 333 repo_group, user, granted_permission)
334 334
335 335 permissions = get_permissions(user)
336 336 assert permissions['repositories_groups'][repo_group.group_name] == \
337 337 'group.write'
338 338
339 339
340 340 def test_cached_perms_data_repository_groups_permissions_explicit(
341 341 user_util, backend_random):
342 342 user = user_util.create_user()
343 343
344 344 repo_group = user_util.create_repo_group()
345 345
346 346 granted_permission = 'group.none'
347 347 user_util.grant_user_permission_to_repo_group(
348 348 repo_group, user, granted_permission)
349 349
350 350 permissions = get_permissions(user, explicit=True)
351 351 assert permissions['repositories_groups'][repo_group.group_name] == \
352 352 'group.none'
353 353
354 354
355 355 def test_cached_perms_data_repository_groups_permissions_owner(
356 356 user_util, backend_random):
357 357 user = user_util.create_user()
358 358
359 359 repo_group = user_util.create_repo_group()
360 360 repo_group.user_id = user.user_id
361 361
362 362 granted_permission = 'group.write'
363 363 user_util.grant_user_permission_to_repo_group(
364 364 repo_group, user, granted_permission)
365 365
366 366 permissions = get_permissions(user)
367 367 assert permissions['repositories_groups'][repo_group.group_name] == \
368 368 'group.admin'
369 369
370 370
371 371 def test_cached_perms_data_user_group_permissions_inherited(
372 372 user_util, backend_random):
373 373 user, user_group = user_util.create_user_with_group()
374 374 user_group2 = user_util.create_user_group()
375 375 UserGroupModel().add_user_to_group(user_group2, user)
376 376
377 377 target_user_group = user_util.create_user_group()
378 378
379 379 user_util.grant_user_group_permission_to_user_group(
380 380 target_user_group, user_group, 'usergroup.read')
381 381 user_util.grant_user_group_permission_to_user_group(
382 382 target_user_group, user_group2, 'usergroup.write')
383 383
384 384 permissions = get_permissions(user)
385 385 assert permissions['user_groups'][target_user_group.users_group_name] == \
386 386 'usergroup.write'
387 387
388 388
389 389 def test_cached_perms_data_user_group_permissions(
390 390 user_util, backend_random):
391 391 user = user_util.create_user()
392 392 user_group = user_util.create_user_group()
393 393 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.write')
394 394 Session().commit()
395 395
396 396 permissions = get_permissions(user)
397 397 assert permissions['user_groups'][user_group.users_group_name] == \
398 398 'usergroup.write'
399 399
400 400
401 401 def test_cached_perms_data_user_group_permissions_explicit(
402 402 user_util, backend_random):
403 403 user = user_util.create_user()
404 404 user_group = user_util.create_user_group()
405 405 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.none')
406 406 Session().commit()
407 407
408 408 permissions = get_permissions(user, explicit=True)
409 409 assert permissions['user_groups'][user_group.users_group_name] == \
410 410 'usergroup.none'
411 411
412 412
413 413 def test_cached_perms_data_user_group_permissions_not_inheriting_defaults(
414 414 user_util, backend_random):
415 415 user = user_util.create_user()
416 416 user_group = user_util.create_user_group()
417 417
418 418 # Don't inherit default object permissions
419 419 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
420 420 Session().commit()
421 421
422 422 permissions = get_permissions(user)
423 423 assert permissions['user_groups'][user_group.users_group_name] == \
424 424 'usergroup.none'
425 425
426 426
427 427 def test_permission_calculator_admin_permissions(
428 428 user_util, backend_random):
429 429 user = user_util.create_user()
430 430 user_group = user_util.create_user_group()
431 431 repo = backend_random.repo
432 432 repo_group = user_util.create_repo_group()
433 433
434 434 calculator = auth.PermissionCalculator(
435 435 user.user_id, {}, False, False, True, 'higherwin')
436 permissions = calculator._calculate_admin_permissions()
436 permissions = calculator._calculate_super_admin_permissions()
437 437
438 438 assert permissions['repositories_groups'][repo_group.group_name] == \
439 439 'group.admin'
440 440 assert permissions['user_groups'][user_group.users_group_name] == \
441 441 'usergroup.admin'
442 442 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
443 443 assert 'hg.admin' in permissions['global']
444 444
445 445
446 446 def test_permission_calculator_repository_permissions_robustness_from_group(
447 447 user_util, backend_random):
448 448 user, user_group = user_util.create_user_with_group()
449 449
450 450 RepoModel().grant_user_group_permission(
451 451 backend_random.repo, user_group.users_group_name, 'repository.write')
452 452
453 453 calculator = auth.PermissionCalculator(
454 454 user.user_id, {}, False, False, False, 'higherwin')
455 455 calculator._calculate_repository_permissions()
456 456
457 457
458 458 def test_permission_calculator_repository_permissions_robustness_from_user(
459 459 user_util, backend_random):
460 460 user = user_util.create_user()
461 461
462 462 RepoModel().grant_user_permission(
463 463 backend_random.repo, user, 'repository.write')
464 464 Session().commit()
465 465
466 466 calculator = auth.PermissionCalculator(
467 467 user.user_id, {}, False, False, False, 'higherwin')
468 468 calculator._calculate_repository_permissions()
469 469
470 470
471 471 def test_permission_calculator_repo_group_permissions_robustness_from_group(
472 472 user_util, backend_random):
473 473 user, user_group = user_util.create_user_with_group()
474 474 repo_group = user_util.create_repo_group()
475 475
476 476 user_util.grant_user_group_permission_to_repo_group(
477 477 repo_group, user_group, 'group.write')
478 478
479 479 calculator = auth.PermissionCalculator(
480 480 user.user_id, {}, False, False, False, 'higherwin')
481 481 calculator._calculate_repository_group_permissions()
482 482
483 483
484 484 def test_permission_calculator_repo_group_permissions_robustness_from_user(
485 485 user_util, backend_random):
486 486 user = user_util.create_user()
487 487 repo_group = user_util.create_repo_group()
488 488
489 489 user_util.grant_user_permission_to_repo_group(
490 490 repo_group, user, 'group.write')
491 491
492 492 calculator = auth.PermissionCalculator(
493 493 user.user_id, {}, False, False, False, 'higherwin')
494 494 calculator._calculate_repository_group_permissions()
495 495
496 496
497 497 def test_permission_calculator_user_group_permissions_robustness_from_group(
498 498 user_util, backend_random):
499 499 user, user_group = user_util.create_user_with_group()
500 500 target_user_group = user_util.create_user_group()
501 501
502 502 user_util.grant_user_group_permission_to_user_group(
503 503 target_user_group, user_group, 'usergroup.write')
504 504
505 505 calculator = auth.PermissionCalculator(
506 506 user.user_id, {}, False, False, False, 'higherwin')
507 507 calculator._calculate_user_group_permissions()
508 508
509 509
510 510 def test_permission_calculator_user_group_permissions_robustness_from_user(
511 511 user_util, backend_random):
512 512 user = user_util.create_user()
513 513 target_user_group = user_util.create_user_group()
514 514
515 515 user_util.grant_user_permission_to_user_group(
516 516 target_user_group, user, 'usergroup.write')
517 517
518 518 calculator = auth.PermissionCalculator(
519 519 user.user_id, {}, False, False, False, 'higherwin')
520 520 calculator._calculate_user_group_permissions()
521 521
522 522
523 523 @pytest.mark.parametrize("algo, new_permission, old_permission, expected", [
524 524 ('higherwin', 'repository.none', 'repository.none', 'repository.none'),
525 525 ('higherwin', 'repository.read', 'repository.none', 'repository.read'),
526 526 ('lowerwin', 'repository.write', 'repository.write', 'repository.write'),
527 527 ('lowerwin', 'repository.read', 'repository.write', 'repository.read'),
528 528 ])
529 529 def test_permission_calculator_choose_permission(
530 530 user_regular, algo, new_permission, old_permission, expected):
531 531 calculator = auth.PermissionCalculator(
532 532 user_regular.user_id, {}, False, False, False, algo)
533 533 result = calculator._choose_permission(new_permission, old_permission)
534 534 assert result == expected
535 535
536 536
537 537 def test_permission_calculator_choose_permission_raises_on_wrong_algo(
538 538 user_regular):
539 539 calculator = auth.PermissionCalculator(
540 540 user_regular.user_id, {}, False, False, False, 'invalid')
541 541 result = calculator._choose_permission(
542 542 'repository.read', 'repository.read')
543 543 # TODO: johbo: This documents the existing behavior. Think of an
544 544 # improvement.
545 545 assert result is None
546 546
547 547
548 548 def test_auth_user_get_cookie_store_for_normal_user(user_util):
549 549 user = user_util.create_user()
550 550 auth_user = auth.AuthUser(user_id=user.user_id)
551 551 expected_data = {
552 552 'username': user.username,
553 553 'user_id': user.user_id,
554 554 'password': md5(user.password),
555 555 'is_authenticated': False
556 556 }
557 557 assert auth_user.get_cookie_store() == expected_data
558 558
559 559
560 560 def test_auth_user_get_cookie_store_for_default_user():
561 561 default_user = User.get_default_user()
562 562 auth_user = auth.AuthUser()
563 563 expected_data = {
564 564 'username': User.DEFAULT_USER,
565 565 'user_id': default_user.user_id,
566 566 'password': md5(default_user.password),
567 567 'is_authenticated': True
568 568 }
569 569 assert auth_user.get_cookie_store() == expected_data
570 570
571 571
572 572 def get_permissions(user, **kwargs):
573 573 """
574 574 Utility filling in useful defaults into the call to `_cached_perms_data`.
575 575
576 576 Fill in `**kwargs` if specific values are needed for a test.
577 577 """
578 578 call_args = {
579 579 'user_id': user.user_id,
580 580 'scope': {},
581 581 'user_is_admin': False,
582 582 'user_inherit_default_permissions': False,
583 583 'explicit': False,
584 584 'algo': 'higherwin',
585 585 'calculate_super_admin': False,
586 586 }
587 587 call_args.update(kwargs)
588 588 permissions = auth._cached_perms_data(**call_args)
589 589 return permissions
590 590
591 591
592 592 class TestGenerateAuthToken(object):
593 593 def test_salt_is_used_when_specified(self):
594 594 salt = 'abcde'
595 595 user_name = 'test_user'
596 596 result = auth.generate_auth_token(user_name, salt)
597 597 expected_result = sha1(user_name + salt).hexdigest()
598 598 assert result == expected_result
599 599
600 600 def test_salt_is_geneated_when_not_specified(self):
601 601 user_name = 'test_user'
602 602 random_salt = os.urandom(16)
603 603 with patch.object(auth, 'os') as os_mock:
604 604 os_mock.urandom.return_value = random_salt
605 605 result = auth.generate_auth_token(user_name)
606 606 expected_result = sha1(user_name + random_salt).hexdigest()
607 607 assert result == expected_result
608 608
609 609
610 610 @pytest.mark.parametrize("test_token, test_roles, auth_result, expected_tokens", [
611 611 ('', None, False,
612 612 []),
613 613 ('wrongtoken', None, False,
614 614 []),
615 615 ('abracadabra_vcs', [AuthTokenModel.cls.ROLE_API], False,
616 616 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
617 617 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
618 618 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
619 619 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
620 620 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1),
621 621 ('abracadabra_http', AuthTokenModel.cls.ROLE_HTTP, -1)]),
622 622 ])
623 623 def test_auth_by_token(test_token, test_roles, auth_result, expected_tokens,
624 624 user_util):
625 625 user = user_util.create_user()
626 626 user_id = user.user_id
627 627 for token, role, expires in expected_tokens:
628 628 new_token = AuthTokenModel().create(user_id, u'test-token', expires, role)
629 629 new_token.api_key = token # inject known name for testing...
630 630
631 631 assert auth_result == user.authenticate_by_token(
632 632 test_token, roles=test_roles)
General Comments 0
You need to be logged in to leave comments. Login now