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