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