##// END OF EJS Templates
logging: improve logging on failed auth user lookup
marcink -
r77:f0ab3c53 default
parent child Browse files
Show More
@@ -1,1828 +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 log.debug('Failed to load user. Fallback to default user')
802 log.debug('Failed to load user %s. Fallback to default user', self)
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 1102 from rhodecode.lib import helpers as h
1103 1103 cls = fargs[0]
1104 1104 user = cls._rhodecode_user
1105 1105 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1106 1106 log.debug('Starting login restriction checks for user: %s' % (user,))
1107 1107 # check if our IP is allowed
1108 1108 ip_access_valid = True
1109 1109 if not user.ip_allowed:
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 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 1187 return redirect(
1188 1188 h.route_path('login', _query={'came_from': came_from}))
1189 1189 else:
1190 1190 return func(*fargs, **fkwargs)
1191 1191
1192 1192
1193 1193 class XHRRequired(object):
1194 1194 def __call__(self, func):
1195 1195 return get_cython_compat_decorator(self.__wrapper, func)
1196 1196
1197 1197 def __wrapper(self, func, *fargs, **fkwargs):
1198 1198 log.debug('Checking if request is XMLHttpRequest (XHR)')
1199 1199 xhr_message = 'This is not a valid XMLHttpRequest (XHR) request'
1200 1200 if not request.is_xhr:
1201 1201 abort(400, detail=xhr_message)
1202 1202
1203 1203 return func(*fargs, **fkwargs)
1204 1204
1205 1205
1206 1206 class HasAcceptedRepoType(object):
1207 1207 """
1208 1208 Check if requested repo is within given repo type aliases
1209 1209
1210 1210 TODO: anderson: not sure where to put this decorator
1211 1211 """
1212 1212
1213 1213 def __init__(self, *repo_type_list):
1214 1214 self.repo_type_list = set(repo_type_list)
1215 1215
1216 1216 def __call__(self, func):
1217 1217 return get_cython_compat_decorator(self.__wrapper, func)
1218 1218
1219 1219 def __wrapper(self, func, *fargs, **fkwargs):
1220 1220 cls = fargs[0]
1221 1221 rhodecode_repo = cls.rhodecode_repo
1222 1222
1223 1223 log.debug('%s checking repo type for %s in %s',
1224 1224 self.__class__.__name__,
1225 1225 rhodecode_repo.alias, self.repo_type_list)
1226 1226
1227 1227 if rhodecode_repo.alias in self.repo_type_list:
1228 1228 return func(*fargs, **fkwargs)
1229 1229 else:
1230 1230 import rhodecode.lib.helpers as h
1231 1231 h.flash(h.literal(
1232 1232 _('Action not supported for %s.' % rhodecode_repo.alias)),
1233 1233 category='warning')
1234 1234 return redirect(
1235 1235 url('summary_home', repo_name=cls.rhodecode_db_repo.repo_name))
1236 1236
1237 1237
1238 1238 class PermsDecorator(object):
1239 1239 """
1240 1240 Base class for controller decorators, we extract the current user from
1241 1241 the class itself, which has it stored in base controllers
1242 1242 """
1243 1243
1244 1244 def __init__(self, *required_perms):
1245 1245 self.required_perms = set(required_perms)
1246 1246
1247 1247 def __call__(self, func):
1248 1248 return get_cython_compat_decorator(self.__wrapper, func)
1249 1249
1250 1250 def __wrapper(self, func, *fargs, **fkwargs):
1251 1251 cls = fargs[0]
1252 1252 _user = cls._rhodecode_user
1253 1253
1254 1254 log.debug('checking %s permissions %s for %s %s',
1255 1255 self.__class__.__name__, self.required_perms, cls, _user)
1256 1256
1257 1257 if self.check_permissions(_user):
1258 1258 log.debug('Permission granted for %s %s', cls, _user)
1259 1259 return func(*fargs, **fkwargs)
1260 1260
1261 1261 else:
1262 1262 log.debug('Permission denied for %s %s', cls, _user)
1263 1263 anonymous = _user.username == User.DEFAULT_USER
1264 1264
1265 1265 if anonymous:
1266 1266 came_from = request.path_qs
1267 1267
1268 1268 import rhodecode.lib.helpers as h
1269 1269 h.flash(_('You need to be signed in to view this page'),
1270 1270 category='warning')
1271 1271 return redirect(
1272 1272 h.route_path('login', _query={'came_from': came_from}))
1273 1273
1274 1274 else:
1275 1275 # redirect with forbidden ret code
1276 1276 return abort(403)
1277 1277
1278 1278 def check_permissions(self, user):
1279 1279 """Dummy function for overriding"""
1280 1280 raise NotImplementedError(
1281 1281 'You have to write this function in child class')
1282 1282
1283 1283
1284 1284 class HasPermissionAllDecorator(PermsDecorator):
1285 1285 """
1286 1286 Checks for access permission for all given predicates. All of them
1287 1287 have to be meet in order to fulfill the request
1288 1288 """
1289 1289
1290 1290 def check_permissions(self, user):
1291 1291 perms = user.permissions_with_scope({})
1292 1292 if self.required_perms.issubset(perms['global']):
1293 1293 return True
1294 1294 return False
1295 1295
1296 1296
1297 1297 class HasPermissionAnyDecorator(PermsDecorator):
1298 1298 """
1299 1299 Checks for access permission for any of given predicates. In order to
1300 1300 fulfill the request any of predicates must be meet
1301 1301 """
1302 1302
1303 1303 def check_permissions(self, user):
1304 1304 perms = user.permissions_with_scope({})
1305 1305 if self.required_perms.intersection(perms['global']):
1306 1306 return True
1307 1307 return False
1308 1308
1309 1309
1310 1310 class HasRepoPermissionAllDecorator(PermsDecorator):
1311 1311 """
1312 1312 Checks for access permission for all given predicates for specific
1313 1313 repository. All of them have to be meet in order to fulfill the request
1314 1314 """
1315 1315
1316 1316 def check_permissions(self, user):
1317 1317 perms = user.permissions
1318 1318 repo_name = get_repo_slug(request)
1319 1319 try:
1320 1320 user_perms = set([perms['repositories'][repo_name]])
1321 1321 except KeyError:
1322 1322 return False
1323 1323 if self.required_perms.issubset(user_perms):
1324 1324 return True
1325 1325 return False
1326 1326
1327 1327
1328 1328 class HasRepoPermissionAnyDecorator(PermsDecorator):
1329 1329 """
1330 1330 Checks for access permission for any of given predicates for specific
1331 1331 repository. In order to fulfill the request any of predicates must be meet
1332 1332 """
1333 1333
1334 1334 def check_permissions(self, user):
1335 1335 perms = user.permissions
1336 1336 repo_name = get_repo_slug(request)
1337 1337 try:
1338 1338 user_perms = set([perms['repositories'][repo_name]])
1339 1339 except KeyError:
1340 1340 return False
1341 1341
1342 1342 if self.required_perms.intersection(user_perms):
1343 1343 return True
1344 1344 return False
1345 1345
1346 1346
1347 1347 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1348 1348 """
1349 1349 Checks for access permission for all given predicates for specific
1350 1350 repository group. All of them have to be meet in order to
1351 1351 fulfill the request
1352 1352 """
1353 1353
1354 1354 def check_permissions(self, user):
1355 1355 perms = user.permissions
1356 1356 group_name = get_repo_group_slug(request)
1357 1357 try:
1358 1358 user_perms = set([perms['repositories_groups'][group_name]])
1359 1359 except KeyError:
1360 1360 return False
1361 1361
1362 1362 if self.required_perms.issubset(user_perms):
1363 1363 return True
1364 1364 return False
1365 1365
1366 1366
1367 1367 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1368 1368 """
1369 1369 Checks for access permission for any of given predicates for specific
1370 1370 repository group. In order to fulfill the request any
1371 1371 of predicates must be met
1372 1372 """
1373 1373
1374 1374 def check_permissions(self, user):
1375 1375 perms = user.permissions
1376 1376 group_name = get_repo_group_slug(request)
1377 1377 try:
1378 1378 user_perms = set([perms['repositories_groups'][group_name]])
1379 1379 except KeyError:
1380 1380 return False
1381 1381
1382 1382 if self.required_perms.intersection(user_perms):
1383 1383 return True
1384 1384 return False
1385 1385
1386 1386
1387 1387 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1388 1388 """
1389 1389 Checks for access permission for all given predicates for specific
1390 1390 user group. All of them have to be meet in order to fulfill the request
1391 1391 """
1392 1392
1393 1393 def check_permissions(self, user):
1394 1394 perms = user.permissions
1395 1395 group_name = get_user_group_slug(request)
1396 1396 try:
1397 1397 user_perms = set([perms['user_groups'][group_name]])
1398 1398 except KeyError:
1399 1399 return False
1400 1400
1401 1401 if self.required_perms.issubset(user_perms):
1402 1402 return True
1403 1403 return False
1404 1404
1405 1405
1406 1406 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1407 1407 """
1408 1408 Checks for access permission for any of given predicates for specific
1409 1409 user group. In order to fulfill the request any of predicates must be meet
1410 1410 """
1411 1411
1412 1412 def check_permissions(self, user):
1413 1413 perms = user.permissions
1414 1414 group_name = get_user_group_slug(request)
1415 1415 try:
1416 1416 user_perms = set([perms['user_groups'][group_name]])
1417 1417 except KeyError:
1418 1418 return False
1419 1419
1420 1420 if self.required_perms.intersection(user_perms):
1421 1421 return True
1422 1422 return False
1423 1423
1424 1424
1425 1425 # CHECK FUNCTIONS
1426 1426 class PermsFunction(object):
1427 1427 """Base function for other check functions"""
1428 1428
1429 1429 def __init__(self, *perms):
1430 1430 self.required_perms = set(perms)
1431 1431 self.repo_name = None
1432 1432 self.repo_group_name = None
1433 1433 self.user_group_name = None
1434 1434
1435 1435 def __bool__(self):
1436 1436 frame = inspect.currentframe()
1437 1437 stack_trace = traceback.format_stack(frame)
1438 1438 log.error('Checking bool value on a class instance of perm '
1439 1439 'function is not allowed: %s' % ''.join(stack_trace))
1440 1440 # rather than throwing errors, here we always return False so if by
1441 1441 # accident someone checks truth for just an instance it will always end
1442 1442 # up in returning False
1443 1443 return False
1444 1444 __nonzero__ = __bool__
1445 1445
1446 1446 def __call__(self, check_location='', user=None):
1447 1447 if not user:
1448 1448 log.debug('Using user attribute from global request')
1449 1449 # TODO: remove this someday,put as user as attribute here
1450 1450 user = request.user
1451 1451
1452 1452 # init auth user if not already given
1453 1453 if not isinstance(user, AuthUser):
1454 1454 log.debug('Wrapping user %s into AuthUser', user)
1455 1455 user = AuthUser(user.user_id)
1456 1456
1457 1457 cls_name = self.__class__.__name__
1458 1458 check_scope = self._get_check_scope(cls_name)
1459 1459 check_location = check_location or 'unspecified location'
1460 1460
1461 1461 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1462 1462 self.required_perms, user, check_scope, check_location)
1463 1463 if not user:
1464 1464 log.warning('Empty user given for permission check')
1465 1465 return False
1466 1466
1467 1467 if self.check_permissions(user):
1468 1468 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1469 1469 check_scope, user, check_location)
1470 1470 return True
1471 1471
1472 1472 else:
1473 1473 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1474 1474 check_scope, user, check_location)
1475 1475 return False
1476 1476
1477 1477 def _get_check_scope(self, cls_name):
1478 1478 return {
1479 1479 'HasPermissionAll': 'GLOBAL',
1480 1480 'HasPermissionAny': 'GLOBAL',
1481 1481 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1482 1482 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1483 1483 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1484 1484 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1485 1485 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1486 1486 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1487 1487 }.get(cls_name, '?:%s' % cls_name)
1488 1488
1489 1489 def check_permissions(self, user):
1490 1490 """Dummy function for overriding"""
1491 1491 raise Exception('You have to write this function in child class')
1492 1492
1493 1493
1494 1494 class HasPermissionAll(PermsFunction):
1495 1495 def check_permissions(self, user):
1496 1496 perms = user.permissions_with_scope({})
1497 1497 if self.required_perms.issubset(perms.get('global')):
1498 1498 return True
1499 1499 return False
1500 1500
1501 1501
1502 1502 class HasPermissionAny(PermsFunction):
1503 1503 def check_permissions(self, user):
1504 1504 perms = user.permissions_with_scope({})
1505 1505 if self.required_perms.intersection(perms.get('global')):
1506 1506 return True
1507 1507 return False
1508 1508
1509 1509
1510 1510 class HasRepoPermissionAll(PermsFunction):
1511 1511 def __call__(self, repo_name=None, check_location='', user=None):
1512 1512 self.repo_name = repo_name
1513 1513 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1514 1514
1515 1515 def check_permissions(self, user):
1516 1516 if not self.repo_name:
1517 1517 self.repo_name = get_repo_slug(request)
1518 1518
1519 1519 perms = user.permissions
1520 1520 try:
1521 1521 user_perms = set([perms['repositories'][self.repo_name]])
1522 1522 except KeyError:
1523 1523 return False
1524 1524 if self.required_perms.issubset(user_perms):
1525 1525 return True
1526 1526 return False
1527 1527
1528 1528
1529 1529 class HasRepoPermissionAny(PermsFunction):
1530 1530 def __call__(self, repo_name=None, check_location='', user=None):
1531 1531 self.repo_name = repo_name
1532 1532 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1533 1533
1534 1534 def check_permissions(self, user):
1535 1535 if not self.repo_name:
1536 1536 self.repo_name = get_repo_slug(request)
1537 1537
1538 1538 perms = user.permissions
1539 1539 try:
1540 1540 user_perms = set([perms['repositories'][self.repo_name]])
1541 1541 except KeyError:
1542 1542 return False
1543 1543 if self.required_perms.intersection(user_perms):
1544 1544 return True
1545 1545 return False
1546 1546
1547 1547
1548 1548 class HasRepoGroupPermissionAny(PermsFunction):
1549 1549 def __call__(self, group_name=None, check_location='', user=None):
1550 1550 self.repo_group_name = group_name
1551 1551 return super(HasRepoGroupPermissionAny, self).__call__(
1552 1552 check_location, user)
1553 1553
1554 1554 def check_permissions(self, user):
1555 1555 perms = user.permissions
1556 1556 try:
1557 1557 user_perms = set(
1558 1558 [perms['repositories_groups'][self.repo_group_name]])
1559 1559 except KeyError:
1560 1560 return False
1561 1561 if self.required_perms.intersection(user_perms):
1562 1562 return True
1563 1563 return False
1564 1564
1565 1565
1566 1566 class HasRepoGroupPermissionAll(PermsFunction):
1567 1567 def __call__(self, group_name=None, check_location='', user=None):
1568 1568 self.repo_group_name = group_name
1569 1569 return super(HasRepoGroupPermissionAll, self).__call__(
1570 1570 check_location, user)
1571 1571
1572 1572 def check_permissions(self, user):
1573 1573 perms = user.permissions
1574 1574 try:
1575 1575 user_perms = set(
1576 1576 [perms['repositories_groups'][self.repo_group_name]])
1577 1577 except KeyError:
1578 1578 return False
1579 1579 if self.required_perms.issubset(user_perms):
1580 1580 return True
1581 1581 return False
1582 1582
1583 1583
1584 1584 class HasUserGroupPermissionAny(PermsFunction):
1585 1585 def __call__(self, user_group_name=None, check_location='', user=None):
1586 1586 self.user_group_name = user_group_name
1587 1587 return super(HasUserGroupPermissionAny, self).__call__(
1588 1588 check_location, user)
1589 1589
1590 1590 def check_permissions(self, user):
1591 1591 perms = user.permissions
1592 1592 try:
1593 1593 user_perms = set([perms['user_groups'][self.user_group_name]])
1594 1594 except KeyError:
1595 1595 return False
1596 1596 if self.required_perms.intersection(user_perms):
1597 1597 return True
1598 1598 return False
1599 1599
1600 1600
1601 1601 class HasUserGroupPermissionAll(PermsFunction):
1602 1602 def __call__(self, user_group_name=None, check_location='', user=None):
1603 1603 self.user_group_name = user_group_name
1604 1604 return super(HasUserGroupPermissionAll, self).__call__(
1605 1605 check_location, user)
1606 1606
1607 1607 def check_permissions(self, user):
1608 1608 perms = user.permissions
1609 1609 try:
1610 1610 user_perms = set([perms['user_groups'][self.user_group_name]])
1611 1611 except KeyError:
1612 1612 return False
1613 1613 if self.required_perms.issubset(user_perms):
1614 1614 return True
1615 1615 return False
1616 1616
1617 1617
1618 1618 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
1619 1619 class HasPermissionAnyMiddleware(object):
1620 1620 def __init__(self, *perms):
1621 1621 self.required_perms = set(perms)
1622 1622
1623 1623 def __call__(self, user, repo_name):
1624 1624 # repo_name MUST be unicode, since we handle keys in permission
1625 1625 # dict by unicode
1626 1626 repo_name = safe_unicode(repo_name)
1627 1627 user = AuthUser(user.user_id)
1628 1628 log.debug(
1629 1629 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
1630 1630 self.required_perms, user, repo_name)
1631 1631
1632 1632 if self.check_permissions(user, repo_name):
1633 1633 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
1634 1634 repo_name, user, 'PermissionMiddleware')
1635 1635 return True
1636 1636
1637 1637 else:
1638 1638 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
1639 1639 repo_name, user, 'PermissionMiddleware')
1640 1640 return False
1641 1641
1642 1642 def check_permissions(self, user, repo_name):
1643 1643 perms = user.permissions_with_scope({'repo_name': repo_name})
1644 1644
1645 1645 try:
1646 1646 user_perms = set([perms['repositories'][repo_name]])
1647 1647 except Exception:
1648 1648 log.exception('Error while accessing user permissions')
1649 1649 return False
1650 1650
1651 1651 if self.required_perms.intersection(user_perms):
1652 1652 return True
1653 1653 return False
1654 1654
1655 1655
1656 1656 # SPECIAL VERSION TO HANDLE API AUTH
1657 1657 class _BaseApiPerm(object):
1658 1658 def __init__(self, *perms):
1659 1659 self.required_perms = set(perms)
1660 1660
1661 1661 def __call__(self, check_location=None, user=None, repo_name=None,
1662 1662 group_name=None, user_group_name=None):
1663 1663 cls_name = self.__class__.__name__
1664 1664 check_scope = 'global:%s' % (self.required_perms,)
1665 1665 if repo_name:
1666 1666 check_scope += ', repo_name:%s' % (repo_name,)
1667 1667
1668 1668 if group_name:
1669 1669 check_scope += ', repo_group_name:%s' % (group_name,)
1670 1670
1671 1671 if user_group_name:
1672 1672 check_scope += ', user_group_name:%s' % (user_group_name,)
1673 1673
1674 1674 log.debug(
1675 1675 'checking cls:%s %s %s @ %s'
1676 1676 % (cls_name, self.required_perms, check_scope, check_location))
1677 1677 if not user:
1678 1678 log.debug('Empty User passed into arguments')
1679 1679 return False
1680 1680
1681 1681 # process user
1682 1682 if not isinstance(user, AuthUser):
1683 1683 user = AuthUser(user.user_id)
1684 1684 if not check_location:
1685 1685 check_location = 'unspecified'
1686 1686 if self.check_permissions(user.permissions, repo_name, group_name,
1687 1687 user_group_name):
1688 1688 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1689 1689 check_scope, user, check_location)
1690 1690 return True
1691 1691
1692 1692 else:
1693 1693 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1694 1694 check_scope, user, check_location)
1695 1695 return False
1696 1696
1697 1697 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1698 1698 user_group_name=None):
1699 1699 """
1700 1700 implement in child class should return True if permissions are ok,
1701 1701 False otherwise
1702 1702
1703 1703 :param perm_defs: dict with permission definitions
1704 1704 :param repo_name: repo name
1705 1705 """
1706 1706 raise NotImplementedError()
1707 1707
1708 1708
1709 1709 class HasPermissionAllApi(_BaseApiPerm):
1710 1710 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1711 1711 user_group_name=None):
1712 1712 if self.required_perms.issubset(perm_defs.get('global')):
1713 1713 return True
1714 1714 return False
1715 1715
1716 1716
1717 1717 class HasPermissionAnyApi(_BaseApiPerm):
1718 1718 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1719 1719 user_group_name=None):
1720 1720 if self.required_perms.intersection(perm_defs.get('global')):
1721 1721 return True
1722 1722 return False
1723 1723
1724 1724
1725 1725 class HasRepoPermissionAllApi(_BaseApiPerm):
1726 1726 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1727 1727 user_group_name=None):
1728 1728 try:
1729 1729 _user_perms = set([perm_defs['repositories'][repo_name]])
1730 1730 except KeyError:
1731 1731 log.warning(traceback.format_exc())
1732 1732 return False
1733 1733 if self.required_perms.issubset(_user_perms):
1734 1734 return True
1735 1735 return False
1736 1736
1737 1737
1738 1738 class HasRepoPermissionAnyApi(_BaseApiPerm):
1739 1739 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1740 1740 user_group_name=None):
1741 1741 try:
1742 1742 _user_perms = set([perm_defs['repositories'][repo_name]])
1743 1743 except KeyError:
1744 1744 log.warning(traceback.format_exc())
1745 1745 return False
1746 1746 if self.required_perms.intersection(_user_perms):
1747 1747 return True
1748 1748 return False
1749 1749
1750 1750
1751 1751 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
1752 1752 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1753 1753 user_group_name=None):
1754 1754 try:
1755 1755 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1756 1756 except KeyError:
1757 1757 log.warning(traceback.format_exc())
1758 1758 return False
1759 1759 if self.required_perms.intersection(_user_perms):
1760 1760 return True
1761 1761 return False
1762 1762
1763 1763
1764 1764 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
1765 1765 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1766 1766 user_group_name=None):
1767 1767 try:
1768 1768 _user_perms = set([perm_defs['repositories_groups'][group_name]])
1769 1769 except KeyError:
1770 1770 log.warning(traceback.format_exc())
1771 1771 return False
1772 1772 if self.required_perms.issubset(_user_perms):
1773 1773 return True
1774 1774 return False
1775 1775
1776 1776
1777 1777 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
1778 1778 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
1779 1779 user_group_name=None):
1780 1780 try:
1781 1781 _user_perms = set([perm_defs['user_groups'][user_group_name]])
1782 1782 except KeyError:
1783 1783 log.warning(traceback.format_exc())
1784 1784 return False
1785 1785 if self.required_perms.intersection(_user_perms):
1786 1786 return True
1787 1787 return False
1788 1788
1789 1789
1790 1790 def check_ip_access(source_ip, allowed_ips=None):
1791 1791 """
1792 1792 Checks if source_ip is a subnet of any of allowed_ips.
1793 1793
1794 1794 :param source_ip:
1795 1795 :param allowed_ips: list of allowed ips together with mask
1796 1796 """
1797 1797 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
1798 1798 source_ip_address = ipaddress.ip_address(source_ip)
1799 1799 if isinstance(allowed_ips, (tuple, list, set)):
1800 1800 for ip in allowed_ips:
1801 1801 try:
1802 1802 network_address = ipaddress.ip_network(ip, strict=False)
1803 1803 if source_ip_address in network_address:
1804 1804 log.debug('IP %s is network %s' %
1805 1805 (source_ip_address, network_address))
1806 1806 return True
1807 1807 # for any case we cannot determine the IP, don't crash just
1808 1808 # skip it and log as error, we want to say forbidden still when
1809 1809 # sending bad IP
1810 1810 except Exception:
1811 1811 log.error(traceback.format_exc())
1812 1812 continue
1813 1813 return False
1814 1814
1815 1815
1816 1816 def get_cython_compat_decorator(wrapper, func):
1817 1817 """
1818 1818 Creates a cython compatible decorator. The previously used
1819 1819 decorator.decorator() function seems to be incompatible with cython.
1820 1820
1821 1821 :param wrapper: __wrapper method of the decorator class
1822 1822 :param func: decorated function
1823 1823 """
1824 1824 @wraps(func)
1825 1825 def local_wrapper(*args, **kwds):
1826 1826 return wrapper(func, *args, **kwds)
1827 1827 local_wrapper.__wrapped__ = func
1828 1828 return local_wrapper
General Comments 0
You need to be logged in to leave comments. Login now