##// END OF EJS Templates
auth: Change login url generation in auth decorators....
johbo -
r35:3c60da43 default
parent child Browse files
Show More
@@ -1,1826 +1,1828 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 868 def is_default(self):
869 869 return self.username == User.DEFAULT_USER
870 870
871 871 @property
872 872 def is_admin(self):
873 873 return self.admin
874 874
875 875 @property
876 876 def is_user_object(self):
877 877 return self.user_id is not None
878 878
879 879 @property
880 880 def repositories_admin(self):
881 881 """
882 882 Returns list of repositories you're an admin of
883 883 """
884 884 return [x[0] for x in self.permissions['repositories'].iteritems()
885 885 if x[1] == 'repository.admin']
886 886
887 887 @property
888 888 def repository_groups_admin(self):
889 889 """
890 890 Returns list of repository groups you're an admin of
891 891 """
892 892 return [x[0]
893 893 for x in self.permissions['repositories_groups'].iteritems()
894 894 if x[1] == 'group.admin']
895 895
896 896 @property
897 897 def user_groups_admin(self):
898 898 """
899 899 Returns list of user groups you're an admin of
900 900 """
901 901 return [x[0] for x in self.permissions['user_groups'].iteritems()
902 902 if x[1] == 'usergroup.admin']
903 903
904 904 @property
905 905 def ip_allowed(self):
906 906 """
907 907 Checks if ip_addr used in constructor is allowed from defined list of
908 908 allowed ip_addresses for user
909 909
910 910 :returns: boolean, True if ip is in allowed ip range
911 911 """
912 912 # check IP
913 913 inherit = self.inherit_default_permissions
914 914 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
915 915 inherit_from_default=inherit)
916 916
917 917 @classmethod
918 918 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
919 919 allowed_ips = AuthUser.get_allowed_ips(
920 920 user_id, cache=True, inherit_from_default=inherit_from_default)
921 921 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
922 922 log.debug('IP:%s is in range of %s' % (ip_addr, allowed_ips))
923 923 return True
924 924 else:
925 925 log.info('Access for IP:%s forbidden, '
926 926 'not in %s' % (ip_addr, allowed_ips))
927 927 return False
928 928
929 929 def __repr__(self):
930 930 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
931 931 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
932 932
933 933 def set_authenticated(self, authenticated=True):
934 934 if self.user_id != self.anonymous_user.user_id:
935 935 self.is_authenticated = authenticated
936 936
937 937 def get_cookie_store(self):
938 938 return {
939 939 'username': self.username,
940 940 'password': md5(self.password),
941 941 'user_id': self.user_id,
942 942 'is_authenticated': self.is_authenticated
943 943 }
944 944
945 945 @classmethod
946 946 def from_cookie_store(cls, cookie_store):
947 947 """
948 948 Creates AuthUser from a cookie store
949 949
950 950 :param cls:
951 951 :param cookie_store:
952 952 """
953 953 user_id = cookie_store.get('user_id')
954 954 username = cookie_store.get('username')
955 955 api_key = cookie_store.get('api_key')
956 956 return AuthUser(user_id, api_key, username)
957 957
958 958 @classmethod
959 959 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
960 960 _set = set()
961 961
962 962 if inherit_from_default:
963 963 default_ips = UserIpMap.query().filter(
964 964 UserIpMap.user == User.get_default_user(cache=True))
965 965 if cache:
966 966 default_ips = default_ips.options(FromCache("sql_cache_short",
967 967 "get_user_ips_default"))
968 968
969 969 # populate from default user
970 970 for ip in default_ips:
971 971 try:
972 972 _set.add(ip.ip_addr)
973 973 except ObjectDeletedError:
974 974 # since we use heavy caching sometimes it happens that
975 975 # we get deleted objects here, we just skip them
976 976 pass
977 977
978 978 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
979 979 if cache:
980 980 user_ips = user_ips.options(FromCache("sql_cache_short",
981 981 "get_user_ips_%s" % user_id))
982 982
983 983 for ip in user_ips:
984 984 try:
985 985 _set.add(ip.ip_addr)
986 986 except ObjectDeletedError:
987 987 # since we use heavy caching sometimes it happens that we get
988 988 # deleted objects here, we just skip them
989 989 pass
990 990 return _set or set(['0.0.0.0/0', '::/0'])
991 991
992 992
993 993 def set_available_permissions(config):
994 994 """
995 995 This function will propagate pylons globals with all available defined
996 996 permission given in db. We don't want to check each time from db for new
997 997 permissions since adding a new permission also requires application restart
998 998 ie. to decorate new views with the newly created permission
999 999
1000 1000 :param config: current pylons config instance
1001 1001
1002 1002 """
1003 1003 log.info('getting information about all available permissions')
1004 1004 try:
1005 1005 sa = meta.Session
1006 1006 all_perms = sa.query(Permission).all()
1007 1007 config['available_permissions'] = [x.permission_name for x in all_perms]
1008 1008 except Exception:
1009 1009 log.error(traceback.format_exc())
1010 1010 finally:
1011 1011 meta.Session.remove()
1012 1012
1013 1013
1014 1014 def get_csrf_token(session=None, force_new=False, save_if_missing=True):
1015 1015 """
1016 1016 Return the current authentication token, creating one if one doesn't
1017 1017 already exist and the save_if_missing flag is present.
1018 1018
1019 1019 :param session: pass in the pylons session, else we use the global ones
1020 1020 :param force_new: force to re-generate the token and store it in session
1021 1021 :param save_if_missing: save the newly generated token if it's missing in
1022 1022 session
1023 1023 """
1024 1024 if not session:
1025 1025 from pylons import session
1026 1026
1027 1027 if (csrf_token_key not in session and save_if_missing) or force_new:
1028 1028 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1029 1029 session[csrf_token_key] = token
1030 1030 if hasattr(session, 'save'):
1031 1031 session.save()
1032 1032 return session.get(csrf_token_key)
1033 1033
1034 1034
1035 1035 # CHECK DECORATORS
1036 1036 class CSRFRequired(object):
1037 1037 """
1038 1038 Decorator for authenticating a form
1039 1039
1040 1040 This decorator uses an authorization token stored in the client's
1041 1041 session for prevention of certain Cross-site request forgery (CSRF)
1042 1042 attacks (See
1043 1043 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1044 1044 information).
1045 1045
1046 1046 For use with the ``webhelpers.secure_form`` helper functions.
1047 1047
1048 1048 """
1049 1049 def __init__(self, token=csrf_token_key, header='X-CSRF-Token'):
1050 1050 self.token = token
1051 1051 self.header = header
1052 1052
1053 1053 def __call__(self, func):
1054 1054 return get_cython_compat_decorator(self.__wrapper, func)
1055 1055
1056 1056 def _get_csrf(self, _request):
1057 1057 return _request.POST.get(self.token, _request.headers.get(self.header))
1058 1058
1059 1059 def check_csrf(self, _request, cur_token):
1060 1060 supplied_token = self._get_csrf(_request)
1061 1061 return supplied_token and supplied_token == cur_token
1062 1062
1063 1063 def __wrapper(self, func, *fargs, **fkwargs):
1064 1064 cur_token = get_csrf_token(save_if_missing=False)
1065 1065 if self.check_csrf(request, cur_token):
1066 1066 if request.POST.get(self.token):
1067 1067 del request.POST[self.token]
1068 1068 return func(*fargs, **fkwargs)
1069 1069 else:
1070 1070 reason = 'token-missing'
1071 1071 supplied_token = self._get_csrf(request)
1072 1072 if supplied_token and cur_token != supplied_token:
1073 1073 reason = 'token-mismatch [%s:%s]' % (cur_token or ''[:6],
1074 1074 supplied_token or ''[:6])
1075 1075
1076 1076 csrf_message = \
1077 1077 ("Cross-site request forgery detected, request denied. See "
1078 1078 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1079 1079 "more information.")
1080 1080 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1081 1081 'REMOTE_ADDR:%s, HEADERS:%s' % (
1082 1082 request, reason, request.remote_addr, request.headers))
1083 1083
1084 1084 abort(403, detail=csrf_message)
1085 1085
1086 1086
1087 1087 class LoginRequired(object):
1088 1088 """
1089 1089 Must be logged in to execute this function else
1090 1090 redirect to login page
1091 1091
1092 1092 :param api_access: if enabled this checks only for valid auth token
1093 1093 and grants access based on valid token
1094 1094 """
1095 1095 def __init__(self, auth_token_access=False):
1096 1096 self.auth_token_access = auth_token_access
1097 1097
1098 1098 def __call__(self, func):
1099 1099 return get_cython_compat_decorator(self.__wrapper, func)
1100 1100
1101 1101 def __wrapper(self, func, *fargs, **fkwargs):
1102 from rhodecode.lib import helpers as h
1102 1103 cls = fargs[0]
1103 1104 user = cls._rhodecode_user
1104 1105 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1105 1106 log.debug('Starting login restriction checks for user: %s' % (user,))
1106 1107 # check if our IP is allowed
1107 1108 ip_access_valid = True
1108 1109 if not user.ip_allowed:
1109 from rhodecode.lib import helpers as h
1110 1110 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1111 1111 category='warning')
1112 1112 ip_access_valid = False
1113 1113
1114 1114 # check if we used an APIKEY and it's a valid one
1115 1115 # defined whitelist of controllers which API access will be enabled
1116 1116 _auth_token = request.GET.get(
1117 1117 'auth_token', '') or request.GET.get('api_key', '')
1118 1118 auth_token_access_valid = allowed_auth_token_access(
1119 1119 loc, auth_token=_auth_token)
1120 1120
1121 1121 # explicit controller is enabled or API is in our whitelist
1122 1122 if self.auth_token_access or auth_token_access_valid:
1123 1123 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1124 1124
1125 1125 if _auth_token and _auth_token in user.auth_tokens:
1126 1126 auth_token_access_valid = True
1127 1127 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1128 1128 else:
1129 1129 auth_token_access_valid = False
1130 1130 if not _auth_token:
1131 1131 log.debug("AUTH TOKEN *NOT* present in request")
1132 1132 else:
1133 1133 log.warning(
1134 1134 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1135 1135
1136 1136 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1137 1137 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1138 1138 else 'AUTH_TOKEN_AUTH'
1139 1139
1140 1140 if ip_access_valid and (
1141 1141 user.is_authenticated or auth_token_access_valid):
1142 1142 log.info(
1143 1143 'user %s authenticating with:%s IS authenticated on func %s'
1144 1144 % (user, reason, loc))
1145 1145
1146 1146 # update user data to check last activity
1147 1147 user.update_lastactivity()
1148 1148 Session().commit()
1149 1149 return func(*fargs, **fkwargs)
1150 1150 else:
1151 1151 log.warning(
1152 1152 'user %s authenticating with:%s NOT authenticated on '
1153 1153 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1154 1154 % (user, reason, loc, ip_access_valid,
1155 1155 auth_token_access_valid))
1156 1156 # we preserve the get PARAM
1157 1157 came_from = request.path_qs
1158 1158
1159 1159 log.debug('redirecting to login page with %s' % (came_from,))
1160 1160 return redirect(
1161 url('login_home', came_from=came_from))
1161 h.route_path('login', _query={'came_from': came_from}))
1162 1162
1163 1163
1164 1164 class NotAnonymous(object):
1165 1165 """
1166 1166 Must be logged in to execute this function else
1167 1167 redirect to login page"""
1168 1168
1169 1169 def __call__(self, func):
1170 1170 return get_cython_compat_decorator(self.__wrapper, func)
1171 1171
1172 1172 def __wrapper(self, func, *fargs, **fkwargs):
1173 1173 cls = fargs[0]
1174 1174 self.user = cls._rhodecode_user
1175 1175
1176 1176 log.debug('Checking if user is not anonymous @%s' % cls)
1177 1177
1178 1178 anonymous = self.user.username == User.DEFAULT_USER
1179 1179
1180 1180 if anonymous:
1181 1181 came_from = request.path_qs
1182 1182
1183 1183 import rhodecode.lib.helpers as h
1184 1184 h.flash(_('You need to be a registered user to '
1185 1185 'perform this action'),
1186 1186 category='warning')
1187 return redirect(url('login_home', came_from=came_from))
1187 return redirect(
1188 h.route_path('login', _query={'came_from': came_from}))
1188 1189 else:
1189 1190 return func(*fargs, **fkwargs)
1190 1191
1191 1192
1192 1193 class XHRRequired(object):
1193 1194 def __call__(self, func):
1194 1195 return get_cython_compat_decorator(self.__wrapper, func)
1195 1196
1196 1197 def __wrapper(self, func, *fargs, **fkwargs):
1197 1198 log.debug('Checking if request is XMLHttpRequest (XHR)')
1198 1199 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1199 1200 if not request.is_xhr:
1200 1201 abort(400, detail=xhr_message)
1201 1202
1202 1203 return func(*fargs, **fkwargs)
1203 1204
1204 1205
1205 1206 class HasAcceptedRepoType(object):
1206 1207 """
1207 1208 Check if requested repo is within given repo type aliases
1208 1209
1209 1210 TODO: anderson: not sure where to put this decorator
1210 1211 """
1211 1212
1212 1213 def __init__(self, *repo_type_list):
1213 1214 self.repo_type_list = set(repo_type_list)
1214 1215
1215 1216 def __call__(self, func):
1216 1217 return get_cython_compat_decorator(self.__wrapper, func)
1217 1218
1218 1219 def __wrapper(self, func, *fargs, **fkwargs):
1219 1220 cls = fargs[0]
1220 1221 rhodecode_repo = cls.rhodecode_repo
1221 1222
1222 1223 log.debug('%s checking repo type for %s in %s',
1223 1224 self.__class__.__name__,
1224 1225 rhodecode_repo.alias, self.repo_type_list)
1225 1226
1226 1227 if rhodecode_repo.alias in self.repo_type_list:
1227 1228 return func(*fargs, **fkwargs)
1228 1229 else:
1229 1230 import rhodecode.lib.helpers as h
1230 1231 h.flash(h.literal(
1231 1232 _('Action not supported for %s.' % rhodecode_repo.alias)),
1232 1233 category='warning')
1233 1234 return redirect(
1234 1235 url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name))
1235 1236
1236 1237
1237 1238 class PermsDecorator(object):
1238 1239 """
1239 1240 Base class for controller decorators, we extract the current user from
1240 1241 the class itself, which has it stored in base controllers
1241 1242 """
1242 1243
1243 1244 def __init__(self, *required_perms):
1244 1245 self.required_perms = set(required_perms)
1245 1246
1246 1247 def __call__(self, func):
1247 1248 return get_cython_compat_decorator(self.__wrapper, func)
1248 1249
1249 1250 def __wrapper(self, func, *fargs, **fkwargs):
1250 1251 cls = fargs[0]
1251 1252 _user = cls._rhodecode_user
1252 1253
1253 1254 log.debug('checking %s permissions %s for %s %s',
1254 1255 self.__class__.__name__, self.required_perms, cls, _user)
1255 1256
1256 1257 if self.check_permissions(_user):
1257 1258 log.debug('Permission granted for %s %s', cls, _user)
1258 1259 return func(*fargs, **fkwargs)
1259 1260
1260 1261 else:
1261 1262 log.debug('Permission denied for %s %s', cls, _user)
1262 1263 anonymous = _user.username == User.DEFAULT_USER
1263 1264
1264 1265 if anonymous:
1265 1266 came_from = request.path_qs
1266 1267
1267 1268 import rhodecode.lib.helpers as h
1268 1269 h.flash(_('You need to be signed in to view this page'),
1269 1270 category='warning')
1270 return redirect(url('login_home', came_from=came_from))
1271 return redirect(
1272 h.route_path('login', _query={'came_from': came_from}))
1271 1273
1272 1274 else:
1273 1275 # redirect with forbidden ret code
1274 1276 return abort(403)
1275 1277
1276 1278 def check_permissions(self, user):
1277 1279 """Dummy function for overriding"""
1278 1280 raise NotImplementedError(
1279 1281 'You have to write this function in child class')
1280 1282
1281 1283
1282 1284 class HasPermissionAllDecorator(PermsDecorator):
1283 1285 """
1284 1286 Checks for access permission for all given predicates. All of them
1285 1287 have to be meet in order to fulfill the request
1286 1288 """
1287 1289
1288 1290 def check_permissions(self, user):
1289 1291 perms = user.permissions_with_scope({})
1290 1292 if self.required_perms.issubset(perms['global']):
1291 1293 return True
1292 1294 return False
1293 1295
1294 1296
1295 1297 class HasPermissionAnyDecorator(PermsDecorator):
1296 1298 """
1297 1299 Checks for access permission for any of given predicates. In order to
1298 1300 fulfill the request any of predicates must be meet
1299 1301 """
1300 1302
1301 1303 def check_permissions(self, user):
1302 1304 perms = user.permissions_with_scope({})
1303 1305 if self.required_perms.intersection(perms['global']):
1304 1306 return True
1305 1307 return False
1306 1308
1307 1309
1308 1310 class HasRepoPermissionAllDecorator(PermsDecorator):
1309 1311 """
1310 1312 Checks for access permission for all given predicates for specific
1311 1313 repository. All of them have to be meet in order to fulfill the request
1312 1314 """
1313 1315
1314 1316 def check_permissions(self, user):
1315 1317 perms = user.permissions
1316 1318 repo_name = get_repo_slug(request)
1317 1319 try:
1318 1320 user_perms = set([perms['repositories'][repo_name]])
1319 1321 except KeyError:
1320 1322 return False
1321 1323 if self.required_perms.issubset(user_perms):
1322 1324 return True
1323 1325 return False
1324 1326
1325 1327
1326 1328 class HasRepoPermissionAnyDecorator(PermsDecorator):
1327 1329 """
1328 1330 Checks for access permission for any of given predicates for specific
1329 1331 repository. In order to fulfill the request any of predicates must be meet
1330 1332 """
1331 1333
1332 1334 def check_permissions(self, user):
1333 1335 perms = user.permissions
1334 1336 repo_name = get_repo_slug(request)
1335 1337 try:
1336 1338 user_perms = set([perms['repositories'][repo_name]])
1337 1339 except KeyError:
1338 1340 return False
1339 1341
1340 1342 if self.required_perms.intersection(user_perms):
1341 1343 return True
1342 1344 return False
1343 1345
1344 1346
1345 1347 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1346 1348 """
1347 1349 Checks for access permission for all given predicates for specific
1348 1350 repository group. All of them have to be meet in order to
1349 1351 fulfill the request
1350 1352 """
1351 1353
1352 1354 def check_permissions(self, user):
1353 1355 perms = user.permissions
1354 1356 group_name = get_repo_group_slug(request)
1355 1357 try:
1356 1358 user_perms = set([perms['repositories_groups'][group_name]])
1357 1359 except KeyError:
1358 1360 return False
1359 1361
1360 1362 if self.required_perms.issubset(user_perms):
1361 1363 return True
1362 1364 return False
1363 1365
1364 1366
1365 1367 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1366 1368 """
1367 1369 Checks for access permission for any of given predicates for specific
1368 1370 repository group. In order to fulfill the request any
1369 1371 of predicates must be met
1370 1372 """
1371 1373
1372 1374 def check_permissions(self, user):
1373 1375 perms = user.permissions
1374 1376 group_name = get_repo_group_slug(request)
1375 1377 try:
1376 1378 user_perms = set([perms['repositories_groups'][group_name]])
1377 1379 except KeyError:
1378 1380 return False
1379 1381
1380 1382 if self.required_perms.intersection(user_perms):
1381 1383 return True
1382 1384 return False
1383 1385
1384 1386
1385 1387 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1386 1388 """
1387 1389 Checks for access permission for all given predicates for specific
1388 1390 user group. All of them have to be meet in order to fulfill the request
1389 1391 """
1390 1392
1391 1393 def check_permissions(self, user):
1392 1394 perms = user.permissions
1393 1395 group_name = get_user_group_slug(request)
1394 1396 try:
1395 1397 user_perms = set([perms['user_groups'][group_name]])
1396 1398 except KeyError:
1397 1399 return False
1398 1400
1399 1401 if self.required_perms.issubset(user_perms):
1400 1402 return True
1401 1403 return False
1402 1404
1403 1405
1404 1406 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1405 1407 """
1406 1408 Checks for access permission for any of given predicates for specific
1407 1409 user group. In order to fulfill the request any of predicates must be meet
1408 1410 """
1409 1411
1410 1412 def check_permissions(self, user):
1411 1413 perms = user.permissions
1412 1414 group_name = get_user_group_slug(request)
1413 1415 try:
1414 1416 user_perms = set([perms['user_groups'][group_name]])
1415 1417 except KeyError:
1416 1418 return False
1417 1419
1418 1420 if self.required_perms.intersection(user_perms):
1419 1421 return True
1420 1422 return False
1421 1423
1422 1424
1423 1425 # CHECK FUNCTIONS
1424 1426 class PermsFunction(object):
1425 1427 """Base function for other check functions"""
1426 1428
1427 1429 def __init__(self, *perms):
1428 1430 self.required_perms = set(perms)
1429 1431 self.repo_name = None
1430 1432 self.repo_group_name = None
1431 1433 self.user_group_name = None
1432 1434
1433 1435 def __bool__(self):
1434 1436 frame = inspect.currentframe()
1435 1437 stack_trace = traceback.format_stack(frame)
1436 1438 log.error('Checking bool value on a class instance of perm '
1437 1439 'function is not allowed: %s' % ''.join(stack_trace))
1438 1440 # rather than throwing errors, here we always return False so if by
1439 1441 # accident someone checks truth for just an instance it will always end
1440 1442 # up in returning False
1441 1443 return False
1442 1444 __nonzero__ = __bool__
1443 1445
1444 1446 def __call__(self, check_location='', user=None):
1445 1447 if not user:
1446 1448 log.debug('Using user attribute from global request')
1447 1449 # TODO: remove this someday,put as user as attribute here
1448 1450 user = request.user
1449 1451
1450 1452 # init auth user if not already given
1451 1453 if not isinstance(user, AuthUser):
1452 1454 log.debug('Wrapping user %s into AuthUser', user)
1453 1455 user = AuthUser(user.user_id)
1454 1456
1455 1457 cls_name = self.__class__.__name__
1456 1458 check_scope = self._get_check_scope(cls_name)
1457 1459 check_location = check_location or 'unspecified location'
1458 1460
1459 1461 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1460 1462 self.required_perms, user, check_scope, check_location)
1461 1463 if not user:
1462 1464 log.warning('Empty user given for permission check')
1463 1465 return False
1464 1466
1465 1467 if self.check_permissions(user):
1466 1468 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1467 1469 check_scope, user, check_location)
1468 1470 return True
1469 1471
1470 1472 else:
1471 1473 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1472 1474 check_scope, user, check_location)
1473 1475 return False
1474 1476
1475 1477 def _get_check_scope(self, cls_name):
1476 1478 return {
1477 1479 'HasPermissionAll': 'GLOBAL',
1478 1480 'HasPermissionAny': 'GLOBAL',
1479 1481 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1480 1482 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1481 1483 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1482 1484 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1483 1485 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1484 1486 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1485 1487 }.get(cls_name, '?:%s' % cls_name)
1486 1488
1487 1489 def check_permissions(self, user):
1488 1490 """Dummy function for overriding"""
1489 1491 raise Exception('You have to write this function in child class')
1490 1492
1491 1493
1492 1494 class HasPermissionAll(PermsFunction):
1493 1495 def check_permissions(self, user):
1494 1496 perms = user.permissions_with_scope({})
1495 1497 if self.required_perms.issubset(perms.get('global')):
1496 1498 return True
1497 1499 return False
1498 1500
1499 1501
1500 1502 class HasPermissionAny(PermsFunction):
1501 1503 def check_permissions(self, user):
1502 1504 perms = user.permissions_with_scope({})
1503 1505 if self.required_perms.intersection(perms.get('global')):
1504 1506 return True
1505 1507 return False
1506 1508
1507 1509
1508 1510 class HasRepoPermissionAll(PermsFunction):
1509 1511 def __call__(self, repo_name=None, check_location='', user=None):
1510 1512 self.repo_name = repo_name
1511 1513 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1512 1514
1513 1515 def check_permissions(self, user):
1514 1516 if not self.repo_name:
1515 1517 self.repo_name = get_repo_slug(request)
1516 1518
1517 1519 perms = user.permissions
1518 1520 try:
1519 1521 user_perms = set([perms['repositories'][self.repo_name]])
1520 1522 except KeyError:
1521 1523 return False
1522 1524 if self.required_perms.issubset(user_perms):
1523 1525 return True
1524 1526 return False
1525 1527
1526 1528
1527 1529 class HasRepoPermissionAny(PermsFunction):
1528 1530 def __call__(self, repo_name=None, check_location='', user=None):
1529 1531 self.repo_name = repo_name
1530 1532 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1531 1533
1532 1534 def check_permissions(self, user):
1533 1535 if not self.repo_name:
1534 1536 self.repo_name = get_repo_slug(request)
1535 1537
1536 1538 perms = user.permissions
1537 1539 try:
1538 1540 user_perms = set([perms['repositories'][self.repo_name]])
1539 1541 except KeyError:
1540 1542 return False
1541 1543 if self.required_perms.intersection(user_perms):
1542 1544 return True
1543 1545 return False
1544 1546
1545 1547
1546 1548 class HasRepoGroupPermissionAny(PermsFunction):
1547 1549 def __call__(self, group_name=None, check_location='', user=None):
1548 1550 self.repo_group_name = group_name
1549 1551 return super(HasRepoGroupPermissionAny, self).__call__(
1550 1552 check_location, user)
1551 1553
1552 1554 def check_permissions(self, user):
1553 1555 perms = user.permissions
1554 1556 try:
1555 1557 user_perms = set(
1556 1558 [perms['repositories_groups'][self.repo_group_name]])
1557 1559 except KeyError:
1558 1560 return False
1559 1561 if self.required_perms.intersection(user_perms):
1560 1562 return True
1561 1563 return False
1562 1564
1563 1565
1564 1566 class HasRepoGroupPermissionAll(PermsFunction):
1565 1567 def __call__(self, group_name=None, check_location='', user=None):
1566 1568 self.repo_group_name = group_name
1567 1569 return super(HasRepoGroupPermissionAll, self).__call__(
1568 1570 check_location, user)
1569 1571
1570 1572 def check_permissions(self, user):
1571 1573 perms = user.permissions
1572 1574 try:
1573 1575 user_perms = set(
1574 1576 [perms['repositories_groups'][self.repo_group_name]])
1575 1577 except KeyError:
1576 1578 return False
1577 1579 if self.required_perms.issubset(user_perms):
1578 1580 return True
1579 1581 return False
1580 1582
1581 1583
1582 1584 class HasUserGroupPermissionAny(PermsFunction):
1583 1585 def __call__(self, user_group_name=None, check_location='', user=None):
1584 1586 self.user_group_name = user_group_name
1585 1587 return super(HasUserGroupPermissionAny, self).__call__(
1586 1588 check_location, user)
1587 1589
1588 1590 def check_permissions(self, user):
1589 1591 perms = user.permissions
1590 1592 try:
1591 1593 user_perms = set([perms['user_groups'][self.user_group_name]])
1592 1594 except KeyError:
1593 1595 return False
1594 1596 if self.required_perms.intersection(user_perms):
1595 1597 return True
1596 1598 return False
1597 1599
1598 1600
1599 1601 class HasUserGroupPermissionAll(PermsFunction):
1600 1602 def __call__(self, user_group_name=None, check_location='', user=None):
1601 1603 self.user_group_name = user_group_name
1602 1604 return super(HasUserGroupPermissionAll, self).__call__(
1603 1605 check_location, user)
1604 1606
1605 1607 def check_permissions(self, user):
1606 1608 perms = user.permissions
1607 1609 try:
1608 1610 user_perms = set([perms['user_groups'][self.user_group_name]])
1609 1611 except KeyError:
1610 1612 return False
1611 1613 if self.required_perms.issubset(user_perms):
1612 1614 return True
1613 1615 return False
1614 1616
1615 1617
1616 1618 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1617 1619 class HasPermissionAnyMiddleware(object):
1618 1620 def __init__(self, *perms):
1619 1621 self.required_perms = set(perms)
1620 1622
1621 1623 def __call__(self, user, repo_name):
1622 1624 # repo_name MUST be unicode, since we handle keys in permission
1623 1625 # dict by unicode
1624 1626 repo_name = safe_unicode(repo_name)
1625 1627 user = AuthUser(user.user_id)
1626 1628 log.debug(
1627 1629 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1628 1630 self.required_perms, user, repo_name)
1629 1631
1630 1632 if self.check_permissions(user, repo_name):
1631 1633 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1632 1634 repo_name, user, 'PermissionMiddleware')
1633 1635 return True
1634 1636
1635 1637 else:
1636 1638 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1637 1639 repo_name, user, 'PermissionMiddleware')
1638 1640 return False
1639 1641
1640 1642 def check_permissions(self, user, repo_name):
1641 1643 perms = user.permissions_with_scope({'repo_name': repo_name})
1642 1644
1643 1645 try:
1644 1646 user_perms = set([perms['repositories'][repo_name]])
1645 1647 except Exception:
1646 1648 log.exception('Error while accessing user permissions')
1647 1649 return False
1648 1650
1649 1651 if self.required_perms.intersection(user_perms):
1650 1652 return True
1651 1653 return False
1652 1654
1653 1655
1654 1656 # SPECIAL VERSION TO HANDLE API AUTH
1655 1657 class _BaseApiPerm(object):
1656 1658 def __init__(self, *perms):
1657 1659 self.required_perms = set(perms)
1658 1660
1659 1661 def __call__(self, check_location=None, user=None, repo_name=None,
1660 1662 group_name=None, user_group_name=None):
1661 1663 cls_name = self.__class__.__name__
1662 1664 check_scope = 'global:%s' % (self.required_perms,)
1663 1665 if repo_name:
1664 1666 check_scope += ', repo_name:%s' % (repo_name,)
1665 1667
1666 1668 if group_name:
1667 1669 check_scope += ', repo_group_name:%s' % (group_name,)
1668 1670
1669 1671 if user_group_name:
1670 1672 check_scope += ', user_group_name:%s' % (user_group_name,)
1671 1673
1672 1674 log.debug(
1673 1675 'checking cls:%s %s %s @ %s'
1674 1676 % (cls_name, self.required_perms, check_scope, check_location))
1675 1677 if not user:
1676 1678 log.debug('Empty User passed into arguments')
1677 1679 return False
1678 1680
1679 1681 # process user
1680 1682 if not isinstance(user, AuthUser):
1681 1683 user = AuthUser(user.user_id)
1682 1684 if not check_location:
1683 1685 check_location = 'unspecified'
1684 1686 if self.check_permissions(user.permissions, repo_name, group_name,
1685 1687 user_group_name):
1686 1688 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1687 1689 check_scope, user, check_location)
1688 1690 return True
1689 1691
1690 1692 else:
1691 1693 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1692 1694 check_scope, user, check_location)
1693 1695 return False
1694 1696
1695 1697 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1696 1698 user_group_name=None):
1697 1699 """
1698 1700 implement in child class should return True if permissions are ok,
1699 1701 False otherwise
1700 1702
1701 1703 :param perm_defs: dict with permission definitions
1702 1704 :param repo_name: repo name
1703 1705 """
1704 1706 raise NotImplementedError()
1705 1707
1706 1708
1707 1709 class HasPermissionAllApi(_BaseApiPerm):
1708 1710 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1709 1711 user_group_name=None):
1710 1712 if self.required_perms.issubset(perm_defs.get('global')):
1711 1713 return True
1712 1714 return False
1713 1715
1714 1716
1715 1717 class HasPermissionAnyApi(_BaseApiPerm):
1716 1718 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1717 1719 user_group_name=None):
1718 1720 if self.required_perms.intersection(perm_defs.get('global')):
1719 1721 return True
1720 1722 return False
1721 1723
1722 1724
1723 1725 class HasRepoPermissionAllApi(_BaseApiPerm):
1724 1726 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1725 1727 user_group_name=None):
1726 1728 try:
1727 1729 _user_perms = set([perm_defs['repositories'][repo_name]])
1728 1730 except KeyError:
1729 1731 log.warning(traceback.format_exc())
1730 1732 return False
1731 1733 if self.required_perms.issubset(_user_perms):
1732 1734 return True
1733 1735 return False
1734 1736
1735 1737
1736 1738 class HasRepoPermissionAnyApi(_BaseApiPerm):
1737 1739 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1738 1740 user_group_name=None):
1739 1741 try:
1740 1742 _user_perms = set([perm_defs['repositories'][repo_name]])
1741 1743 except KeyError:
1742 1744 log.warning(traceback.format_exc())
1743 1745 return False
1744 1746 if self.required_perms.intersection(_user_perms):
1745 1747 return True
1746 1748 return False
1747 1749
1748 1750
1749 1751 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
1750 1752 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1751 1753 user_group_name=None):
1752 1754 try:
1753 1755 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1754 1756 except KeyError:
1755 1757 log.warning(traceback.format_exc())
1756 1758 return False
1757 1759 if self.required_perms.intersection(_user_perms):
1758 1760 return True
1759 1761 return False
1760 1762
1761 1763
1762 1764 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
1763 1765 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1764 1766 user_group_name=None):
1765 1767 try:
1766 1768 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1767 1769 except KeyError:
1768 1770 log.warning(traceback.format_exc())
1769 1771 return False
1770 1772 if self.required_perms.issubset(_user_perms):
1771 1773 return True
1772 1774 return False
1773 1775
1774 1776
1775 1777 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
1776 1778 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1777 1779 user_group_name=None):
1778 1780 try:
1779 1781 _user_perms = set([perm_defs['user_groups'][user_group_name]])
1780 1782 except KeyError:
1781 1783 log.warning(traceback.format_exc())
1782 1784 return False
1783 1785 if self.required_perms.intersection(_user_perms):
1784 1786 return True
1785 1787 return False
1786 1788
1787 1789
1788 1790 def check_ip_access(source_ip, allowed_ips=None):
1789 1791 """
1790 1792 Checks if source_ip is a subnet of any of allowed_ips.
1791 1793
1792 1794 :param source_ip:
1793 1795 :param allowed_ips: list of allowed ips together with mask
1794 1796 """
1795 1797 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1796 1798 source_ip_address = ipaddress.ip_address(source_ip)
1797 1799 if isinstance(allowed_ips, (tuple, list, set)):
1798 1800 for ip in allowed_ips:
1799 1801 try:
1800 1802 network_address = ipaddress.ip_network(ip, strict=False)
1801 1803 if source_ip_address in network_address:
1802 1804 log.debug('IP %s is network %s' %
1803 1805 (source_ip_address, network_address))
1804 1806 return True
1805 1807 # for any case we cannot determine the IP, don't crash just
1806 1808 # skip it and log as error, we want to say forbidden still when
1807 1809 # sending bad IP
1808 1810 except Exception:
1809 1811 log.error(traceback.format_exc())
1810 1812 continue
1811 1813 return False
1812 1814
1813 1815
1814 1816 def get_cython_compat_decorator(wrapper, func):
1815 1817 """
1816 1818 Creates a cython compatible decorator. The previously used
1817 1819 decorator.decorator() function seems to be incompatible with cython.
1818 1820
1819 1821 :param wrapper: __wrapper method of the decorator class
1820 1822 :param func: decorated function
1821 1823 """
1822 1824 @wraps(func)
1823 1825 def local_wrapper(*args, **kwds):
1824 1826 return wrapper(func, *args, **kwds)
1825 1827 local_wrapper.__wrapped__ = func
1826 1828 return local_wrapper
General Comments 0
You need to be logged in to leave comments. Login now