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