auth.py
2355 lines
| 85.6 KiB
| text/x-python
|
PythonLexer
r1 | # -*- coding: utf-8 -*- | |||
r2487 | # Copyright (C) 2010-2018 RhodeCode GmbH | |||
r1 | # | |||
# This program is free software: you can redistribute it and/or modify | ||||
# it under the terms of the GNU Affero General Public License, version 3 | ||||
# (only), as published by the Free Software Foundation. | ||||
# | ||||
# This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU Affero General Public License | ||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
# | ||||
# This program is dual-licensed. If you wish to learn more about the | ||||
# RhodeCode Enterprise Edition, including its added features, Support services, | ||||
# and proprietary license terms, please see https://rhodecode.com/licenses/ | ||||
""" | ||||
authentication and permission libraries | ||||
""" | ||||
r1494 | import os | |||
r2813 | import time | |||
r1 | import inspect | |||
import collections | ||||
import fnmatch | ||||
import hashlib | ||||
import itertools | ||||
import logging | ||||
import random | ||||
import traceback | ||||
from functools import wraps | ||||
import ipaddress | ||||
r2351 | ||||
r1817 | from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound | |||
r1 | from sqlalchemy.orm.exc import ObjectDeletedError | |||
from sqlalchemy.orm import joinedload | ||||
from zope.cachedescriptors.property import Lazy as LazyProperty | ||||
import rhodecode | ||||
from rhodecode.model import meta | ||||
from rhodecode.model.meta import Session | ||||
from rhodecode.model.user import UserModel | ||||
from rhodecode.model.db import ( | ||||
User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember, | ||||
r2036 | UserIpMap, UserApiKeys, RepoGroup, UserGroup) | |||
r2845 | from rhodecode.lib import rc_cache | |||
r2836 | from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1 | |||
r1 | from rhodecode.lib.utils import ( | |||
get_repo_slug, get_repo_group_slug, get_user_group_slug) | ||||
from rhodecode.lib.caching_query import FromCache | ||||
if rhodecode.is_unix: | ||||
import bcrypt | ||||
log = logging.getLogger(__name__) | ||||
csrf_token_key = "csrf_token" | ||||
class PasswordGenerator(object): | ||||
""" | ||||
This is a simple class for generating password from different sets of | ||||
characters | ||||
usage:: | ||||
passwd_gen = PasswordGenerator() | ||||
#print 8-letter password containing only big and small letters | ||||
of alphabet | ||||
passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL) | ||||
""" | ||||
ALPHABETS_NUM = r'''1234567890''' | ||||
ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm''' | ||||
ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM''' | ||||
ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' | ||||
ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \ | ||||
+ ALPHABETS_NUM + ALPHABETS_SPECIAL | ||||
ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM | ||||
ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL | ||||
ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM | ||||
ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM | ||||
def __init__(self, passwd=''): | ||||
self.passwd = passwd | ||||
def gen_password(self, length, type_=None): | ||||
if type_ is None: | ||||
type_ = self.ALPHABETS_FULL | ||||
r2822 | self.passwd = ''.join([random.choice(type_) for _ in range(length)]) | |||
r1 | return self.passwd | |||
class _RhodeCodeCryptoBase(object): | ||||
r1421 | ENC_PREF = None | |||
r1 | ||||
def hash_create(self, str_): | ||||
""" | ||||
hash the string using | ||||
:param str_: password to hash | ||||
""" | ||||
raise NotImplementedError | ||||
def hash_check_with_upgrade(self, password, hashed): | ||||
""" | ||||
Returns tuple in which first element is boolean that states that | ||||
given password matches it's hashed version, and the second is new hash | ||||
of the password, in case this password should be migrated to new | ||||
cipher. | ||||
""" | ||||
checked_hash = self.hash_check(password, hashed) | ||||
return checked_hash, None | ||||
def hash_check(self, password, hashed): | ||||
""" | ||||
Checks matching password with it's hashed value. | ||||
:param password: password | ||||
:param hashed: password in hashed form | ||||
""" | ||||
raise NotImplementedError | ||||
def _assert_bytes(self, value): | ||||
""" | ||||
Passing in an `unicode` object can lead to hard to detect issues | ||||
if passwords contain non-ascii characters. Doing a type check | ||||
during runtime, so that such mistakes are detected early on. | ||||
""" | ||||
if not isinstance(value, str): | ||||
raise TypeError( | ||||
"Bytestring required as input, got %r." % (value, )) | ||||
class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase): | ||||
r1626 | ENC_PREF = ('$2a$10', '$2b$10') | |||
r1 | ||||
def hash_create(self, str_): | ||||
self._assert_bytes(str_) | ||||
return bcrypt.hashpw(str_, bcrypt.gensalt(10)) | ||||
def hash_check_with_upgrade(self, password, hashed): | ||||
""" | ||||
Returns tuple in which first element is boolean that states that | ||||
given password matches it's hashed version, and the second is new hash | ||||
of the password, in case this password should be migrated to new | ||||
cipher. | ||||
This implements special upgrade logic which works like that: | ||||
- check if the given password == bcrypted hash, if yes then we | ||||
properly used password and it was already in bcrypt. Proceed | ||||
without any changes | ||||
- if bcrypt hash check is not working try with sha256. If hash compare | ||||
is ok, it means we using correct but old hashed password. indicate | ||||
hash change and proceed | ||||
""" | ||||
new_hash = None | ||||
# regular pw check | ||||
password_match_bcrypt = self.hash_check(password, hashed) | ||||
# now we want to know if the password was maybe from sha256 | ||||
# basically calling _RhodeCodeCryptoSha256().hash_check() | ||||
if not password_match_bcrypt: | ||||
if _RhodeCodeCryptoSha256().hash_check(password, hashed): | ||||
new_hash = self.hash_create(password) # make new bcrypt hash | ||||
password_match_bcrypt = True | ||||
return password_match_bcrypt, new_hash | ||||
def hash_check(self, password, hashed): | ||||
""" | ||||
Checks matching password with it's hashed value. | ||||
:param password: password | ||||
:param hashed: password in hashed form | ||||
""" | ||||
self._assert_bytes(password) | ||||
try: | ||||
return bcrypt.hashpw(password, hashed) == hashed | ||||
except ValueError as e: | ||||
# we're having a invalid salt here probably, we should not crash | ||||
# just return with False as it would be a wrong password. | ||||
log.debug('Failed to check password hash using bcrypt %s', | ||||
safe_str(e)) | ||||
return False | ||||
class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase): | ||||
r1421 | ENC_PREF = '_' | |||
r1 | ||||
def hash_create(self, str_): | ||||
self._assert_bytes(str_) | ||||
return hashlib.sha256(str_).hexdigest() | ||||
def hash_check(self, password, hashed): | ||||
""" | ||||
Checks matching password with it's hashed value. | ||||
:param password: password | ||||
:param hashed: password in hashed form | ||||
""" | ||||
self._assert_bytes(password) | ||||
return hashlib.sha256(password).hexdigest() == hashed | ||||
r2836 | class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase): | |||
r1421 | ENC_PREF = '_' | |||
r1 | ||||
def hash_create(self, str_): | ||||
self._assert_bytes(str_) | ||||
r2836 | return sha1(str_) | |||
r1 | ||||
def hash_check(self, password, hashed): | ||||
""" | ||||
Checks matching password with it's hashed value. | ||||
:param password: password | ||||
:param hashed: password in hashed form | ||||
""" | ||||
self._assert_bytes(password) | ||||
r2836 | return sha1(password) == hashed | |||
r1 | ||||
def crypto_backend(): | ||||
""" | ||||
Return the matching crypto backend. | ||||
r2836 | Selection is based on if we run tests or not, we pick sha1-test backend to run | |||
r1 | tests faster since BCRYPT is expensive to calculate | |||
""" | ||||
if rhodecode.is_test: | ||||
r2836 | RhodeCodeCrypto = _RhodeCodeCryptoTest() | |||
r1 | else: | |||
RhodeCodeCrypto = _RhodeCodeCryptoBCrypt() | ||||
return RhodeCodeCrypto | ||||
def get_crypt_password(password): | ||||
""" | ||||
Create the hash of `password` with the active crypto backend. | ||||
:param password: The cleartext password. | ||||
:type password: unicode | ||||
""" | ||||
password = safe_str(password) | ||||
return crypto_backend().hash_create(password) | ||||
def check_password(password, hashed): | ||||
""" | ||||
Check if the value in `password` matches the hash in `hashed`. | ||||
:param password: The cleartext password. | ||||
:type password: unicode | ||||
:param hashed: The expected hashed version of the password. | ||||
:type hashed: The hash has to be passed in in text representation. | ||||
""" | ||||
password = safe_str(password) | ||||
return crypto_backend().hash_check(password, hashed) | ||||
def generate_auth_token(data, salt=None): | ||||
""" | ||||
Generates API KEY from given string | ||||
""" | ||||
if salt is None: | ||||
salt = os.urandom(16) | ||||
return hashlib.sha1(safe_str(data) + salt).hexdigest() | ||||
r2074 | def get_came_from(request): | |||
""" | ||||
get query_string+path from request sanitized after removing auth_token | ||||
""" | ||||
_req = request | ||||
path = _req.path | ||||
if 'auth_token' in _req.GET: | ||||
# sanitize the request and remove auth_token for redirection | ||||
_req.GET.pop('auth_token') | ||||
qs = _req.query_string | ||||
if qs: | ||||
path += '?' + qs | ||||
return path | ||||
r1 | class CookieStoreWrapper(object): | |||
def __init__(self, cookie_store): | ||||
self.cookie_store = cookie_store | ||||
def __repr__(self): | ||||
return 'CookieStore<%s>' % (self.cookie_store) | ||||
def get(self, key, other=None): | ||||
if isinstance(self.cookie_store, dict): | ||||
return self.cookie_store.get(key, other) | ||||
elif isinstance(self.cookie_store, AuthUser): | ||||
return self.cookie_store.__dict__.get(key, other) | ||||
def _cached_perms_data(user_id, scope, user_is_admin, | ||||
r2065 | user_inherit_default_permissions, explicit, algo, | |||
calculate_super_admin): | ||||
r1 | ||||
permissions = PermissionCalculator( | ||||
user_id, scope, user_is_admin, user_inherit_default_permissions, | ||||
r2065 | explicit, algo, calculate_super_admin) | |||
r1 | return permissions.calculate() | |||
r1791 | ||||
class PermOrigin(object): | ||||
r2065 | SUPER_ADMIN = 'superadmin' | |||
r3090 | ARCHIVED = 'archived' | |||
r100 | ||||
REPO_USER = 'user:%s' | ||||
REPO_USERGROUP = 'usergroup:%s' | ||||
REPO_OWNER = 'repo.owner' | ||||
REPO_DEFAULT = 'repo.default' | ||||
r2063 | REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit' | |||
r100 | REPO_PRIVATE = 'repo.private' | |||
REPOGROUP_USER = 'user:%s' | ||||
REPOGROUP_USERGROUP = 'usergroup:%s' | ||||
REPOGROUP_OWNER = 'group.owner' | ||||
REPOGROUP_DEFAULT = 'group.default' | ||||
r2063 | REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit' | |||
r100 | ||||
USERGROUP_USER = 'user:%s' | ||||
USERGROUP_USERGROUP = 'usergroup:%s' | ||||
USERGROUP_OWNER = 'usergroup.owner' | ||||
USERGROUP_DEFAULT = 'usergroup.default' | ||||
r2063 | USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit' | |||
r100 | ||||
class PermOriginDict(dict): | ||||
""" | ||||
A special dict used for tracking permissions along with their origins. | ||||
`__setitem__` has been overridden to expect a tuple(perm, origin) | ||||
`__getitem__` will return only the perm | ||||
`.perm_origin_stack` will return the stack of (perm, origin) set per key | ||||
>>> perms = PermOriginDict() | ||||
>>> perms['resource'] = 'read', 'default' | ||||
>>> perms['resource'] | ||||
'read' | ||||
>>> perms['resource'] = 'write', 'admin' | ||||
>>> perms['resource'] | ||||
'write' | ||||
>>> perms.perm_origin_stack | ||||
{'resource': [('read', 'default'), ('write', 'admin')]} | ||||
""" | ||||
def __init__(self, *args, **kw): | ||||
dict.__init__(self, *args, **kw) | ||||
r2063 | self.perm_origin_stack = collections.OrderedDict() | |||
r100 | ||||
def __setitem__(self, key, (perm, origin)): | ||||
r2975 | self.perm_origin_stack.setdefault(key, []).append( | |||
(perm, origin)) | ||||
r100 | dict.__setitem__(self, key, perm) | |||
r1 | ||||
r2975 | class BranchPermOriginDict(PermOriginDict): | |||
""" | ||||
Dedicated branch permissions dict, with tracking of patterns and origins. | ||||
>>> perms = BranchPermOriginDict() | ||||
>>> perms['resource'] = '*pattern', 'read', 'default' | ||||
>>> perms['resource'] | ||||
{'*pattern': 'read'} | ||||
>>> perms['resource'] = '*pattern', 'write', 'admin' | ||||
>>> perms['resource'] | ||||
{'*pattern': 'write'} | ||||
>>> perms.perm_origin_stack | ||||
{'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}} | ||||
""" | ||||
def __setitem__(self, key, (pattern, perm, origin)): | ||||
self.perm_origin_stack.setdefault(key, {}) \ | ||||
.setdefault(pattern, []).append((perm, origin)) | ||||
if key in self: | ||||
self[key].__setitem__(pattern, perm) | ||||
else: | ||||
patterns = collections.OrderedDict() | ||||
patterns[pattern] = perm | ||||
dict.__setitem__(self, key, patterns) | ||||
r1 | class PermissionCalculator(object): | |||
def __init__( | ||||
self, user_id, scope, user_is_admin, | ||||
r2065 | user_inherit_default_permissions, explicit, algo, | |||
r2979 | calculate_super_admin_as_user=False): | |||
r2065 | ||||
r1 | self.user_id = user_id | |||
self.user_is_admin = user_is_admin | ||||
self.inherit_default_permissions = user_inherit_default_permissions | ||||
self.explicit = explicit | ||||
self.algo = algo | ||||
r2979 | self.calculate_super_admin_as_user = calculate_super_admin_as_user | |||
r1 | ||||
scope = scope or {} | ||||
self.scope_repo_id = scope.get('repo_id') | ||||
self.scope_repo_group_id = scope.get('repo_group_id') | ||||
self.scope_user_group_id = scope.get('user_group_id') | ||||
self.default_user_id = User.get_default_user(cache=True).user_id | ||||
r100 | self.permissions_repositories = PermOriginDict() | |||
self.permissions_repository_groups = PermOriginDict() | ||||
self.permissions_user_groups = PermOriginDict() | ||||
r2975 | self.permissions_repository_branches = BranchPermOriginDict() | |||
r1 | self.permissions_global = set() | |||
self.default_repo_perms = Permission.get_default_repo_perms( | ||||
self.default_user_id, self.scope_repo_id) | ||||
self.default_repo_groups_perms = Permission.get_default_group_perms( | ||||
self.default_user_id, self.scope_repo_group_id) | ||||
self.default_user_group_perms = \ | ||||
Permission.get_default_user_group_perms( | ||||
self.default_user_id, self.scope_user_group_id) | ||||
r2975 | # default branch perms | |||
self.default_branch_repo_perms = \ | ||||
Permission.get_default_repo_branch_perms( | ||||
self.default_user_id, self.scope_repo_id) | ||||
r1 | def calculate(self): | |||
r2979 | if self.user_is_admin and not self.calculate_super_admin_as_user: | |||
return self._calculate_admin_permissions() | ||||
r1 | ||||
self._calculate_global_default_permissions() | ||||
self._calculate_global_permissions() | ||||
self._calculate_default_permissions() | ||||
self._calculate_repository_permissions() | ||||
r2975 | self._calculate_repository_branch_permissions() | |||
r1 | self._calculate_repository_group_permissions() | |||
self._calculate_user_group_permissions() | ||||
return self._permission_structure() | ||||
r2979 | def _calculate_admin_permissions(self): | |||
r1 | """ | |||
admin user have all default rights for repositories | ||||
and groups set to admin | ||||
""" | ||||
self.permissions_global.add('hg.admin') | ||||
self.permissions_global.add('hg.create.write_on_repogroup.true') | ||||
# repositories | ||||
for perm in self.default_repo_perms: | ||||
r_k = perm.UserRepoToPerm.repository.repo_name | ||||
r3090 | archived = perm.UserRepoToPerm.repository.archived | |||
r1 | p = 'repository.admin' | |||
r2065 | self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN | |||
r3090 | # special case for archived repositories, which we block still even for | |||
# super admins | ||||
if archived: | ||||
p = 'repository.read' | ||||
self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED | ||||
r1 | ||||
# repository groups | ||||
for perm in self.default_repo_groups_perms: | ||||
rg_k = perm.UserRepoGroupToPerm.group.group_name | ||||
p = 'group.admin' | ||||
r2065 | self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN | |||
r1 | ||||
# user groups | ||||
for perm in self.default_user_group_perms: | ||||
u_k = perm.UserUserGroupToPerm.user_group.users_group_name | ||||
p = 'usergroup.admin' | ||||
r2065 | self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN | |||
r1 | ||||
r2975 | # branch permissions | |||
r2979 | # since super-admin also can have custom rule permissions | |||
# we *always* need to calculate those inherited from default, and also explicit | ||||
self._calculate_default_permissions_repository_branches( | ||||
user_inherit_object_permissions=False) | ||||
self._calculate_repository_branch_permissions() | ||||
r2975 | ||||
r1 | return self._permission_structure() | |||
def _calculate_global_default_permissions(self): | ||||
""" | ||||
global permissions taken from the default user | ||||
""" | ||||
default_global_perms = UserToPerm.query()\ | ||||
.filter(UserToPerm.user_id == self.default_user_id)\ | ||||
.options(joinedload(UserToPerm.permission)) | ||||
for perm in default_global_perms: | ||||
self.permissions_global.add(perm.permission.permission_name) | ||||
r2065 | if self.user_is_admin: | |||
self.permissions_global.add('hg.admin') | ||||
self.permissions_global.add('hg.create.write_on_repogroup.true') | ||||
r1 | def _calculate_global_permissions(self): | |||
""" | ||||
Set global system permissions with user permissions or permissions | ||||
taken from the user groups of the current user. | ||||
The permissions include repo creating, repo group creating, forking | ||||
etc. | ||||
""" | ||||
# now we read the defined permissions and overwrite what we have set | ||||
# before those can be configured from groups or users explicitly. | ||||
r2975 | # In case we want to extend this list we should make sure | |||
# this is in sync with User.DEFAULT_USER_PERMISSIONS definitions | ||||
r1 | _configurable = frozenset([ | |||
'hg.fork.none', 'hg.fork.repository', | ||||
'hg.create.none', 'hg.create.repository', | ||||
'hg.usergroup.create.false', 'hg.usergroup.create.true', | ||||
'hg.repogroup.create.false', 'hg.repogroup.create.true', | ||||
r2975 | 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true', | |||
r1 | 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true' | |||
]) | ||||
# USER GROUPS comes first user group global permissions | ||||
user_perms_from_users_groups = Session().query(UserGroupToPerm)\ | ||||
.options(joinedload(UserGroupToPerm.permission))\ | ||||
.join((UserGroupMember, UserGroupToPerm.users_group_id == | ||||
UserGroupMember.users_group_id))\ | ||||
.filter(UserGroupMember.user_id == self.user_id)\ | ||||
.order_by(UserGroupToPerm.users_group_id)\ | ||||
.all() | ||||
# need to group here by groups since user can be in more than | ||||
# one group, so we get all groups | ||||
_explicit_grouped_perms = [ | ||||
[x, list(y)] for x, y in | ||||
itertools.groupby(user_perms_from_users_groups, | ||||
lambda _x: _x.users_group)] | ||||
for gr, perms in _explicit_grouped_perms: | ||||
# since user can be in multiple groups iterate over them and | ||||
# select the lowest permissions first (more explicit) | ||||
r2975 | # TODO(marcink): do this^^ | |||
r1 | ||||
# group doesn't inherit default permissions so we actually set them | ||||
if not gr.inherit_default_permissions: | ||||
# NEED TO IGNORE all previously set configurable permissions | ||||
# and replace them with explicitly set from this user | ||||
# group permissions | ||||
self.permissions_global = self.permissions_global.difference( | ||||
_configurable) | ||||
for perm in perms: | ||||
r100 | self.permissions_global.add(perm.permission.permission_name) | |||
r1 | ||||
# user explicit global permissions | ||||
user_perms = Session().query(UserToPerm)\ | ||||
.options(joinedload(UserToPerm.permission))\ | ||||
.filter(UserToPerm.user_id == self.user_id).all() | ||||
if not self.inherit_default_permissions: | ||||
# NEED TO IGNORE all configurable permissions and | ||||
# replace them with explicitly set from this user permissions | ||||
self.permissions_global = self.permissions_global.difference( | ||||
_configurable) | ||||
for perm in user_perms: | ||||
self.permissions_global.add(perm.permission.permission_name) | ||||
r2979 | def _calculate_default_permissions_repositories(self, user_inherit_object_permissions): | |||
r1 | for perm in self.default_repo_perms: | |||
r_k = perm.UserRepoToPerm.repository.repo_name | ||||
r3090 | archived = perm.UserRepoToPerm.repository.archived | |||
r2063 | p = perm.Permission.permission_name | |||
r100 | o = PermOrigin.REPO_DEFAULT | |||
r2063 | self.permissions_repositories[r_k] = p, o | |||
# if we decide this user isn't inheriting permissions from | ||||
# default user we set him to .none so only explicit | ||||
# permissions work | ||||
if not user_inherit_object_permissions: | ||||
p = 'repository.none' | ||||
o = PermOrigin.REPO_DEFAULT_NO_INHERIT | ||||
r2066 | self.permissions_repositories[r_k] = p, o | |||
r2063 | ||||
r1 | if perm.Repository.private and not ( | |||
perm.Repository.user_id == self.user_id): | ||||
# disable defaults for private repos, | ||||
p = 'repository.none' | ||||
r100 | o = PermOrigin.REPO_PRIVATE | |||
r2063 | self.permissions_repositories[r_k] = p, o | |||
r1 | elif perm.Repository.user_id == self.user_id: | |||
# set admin if owner | ||||
p = 'repository.admin' | ||||
r100 | o = PermOrigin.REPO_OWNER | |||
r2063 | self.permissions_repositories[r_k] = p, o | |||
r1 | ||||
r2065 | if self.user_is_admin: | |||
p = 'repository.admin' | ||||
o = PermOrigin.SUPER_ADMIN | ||||
self.permissions_repositories[r_k] = p, o | ||||
r1 | ||||
r3090 | # finally in case of archived repositories, we downgrade higher | |||
# permissions to read | ||||
if archived: | ||||
current_perm = self.permissions_repositories[r_k] | ||||
if current_perm in ['repository.write', 'repository.admin']: | ||||
p = 'repository.read' | ||||
o = PermOrigin.ARCHIVED | ||||
self.permissions_repositories[r_k] = p, o | ||||
r2979 | def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions): | |||
r2975 | for perm in self.default_branch_repo_perms: | |||
r_k = perm.UserRepoToPerm.repository.repo_name | ||||
p = perm.Permission.permission_name | ||||
pattern = perm.UserToRepoBranchPermission.branch_pattern | ||||
o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username | ||||
if not self.explicit: | ||||
# TODO(marcink): fix this for multiple entries | ||||
cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none' | ||||
p = self._choose_permission(p, cur_perm) | ||||
# NOTE(marcink): register all pattern/perm instances in this | ||||
# special dict that aggregates entries | ||||
self.permissions_repository_branches[r_k] = pattern, p, o | ||||
r2979 | def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions): | |||
r1 | for perm in self.default_repo_groups_perms: | |||
rg_k = perm.UserRepoGroupToPerm.group.group_name | ||||
r2063 | p = perm.Permission.permission_name | |||
r100 | o = PermOrigin.REPOGROUP_DEFAULT | |||
r2063 | self.permissions_repository_groups[rg_k] = p, o | |||
r1 | ||||
# if we decide this user isn't inheriting permissions from default | ||||
# user we set him to .none so only explicit permissions work | ||||
if not user_inherit_object_permissions: | ||||
p = 'group.none' | ||||
r2063 | o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT | |||
self.permissions_repository_groups[rg_k] = p, o | ||||
if perm.RepoGroup.user_id == self.user_id: | ||||
# set admin if owner | ||||
p = 'group.admin' | ||||
o = PermOrigin.REPOGROUP_OWNER | ||||
self.permissions_repository_groups[rg_k] = p, o | ||||
r1 | ||||
r2065 | if self.user_is_admin: | |||
p = 'group.admin' | ||||
o = PermOrigin.SUPER_ADMIN | ||||
self.permissions_repository_groups[rg_k] = p, o | ||||
r1 | ||||
r2979 | def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions): | |||
r1 | for perm in self.default_user_group_perms: | |||
u_k = perm.UserUserGroupToPerm.user_group.users_group_name | ||||
r2063 | p = perm.Permission.permission_name | |||
r100 | o = PermOrigin.USERGROUP_DEFAULT | |||
r2063 | self.permissions_user_groups[u_k] = p, o | |||
r1443 | ||||
r1 | # if we decide this user isn't inheriting permissions from default | |||
# user we set him to .none so only explicit permissions work | ||||
if not user_inherit_object_permissions: | ||||
p = 'usergroup.none' | ||||
r2063 | o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT | |||
self.permissions_user_groups[u_k] = p, o | ||||
if perm.UserGroup.user_id == self.user_id: | ||||
# set admin if owner | ||||
p = 'usergroup.admin' | ||||
o = PermOrigin.USERGROUP_OWNER | ||||
self.permissions_user_groups[u_k] = p, o | ||||
r1 | ||||
r2065 | if self.user_is_admin: | |||
p = 'usergroup.admin' | ||||
o = PermOrigin.SUPER_ADMIN | ||||
self.permissions_user_groups[u_k] = p, o | ||||
r1 | ||||
r2979 | def _calculate_default_permissions(self): | |||
""" | ||||
Set default user permissions for repositories, repository branches, | ||||
repository groups, user groups taken from the default user. | ||||
Calculate inheritance of object permissions based on what we have now | ||||
in GLOBAL permissions. We check if .false is in GLOBAL since this is | ||||
explicitly set. Inherit is the opposite of .false being there. | ||||
.. note:: | ||||
the syntax is little bit odd but what we need to check here is | ||||
the opposite of .false permission being in the list so even for | ||||
inconsistent state when both .true/.false is there | ||||
.false is more important | ||||
""" | ||||
user_inherit_object_permissions = not ('hg.inherit_default_perms.false' | ||||
in self.permissions_global) | ||||
# default permissions inherited from `default` user permissions | ||||
self._calculate_default_permissions_repositories( | ||||
user_inherit_object_permissions) | ||||
self._calculate_default_permissions_repository_branches( | ||||
user_inherit_object_permissions) | ||||
self._calculate_default_permissions_repository_groups( | ||||
user_inherit_object_permissions) | ||||
self._calculate_default_permissions_user_groups( | ||||
user_inherit_object_permissions) | ||||
r1 | def _calculate_repository_permissions(self): | |||
""" | ||||
Repository permissions for the current user. | ||||
Check if the user is part of user groups for this repository and | ||||
fill in the permission from it. `_choose_permission` decides of which | ||||
permission should be selected based on selected method. | ||||
""" | ||||
# user group for repositories permissions | ||||
user_repo_perms_from_user_group = Permission\ | ||||
.get_default_repo_perms_from_user_group( | ||||
self.user_id, self.scope_repo_id) | ||||
multiple_counter = collections.defaultdict(int) | ||||
for perm in user_repo_perms_from_user_group: | ||||
r_k = perm.UserGroupRepoToPerm.repository.repo_name | ||||
multiple_counter[r_k] += 1 | ||||
p = perm.Permission.permission_name | ||||
r2064 | o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\ | |||
.users_group.users_group_name | ||||
r2063 | ||||
if multiple_counter[r_k] > 1: | ||||
cur_perm = self.permissions_repositories[r_k] | ||||
p = self._choose_permission(p, cur_perm) | ||||
self.permissions_repositories[r_k] = p, o | ||||
r1 | ||||
if perm.Repository.user_id == self.user_id: | ||||
# set admin if owner | ||||
p = 'repository.admin' | ||||
r100 | o = PermOrigin.REPO_OWNER | |||
r2063 | self.permissions_repositories[r_k] = p, o | |||
r1 | ||||
r2065 | if self.user_is_admin: | |||
p = 'repository.admin' | ||||
o = PermOrigin.SUPER_ADMIN | ||||
self.permissions_repositories[r_k] = p, o | ||||
r1 | ||||
# user explicit permissions for repositories, overrides any specified | ||||
# by the group permission | ||||
user_repo_perms = Permission.get_default_repo_perms( | ||||
self.user_id, self.scope_repo_id) | ||||
for perm in user_repo_perms: | ||||
r_k = perm.UserRepoToPerm.repository.repo_name | ||||
r2063 | p = perm.Permission.permission_name | |||
r100 | o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username | |||
r2063 | ||||
if not self.explicit: | ||||
cur_perm = self.permissions_repositories.get( | ||||
r_k, 'repository.none') | ||||
p = self._choose_permission(p, cur_perm) | ||||
self.permissions_repositories[r_k] = p, o | ||||
r1 | if perm.Repository.user_id == self.user_id: | |||
r2063 | # set admin if owner | |||
r1 | p = 'repository.admin' | |||
r100 | o = PermOrigin.REPO_OWNER | |||
r2063 | self.permissions_repositories[r_k] = p, o | |||
r1 | ||||
r2065 | if self.user_is_admin: | |||
p = 'repository.admin' | ||||
o = PermOrigin.SUPER_ADMIN | ||||
self.permissions_repositories[r_k] = p, o | ||||
r1 | ||||
r2975 | def _calculate_repository_branch_permissions(self): | |||
# user group for repositories permissions | ||||
user_repo_branch_perms_from_user_group = Permission\ | ||||
.get_default_repo_branch_perms_from_user_group( | ||||
self.user_id, self.scope_repo_id) | ||||
multiple_counter = collections.defaultdict(int) | ||||
for perm in user_repo_branch_perms_from_user_group: | ||||
r_k = perm.UserGroupRepoToPerm.repository.repo_name | ||||
p = perm.Permission.permission_name | ||||
pattern = perm.UserGroupToRepoBranchPermission.branch_pattern | ||||
o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\ | ||||
.users_group.users_group_name | ||||
multiple_counter[r_k] += 1 | ||||
if multiple_counter[r_k] > 1: | ||||
# TODO(marcink): fix this for multi branch support, and multiple entries | ||||
cur_perm = self.permissions_repository_branches[r_k] | ||||
p = self._choose_permission(p, cur_perm) | ||||
self.permissions_repository_branches[r_k] = pattern, p, o | ||||
# user explicit branch permissions for repositories, overrides | ||||
# any specified by the group permission | ||||
user_repo_branch_perms = Permission.get_default_repo_branch_perms( | ||||
self.user_id, self.scope_repo_id) | ||||
r2979 | ||||
r2975 | for perm in user_repo_branch_perms: | |||
r_k = perm.UserRepoToPerm.repository.repo_name | ||||
p = perm.Permission.permission_name | ||||
pattern = perm.UserToRepoBranchPermission.branch_pattern | ||||
o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username | ||||
if not self.explicit: | ||||
# TODO(marcink): fix this for multiple entries | ||||
cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none' | ||||
p = self._choose_permission(p, cur_perm) | ||||
# NOTE(marcink): register all pattern/perm instances in this | ||||
# special dict that aggregates entries | ||||
self.permissions_repository_branches[r_k] = pattern, p, o | ||||
r1 | def _calculate_repository_group_permissions(self): | |||
""" | ||||
Repository group permissions for the current user. | ||||
Check if the user is part of user groups for repository groups and | ||||
r2063 | fill in the permissions from it. `_choose_permission` decides of which | |||
r1 | permission should be selected based on selected method. | |||
""" | ||||
# user group for repo groups permissions | ||||
user_repo_group_perms_from_user_group = Permission\ | ||||
.get_default_group_perms_from_user_group( | ||||
self.user_id, self.scope_repo_group_id) | ||||
multiple_counter = collections.defaultdict(int) | ||||
for perm in user_repo_group_perms_from_user_group: | ||||
r2064 | rg_k = perm.UserGroupRepoGroupToPerm.group.group_name | |||
multiple_counter[rg_k] += 1 | ||||
o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\ | ||||
.users_group.users_group_name | ||||
r1 | p = perm.Permission.permission_name | |||
r2063 | ||||
r2064 | if multiple_counter[rg_k] > 1: | |||
cur_perm = self.permissions_repository_groups[rg_k] | ||||
r2063 | p = self._choose_permission(p, cur_perm) | |||
r2064 | self.permissions_repository_groups[rg_k] = p, o | |||
r2063 | ||||
r1 | if perm.RepoGroup.user_id == self.user_id: | |||
r1443 | # set admin if owner, even for member of other user group | |||
r1 | p = 'group.admin' | |||
r100 | o = PermOrigin.REPOGROUP_OWNER | |||
r2064 | self.permissions_repository_groups[rg_k] = p, o | |||
r1 | ||||
r2065 | if self.user_is_admin: | |||
p = 'group.admin' | ||||
o = PermOrigin.SUPER_ADMIN | ||||
self.permissions_repository_groups[rg_k] = p, o | ||||
r1 | ||||
# user explicit permissions for repository groups | ||||
user_repo_groups_perms = Permission.get_default_group_perms( | ||||
self.user_id, self.scope_repo_group_id) | ||||
for perm in user_repo_groups_perms: | ||||
rg_k = perm.UserRepoGroupToPerm.group.group_name | ||||
r2064 | o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\ | |||
.user.username | ||||
r2063 | p = perm.Permission.permission_name | |||
if not self.explicit: | ||||
cur_perm = self.permissions_repository_groups.get( | ||||
rg_k, 'group.none') | ||||
p = self._choose_permission(p, cur_perm) | ||||
self.permissions_repository_groups[rg_k] = p, o | ||||
r100 | ||||
r1 | if perm.RepoGroup.user_id == self.user_id: | |||
# set admin if owner | ||||
p = 'group.admin' | ||||
r100 | o = PermOrigin.REPOGROUP_OWNER | |||
r2063 | self.permissions_repository_groups[rg_k] = p, o | |||
r1 | ||||
r2065 | if self.user_is_admin: | |||
p = 'group.admin' | ||||
o = PermOrigin.SUPER_ADMIN | ||||
self.permissions_repository_groups[rg_k] = p, o | ||||
r1 | ||||
def _calculate_user_group_permissions(self): | ||||
""" | ||||
User group permissions for the current user. | ||||
""" | ||||
# user group for user group permissions | ||||
user_group_from_user_group = Permission\ | ||||
.get_default_user_group_perms_from_user_group( | ||||
r1443 | self.user_id, self.scope_user_group_id) | |||
r1 | ||||
multiple_counter = collections.defaultdict(int) | ||||
for perm in user_group_from_user_group: | ||||
r2064 | ug_k = perm.UserGroupUserGroupToPerm\ | |||
r1 | .target_user_group.users_group_name | |||
r2064 | multiple_counter[ug_k] += 1 | |||
o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\ | ||||
r100 | .user_group.users_group_name | |||
r1 | p = perm.Permission.permission_name | |||
r1443 | ||||
r2064 | if multiple_counter[ug_k] > 1: | |||
cur_perm = self.permissions_user_groups[ug_k] | ||||
r2063 | p = self._choose_permission(p, cur_perm) | |||
r2064 | self.permissions_user_groups[ug_k] = p, o | |||
r2063 | ||||
r1443 | if perm.UserGroup.user_id == self.user_id: | |||
# set admin if owner, even for member of other user group | ||||
p = 'usergroup.admin' | ||||
o = PermOrigin.USERGROUP_OWNER | ||||
r2064 | self.permissions_user_groups[ug_k] = p, o | |||
r1 | ||||
r2065 | if self.user_is_admin: | |||
p = 'usergroup.admin' | ||||
o = PermOrigin.SUPER_ADMIN | ||||
self.permissions_user_groups[ug_k] = p, o | ||||
r1 | ||||
# user explicit permission for user groups | ||||
user_user_groups_perms = Permission.get_default_user_group_perms( | ||||
self.user_id, self.scope_user_group_id) | ||||
for perm in user_user_groups_perms: | ||||
r100 | ug_k = perm.UserUserGroupToPerm.user_group.users_group_name | |||
r2064 | o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\ | |||
.user.username | ||||
r2063 | p = perm.Permission.permission_name | |||
if not self.explicit: | ||||
cur_perm = self.permissions_user_groups.get( | ||||
ug_k, 'usergroup.none') | ||||
p = self._choose_permission(p, cur_perm) | ||||
self.permissions_user_groups[ug_k] = p, o | ||||
r1443 | ||||
if perm.UserGroup.user_id == self.user_id: | ||||
# set admin if owner | ||||
p = 'usergroup.admin' | ||||
o = PermOrigin.USERGROUP_OWNER | ||||
r2063 | self.permissions_user_groups[ug_k] = p, o | |||
r1 | ||||
r2065 | if self.user_is_admin: | |||
p = 'usergroup.admin' | ||||
o = PermOrigin.SUPER_ADMIN | ||||
self.permissions_user_groups[ug_k] = p, o | ||||
r1 | ||||
def _choose_permission(self, new_perm, cur_perm): | ||||
new_perm_val = Permission.PERM_WEIGHTS[new_perm] | ||||
cur_perm_val = Permission.PERM_WEIGHTS[cur_perm] | ||||
if self.algo == 'higherwin': | ||||
if new_perm_val > cur_perm_val: | ||||
return new_perm | ||||
return cur_perm | ||||
elif self.algo == 'lowerwin': | ||||
if new_perm_val < cur_perm_val: | ||||
return new_perm | ||||
return cur_perm | ||||
def _permission_structure(self): | ||||
return { | ||||
'global': self.permissions_global, | ||||
'repositories': self.permissions_repositories, | ||||
r2975 | 'repository_branches': self.permissions_repository_branches, | |||
r1 | 'repositories_groups': self.permissions_repository_groups, | |||
'user_groups': self.permissions_user_groups, | ||||
} | ||||
r1995 | def allowed_auth_token_access(view_name, auth_token, whitelist=None): | |||
r1 | """ | |||
Check if given controller_name is in whitelist of auth token access | ||||
""" | ||||
if not whitelist: | ||||
from rhodecode import CONFIG | ||||
whitelist = aslist( | ||||
CONFIG.get('api_access_controllers_whitelist'), sep=',') | ||||
r1996 | # backward compat translation | |||
compat = { | ||||
# old controller, new VIEW | ||||
'ChangesetController:*': 'RepoCommitsView:*', | ||||
'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch', | ||||
'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw', | ||||
'FilesController:raw': 'RepoCommitsView:repo_commit_raw', | ||||
'FilesController:archivefile': 'RepoFilesView:repo_archivefile', | ||||
'GistsController:*': 'GistView:*', | ||||
} | ||||
r1 | ||||
r1995 | log.debug( | |||
r3061 | 'Allowed views for AUTH TOKEN access: %s', whitelist) | |||
r1 | auth_token_access_valid = False | |||
for entry in whitelist: | ||||
r1995 | token_match = True | |||
r1996 | if entry in compat: | |||
# translate from old Controllers to Pyramid Views | ||||
entry = compat[entry] | ||||
r1995 | if '@' in entry: | |||
# specific AuthToken | ||||
entry, allowed_token = entry.split('@', 1) | ||||
token_match = auth_token == allowed_token | ||||
if fnmatch.fnmatch(view_name, entry) and token_match: | ||||
r1 | auth_token_access_valid = True | |||
break | ||||
if auth_token_access_valid: | ||||
r3061 | log.debug('view: `%s` matches entry in whitelist: %s', | |||
view_name, whitelist) | ||||
r1 | else: | |||
r1951 | msg = ('view: `%s` does *NOT* match any entry in whitelist: %s' | |||
% (view_name, whitelist)) | ||||
r1 | if auth_token: | |||
# if we use auth token key and don't have access it's a warning | ||||
log.warning(msg) | ||||
else: | ||||
log.debug(msg) | ||||
return auth_token_access_valid | ||||
class AuthUser(object): | ||||
""" | ||||
A simple object that handles all attributes of user in RhodeCode | ||||
It does lookup based on API key,given user, or user present in session | ||||
Then it fills all required information for such user. It also checks if | ||||
anonymous access is enabled and if so, it returns default user as logged in | ||||
""" | ||||
GLOBAL_PERMS = [x[0] for x in Permission.PERMS] | ||||
def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None): | ||||
self.user_id = user_id | ||||
self._api_key = api_key | ||||
self.api_key = None | ||||
self.username = username | ||||
self.ip_addr = ip_addr | ||||
self.name = '' | ||||
self.lastname = '' | ||||
r1815 | self.first_name = '' | |||
self.last_name = '' | ||||
r1 | self.email = '' | |||
self.is_authenticated = False | ||||
self.admin = False | ||||
self.inherit_default_permissions = False | ||||
self.password = '' | ||||
self.anonymous_user = None # propagated on propagate_data | ||||
self.propagate_data() | ||||
self._instance = None | ||||
self._permissions_scoped_cache = {} # used to bind scoped calculation | ||||
@LazyProperty | ||||
def permissions(self): | ||||
r2979 | return self.get_perms(user=self, cache=None) | |||
r1 | ||||
r2194 | @LazyProperty | |||
def permissions_safe(self): | ||||
""" | ||||
Filtered permissions excluding not allowed repositories | ||||
""" | ||||
r2979 | perms = self.get_perms(user=self, cache=None) | |||
r2194 | ||||
perms['repositories'] = { | ||||
r2822 | k: v for k, v in perms['repositories'].items() | |||
r2194 | if v != 'repository.none'} | |||
perms['repositories_groups'] = { | ||||
r2822 | k: v for k, v in perms['repositories_groups'].items() | |||
r2194 | if v != 'group.none'} | |||
perms['user_groups'] = { | ||||
r2822 | k: v for k, v in perms['user_groups'].items() | |||
r2194 | if v != 'usergroup.none'} | |||
r2975 | perms['repository_branches'] = { | |||
k: v for k, v in perms['repository_branches'].iteritems() | ||||
if v != 'branch.none'} | ||||
r2194 | return perms | |||
r2157 | @LazyProperty | |||
r2065 | def permissions_full_details(self): | |||
return self.get_perms( | ||||
r2979 | user=self, cache=None, calculate_super_admin=True) | |||
r2065 | ||||
r1 | def permissions_with_scope(self, scope): | |||
""" | ||||
Call the get_perms function with scoped data. The scope in that function | ||||
narrows the SQL calls to the given ID of objects resulting in fetching | ||||
Just particular permission we want to obtain. If scope is an empty dict | ||||
then it basically narrows the scope to GLOBAL permissions only. | ||||
:param scope: dict | ||||
""" | ||||
if 'repo_name' in scope: | ||||
obj = Repository.get_by_repo_name(scope['repo_name']) | ||||
if obj: | ||||
scope['repo_id'] = obj.repo_id | ||||
r2845 | _scope = collections.OrderedDict() | |||
_scope['repo_id'] = -1 | ||||
_scope['user_group_id'] = -1 | ||||
_scope['repo_group_id'] = -1 | ||||
for k in sorted(scope.keys()): | ||||
_scope[k] = scope[k] | ||||
# store in cache to mimic how the @LazyProperty works, | ||||
# the difference here is that we use the unique key calculated | ||||
# from params and values | ||||
r2979 | return self.get_perms(user=self, cache=None, scope=_scope) | |||
r1 | ||||
def get_instance(self): | ||||
return User.get(self.user_id) | ||||
def propagate_data(self): | ||||
""" | ||||
Fills in user data and propagates values to this instance. Maps fetched | ||||
user attributes to this class instance attributes | ||||
""" | ||||
r1953 | log.debug('AuthUser: starting data propagation for new potential user') | |||
r1 | user_model = UserModel() | |||
anon_user = self.anonymous_user = User.get_default_user(cache=True) | ||||
is_user_loaded = False | ||||
# lookup by userid | ||||
if self.user_id is not None and self.user_id != anon_user.user_id: | ||||
r1955 | log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id) | |||
r1 | is_user_loaded = user_model.fill_data(self, user_id=self.user_id) | |||
# try go get user by api key | ||||
elif self._api_key and self._api_key != anon_user.api_key: | ||||
r1955 | log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key) | |||
r1 | is_user_loaded = user_model.fill_data(self, api_key=self._api_key) | |||
# lookup by username | ||||
elif self.username: | ||||
r1955 | log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username) | |||
r1 | is_user_loaded = user_model.fill_data(self, username=self.username) | |||
else: | ||||
r1955 | log.debug('No data in %s that could been used to log in', self) | |||
r1 | ||||
if not is_user_loaded: | ||||
r2114 | log.debug( | |||
'Failed to load user. Fallback to default user %s', anon_user) | ||||
r1 | # if we cannot authenticate user try anonymous | |||
if anon_user.active: | ||||
r2114 | log.debug('default user is active, using it as a session user') | |||
r1 | user_model.fill_data(self, user_id=anon_user.user_id) | |||
# then we set this user is logged in | ||||
self.is_authenticated = True | ||||
else: | ||||
r2114 | log.debug('default user is NOT active') | |||
r1 | # in case of disabled anonymous user we reset some of the | |||
# parameters so such user is "corrupted", skipping the fill_data | ||||
for attr in ['user_id', 'username', 'admin', 'active']: | ||||
setattr(self, attr, None) | ||||
self.is_authenticated = False | ||||
if not self.username: | ||||
self.username = 'None' | ||||
r1955 | log.debug('AuthUser: propagated user is now %s', self) | |||
r1 | ||||
def get_perms(self, user, scope=None, explicit=True, algo='higherwin', | ||||
r2979 | calculate_super_admin=False, cache=None): | |||
r1 | """ | |||
Fills user permission attribute with permissions taken from database | ||||
works for permissions given for repositories, and for permissions that | ||||
are granted to groups | ||||
:param user: instance of User object from database | ||||
:param explicit: In case there are permissions both for user and a group | ||||
that user is part of, explicit flag will defiine if user will | ||||
explicitly override permissions from group, if it's False it will | ||||
make decision based on the algo | ||||
:param algo: algorithm to decide what permission should be choose if | ||||
it's multiple defined, eg user in two different groups. It also | ||||
decides if explicit flag is turned off how to specify the permission | ||||
for case when user is in a group + have defined separate permission | ||||
r2979 | :param calculate_super_admin: calculate permissions for super-admin in the | |||
same way as for regular user without speedups | ||||
:param cache: Use caching for calculation, None = let the cache backend decide | ||||
r1 | """ | |||
user_id = user.user_id | ||||
user_is_admin = user.is_admin | ||||
# inheritance of global permissions like create repo/fork repo etc | ||||
user_inherit_default_permissions = user.inherit_default_permissions | ||||
r2796 | cache_seconds = safe_int( | |||
r2845 | rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time')) | |||
r2979 | if cache is None: | |||
# let the backend cache decide | ||||
cache_on = cache_seconds > 0 | ||||
else: | ||||
cache_on = cache | ||||
r2796 | log.debug( | |||
r2813 | 'Computing PERMISSION tree for user %s scope `%s` ' | |||
r3061 | 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0) | |||
r2845 | ||||
cache_namespace_uid = 'cache_user_auth.{}'.format(user_id) | ||||
region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid) | ||||
r2892 | @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, | |||
condition=cache_on) | ||||
r2845 | def compute_perm_tree(cache_name, | |||
user_id, scope, user_is_admin,user_inherit_default_permissions, | ||||
explicit, algo, calculate_super_admin): | ||||
return _cached_perms_data( | ||||
user_id, scope, user_is_admin, user_inherit_default_permissions, | ||||
explicit, algo, calculate_super_admin) | ||||
r2813 | start = time.time() | |||
r2979 | result = compute_perm_tree( | |||
'permissions', user_id, scope, user_is_admin, | ||||
user_inherit_default_permissions, explicit, algo, | ||||
calculate_super_admin) | ||||
r1 | ||||
result_repr = [] | ||||
for k in result: | ||||
result_repr.append((k, len(result[k]))) | ||||
r2813 | total = time.time() - start | |||
r3061 | log.debug('PERMISSION tree for user %s computed in %.3fs: %s', | |||
user, total, result_repr) | ||||
r2845 | ||||
r1 | return result | |||
@property | ||||
r29 | def is_default(self): | |||
return self.username == User.DEFAULT_USER | ||||
@property | ||||
r1 | def is_admin(self): | |||
return self.admin | ||||
@property | ||||
def is_user_object(self): | ||||
return self.user_id is not None | ||||
@property | ||||
def repositories_admin(self): | ||||
""" | ||||
Returns list of repositories you're an admin of | ||||
""" | ||||
r1443 | return [ | |||
r2822 | x[0] for x in self.permissions['repositories'].items() | |||
r1443 | if x[1] == 'repository.admin'] | |||
r1 | ||||
@property | ||||
def repository_groups_admin(self): | ||||
""" | ||||
Returns list of repository groups you're an admin of | ||||
""" | ||||
r1443 | return [ | |||
r2822 | x[0] for x in self.permissions['repositories_groups'].items() | |||
r1443 | if x[1] == 'group.admin'] | |||
r1 | ||||
@property | ||||
def user_groups_admin(self): | ||||
""" | ||||
Returns list of user groups you're an admin of | ||||
""" | ||||
r1443 | return [ | |||
r2822 | x[0] for x in self.permissions['user_groups'].items() | |||
r1443 | if x[1] == 'usergroup.admin'] | |||
r1 | ||||
r2037 | def repo_acl_ids(self, perms=None, name_filter=None, cache=False): | |||
r2036 | """ | |||
Returns list of repository ids that user have access to based on given | ||||
perms. The cache flag should be only used in cases that are used for | ||||
display purposes, NOT IN ANY CASE for permission checks. | ||||
""" | ||||
from rhodecode.model.scm import RepoList | ||||
if not perms: | ||||
perms = [ | ||||
'repository.read', 'repository.write', 'repository.admin'] | ||||
r2822 | def _cached_repo_acl(user_id, perm_def, _name_filter): | |||
r2037 | qry = Repository.query() | |||
r2822 | if _name_filter: | |||
ilike_expression = u'%{}%'.format(safe_unicode(_name_filter)) | ||||
r2037 | qry = qry.filter( | |||
Repository.repo_name.ilike(ilike_expression)) | ||||
return [x.repo_id for x in | ||||
RepoList(qry, perm_set=perm_def)] | ||||
r2036 | ||||
r2845 | return _cached_repo_acl(self.user_id, perms, name_filter) | |||
r2036 | ||||
r2037 | def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False): | |||
r2036 | """ | |||
Returns list of repository group ids that user have access to based on given | ||||
perms. The cache flag should be only used in cases that are used for | ||||
display purposes, NOT IN ANY CASE for permission checks. | ||||
""" | ||||
from rhodecode.model.scm import RepoGroupList | ||||
if not perms: | ||||
perms = [ | ||||
'group.read', 'group.write', 'group.admin'] | ||||
r2822 | def _cached_repo_group_acl(user_id, perm_def, _name_filter): | |||
r2037 | qry = RepoGroup.query() | |||
r2822 | if _name_filter: | |||
ilike_expression = u'%{}%'.format(safe_unicode(_name_filter)) | ||||
r2037 | qry = qry.filter( | |||
RepoGroup.group_name.ilike(ilike_expression)) | ||||
return [x.group_id for x in | ||||
RepoGroupList(qry, perm_set=perm_def)] | ||||
r2036 | ||||
r2845 | return _cached_repo_group_acl(self.user_id, perms, name_filter) | |||
r2036 | ||||
r2037 | def user_group_acl_ids(self, perms=None, name_filter=None, cache=False): | |||
r2036 | """ | |||
Returns list of user group ids that user have access to based on given | ||||
perms. The cache flag should be only used in cases that are used for | ||||
display purposes, NOT IN ANY CASE for permission checks. | ||||
""" | ||||
from rhodecode.model.scm import UserGroupList | ||||
if not perms: | ||||
perms = [ | ||||
'usergroup.read', 'usergroup.write', 'usergroup.admin'] | ||||
r2037 | def _cached_user_group_acl(user_id, perm_def, name_filter): | |||
qry = UserGroup.query() | ||||
if name_filter: | ||||
ilike_expression = u'%{}%'.format(safe_unicode(name_filter)) | ||||
qry = qry.filter( | ||||
UserGroup.users_group_name.ilike(ilike_expression)) | ||||
return [x.users_group_id for x in | ||||
UserGroupList(qry, perm_set=perm_def)] | ||||
r2036 | ||||
r2845 | return _cached_user_group_acl(self.user_id, perms, name_filter) | |||
r2036 | ||||
r1 | @property | |||
def ip_allowed(self): | ||||
""" | ||||
Checks if ip_addr used in constructor is allowed from defined list of | ||||
allowed ip_addresses for user | ||||
:returns: boolean, True if ip is in allowed ip range | ||||
""" | ||||
# check IP | ||||
inherit = self.inherit_default_permissions | ||||
return AuthUser.check_ip_allowed(self.user_id, self.ip_addr, | ||||
inherit_from_default=inherit) | ||||
r1094 | @property | |||
def personal_repo_group(self): | ||||
return RepoGroup.get_user_personal_repo_group(self.user_id) | ||||
r1 | ||||
r2424 | @LazyProperty | |||
def feed_token(self): | ||||
return self.get_instance().feed_token | ||||
r1 | @classmethod | |||
def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default): | ||||
allowed_ips = AuthUser.get_allowed_ips( | ||||
user_id, cache=True, inherit_from_default=inherit_from_default) | ||||
if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips): | ||||
r3061 | log.debug('IP:%s for user %s is in range of %s', | |||
ip_addr, user_id, allowed_ips) | ||||
r1 | return True | |||
else: | ||||
r2820 | log.info('Access for IP:%s forbidden for user %s, ' | |||
r3061 | 'not in %s', ip_addr, user_id, allowed_ips) | |||
r1 | return False | |||
r2979 | def get_branch_permissions(self, repo_name, perms=None): | |||
perms = perms or self.permissions_with_scope({'repo_name': repo_name}) | ||||
r2982 | branch_perms = perms.get('repository_branches', {}) | |||
if not branch_perms: | ||||
return {} | ||||
repo_branch_perms = branch_perms.get(repo_name) | ||||
return repo_branch_perms or {} | ||||
r2979 | ||||
def get_rule_and_branch_permission(self, repo_name, branch_name): | ||||
""" | ||||
Check if this AuthUser has defined any permissions for branches. If any of | ||||
the rules match in order, we return the matching permissions | ||||
""" | ||||
rule = default_perm = '' | ||||
r2982 | repo_branch_perms = self.get_branch_permissions(repo_name=repo_name) | |||
r2979 | if not repo_branch_perms: | |||
return rule, default_perm | ||||
# now calculate the permissions | ||||
for pattern, branch_perm in repo_branch_perms.items(): | ||||
if fnmatch.fnmatch(branch_name, pattern): | ||||
rule = '`{}`=>{}'.format(pattern, branch_perm) | ||||
return rule, branch_perm | ||||
return rule, default_perm | ||||
r1 | def __repr__(self): | |||
return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\ | ||||
% (self.user_id, self.username, self.ip_addr, self.is_authenticated) | ||||
def set_authenticated(self, authenticated=True): | ||||
if self.user_id != self.anonymous_user.user_id: | ||||
self.is_authenticated = authenticated | ||||
def get_cookie_store(self): | ||||
return { | ||||
'username': self.username, | ||||
r2203 | 'password': md5(self.password or ''), | |||
r1 | 'user_id': self.user_id, | |||
'is_authenticated': self.is_authenticated | ||||
} | ||||
@classmethod | ||||
def from_cookie_store(cls, cookie_store): | ||||
""" | ||||
Creates AuthUser from a cookie store | ||||
:param cls: | ||||
:param cookie_store: | ||||
""" | ||||
user_id = cookie_store.get('user_id') | ||||
username = cookie_store.get('username') | ||||
api_key = cookie_store.get('api_key') | ||||
return AuthUser(user_id, api_key, username) | ||||
@classmethod | ||||
def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False): | ||||
_set = set() | ||||
if inherit_from_default: | ||||
r2892 | def_user_id = User.get_default_user(cache=True).user_id | |||
default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id) | ||||
r1 | if cache: | |||
r1749 | default_ips = default_ips.options( | |||
FromCache("sql_cache_short", "get_user_ips_default")) | ||||
r1 | ||||
# populate from default user | ||||
for ip in default_ips: | ||||
try: | ||||
_set.add(ip.ip_addr) | ||||
except ObjectDeletedError: | ||||
# since we use heavy caching sometimes it happens that | ||||
# we get deleted objects here, we just skip them | ||||
pass | ||||
r2838 | # NOTE:(marcink) we don't want to load any rules for empty | |||
# user_id which is the case of access of non logged users when anonymous | ||||
# access is disabled | ||||
user_ips = [] | ||||
if user_id: | ||||
user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id) | ||||
if cache: | ||||
user_ips = user_ips.options( | ||||
FromCache("sql_cache_short", "get_user_ips_%s" % user_id)) | ||||
r1 | ||||
for ip in user_ips: | ||||
try: | ||||
_set.add(ip.ip_addr) | ||||
except ObjectDeletedError: | ||||
# since we use heavy caching sometimes it happens that we get | ||||
# deleted objects here, we just skip them | ||||
pass | ||||
r2821 | return _set or {ip for ip in ['0.0.0.0/0', '::/0']} | |||
r1 | ||||
r2114 | def set_available_permissions(settings): | |||
r1 | """ | |||
r2114 | This function will propagate pyramid settings with all available defined | |||
r1 | permission given in db. We don't want to check each time from db for new | |||
permissions since adding a new permission also requires application restart | ||||
ie. to decorate new views with the newly created permission | ||||
r2114 | :param settings: current pyramid registry.settings | |||
r1 | ||||
""" | ||||
r2114 | log.debug('auth: getting information about all available permissions') | |||
r1 | try: | |||
sa = meta.Session | ||||
all_perms = sa.query(Permission).all() | ||||
r2114 | settings.setdefault('available_permissions', | |||
[x.permission_name for x in all_perms]) | ||||
log.debug('auth: set available permissions') | ||||
r1 | except Exception: | |||
r2114 | log.exception('Failed to fetch permissions from the database.') | |||
raise | ||||
r1 | ||||
r2101 | def get_csrf_token(session, force_new=False, save_if_missing=True): | |||
r1 | """ | |||
Return the current authentication token, creating one if one doesn't | ||||
already exist and the save_if_missing flag is present. | ||||
r2345 | :param session: pass in the pyramid session, else we use the global ones | |||
r1 | :param force_new: force to re-generate the token and store it in session | |||
:param save_if_missing: save the newly generated token if it's missing in | ||||
session | ||||
""" | ||||
r1906 | # NOTE(marcink): probably should be replaced with below one from pyramid 1.9 | |||
# from pyramid.csrf import get_csrf_token | ||||
r1 | ||||
if (csrf_token_key not in session and save_if_missing) or force_new: | ||||
token = hashlib.sha1(str(random.getrandbits(128))).hexdigest() | ||||
session[csrf_token_key] = token | ||||
if hasattr(session, 'save'): | ||||
session.save() | ||||
return session.get(csrf_token_key) | ||||
r2068 | def get_request(perm_class_instance): | |||
r1791 | from pyramid.threadlocal import get_current_request | |||
pyramid_request = get_current_request() | ||||
return pyramid_request | ||||
r1 | # CHECK DECORATORS | |||
class CSRFRequired(object): | ||||
""" | ||||
Decorator for authenticating a form | ||||
This decorator uses an authorization token stored in the client's | ||||
session for prevention of certain Cross-site request forgery (CSRF) | ||||
attacks (See | ||||
http://en.wikipedia.org/wiki/Cross-site_request_forgery for more | ||||
information). | ||||
For use with the ``webhelpers.secure_form`` helper functions. | ||||
""" | ||||
r665 | def __init__(self, token=csrf_token_key, header='X-CSRF-Token', | |||
except_methods=None): | ||||
r1 | self.token = token | |||
self.header = header | ||||
r665 | self.except_methods = except_methods or [] | |||
r1 | ||||
def __call__(self, func): | ||||
return get_cython_compat_decorator(self.__wrapper, func) | ||||
def _get_csrf(self, _request): | ||||
return _request.POST.get(self.token, _request.headers.get(self.header)) | ||||
def check_csrf(self, _request, cur_token): | ||||
supplied_token = self._get_csrf(_request) | ||||
return supplied_token and supplied_token == cur_token | ||||
r1791 | def _get_request(self): | |||
return get_request(self) | ||||
r1 | def __wrapper(self, func, *fargs, **fkwargs): | |||
r1791 | request = self._get_request() | |||
r665 | if request.method in self.except_methods: | |||
return func(*fargs, **fkwargs) | ||||
r2101 | cur_token = get_csrf_token(request.session, save_if_missing=False) | |||
r1 | if self.check_csrf(request, cur_token): | |||
if request.POST.get(self.token): | ||||
del request.POST[self.token] | ||||
return func(*fargs, **fkwargs) | ||||
else: | ||||
reason = 'token-missing' | ||||
supplied_token = self._get_csrf(request) | ||||
if supplied_token and cur_token != supplied_token: | ||||
r1791 | reason = 'token-mismatch [%s:%s]' % ( | |||
cur_token or ''[:6], supplied_token or ''[:6]) | ||||
r1 | ||||
csrf_message = \ | ||||
("Cross-site request forgery detected, request denied. See " | ||||
"http://en.wikipedia.org/wiki/Cross-site_request_forgery for " | ||||
"more information.") | ||||
log.warn('Cross-site request forgery detected, request %r DENIED: %s ' | ||||
'REMOTE_ADDR:%s, HEADERS:%s' % ( | ||||
request, reason, request.remote_addr, request.headers)) | ||||
r1310 | raise HTTPForbidden(explanation=csrf_message) | |||
r1 | ||||
class LoginRequired(object): | ||||
""" | ||||
Must be logged in to execute this function else | ||||
redirect to login page | ||||
:param api_access: if enabled this checks only for valid auth token | ||||
and grants access based on valid token | ||||
""" | ||||
r1421 | def __init__(self, auth_token_access=None): | |||
r1 | self.auth_token_access = auth_token_access | |||
def __call__(self, func): | ||||
return get_cython_compat_decorator(self.__wrapper, func) | ||||
r1775 | def _get_request(self): | |||
r1791 | return get_request(self) | |||
r1775 | ||||
r1 | def __wrapper(self, func, *fargs, **fkwargs): | |||
r35 | from rhodecode.lib import helpers as h | |||
r1 | cls = fargs[0] | |||
user = cls._rhodecode_user | ||||
r1775 | request = self._get_request() | |||
r2345 | _ = request.translate | |||
r1775 | ||||
r1 | loc = "%s:%s" % (cls.__class__.__name__, func.__name__) | |||
r3061 | log.debug('Starting login restriction checks for user: %s', user) | |||
r1 | # check if our IP is allowed | |||
ip_access_valid = True | ||||
if not user.ip_allowed: | ||||
h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))), | ||||
category='warning') | ||||
ip_access_valid = False | ||||
# check if we used an APIKEY and it's a valid one | ||||
r1421 | # defined white-list of controllers which API access will be enabled | |||
r1 | _auth_token = request.GET.get( | |||
'auth_token', '') or request.GET.get('api_key', '') | ||||
auth_token_access_valid = allowed_auth_token_access( | ||||
loc, auth_token=_auth_token) | ||||
# explicit controller is enabled or API is in our whitelist | ||||
if self.auth_token_access or auth_token_access_valid: | ||||
r3061 | log.debug('Checking AUTH TOKEN access for %s', cls) | |||
r1421 | db_user = user.get_instance() | |||
r1 | ||||
r1421 | if db_user: | |||
if self.auth_token_access: | ||||
roles = self.auth_token_access | ||||
else: | ||||
roles = [UserApiKeys.ROLE_HTTP] | ||||
token_match = db_user.authenticate_by_token( | ||||
r1477 | _auth_token, roles=roles) | |||
r1421 | else: | |||
log.debug('Unable to fetch db instance for auth user: %s', user) | ||||
token_match = False | ||||
if _auth_token and token_match: | ||||
r1 | auth_token_access_valid = True | |||
r3061 | log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:]) | |||
r1 | else: | |||
auth_token_access_valid = False | ||||
if not _auth_token: | ||||
log.debug("AUTH TOKEN *NOT* present in request") | ||||
else: | ||||
r3061 | log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:]) | |||
log.debug('Checking if %s is authenticated @ %s', user.username, loc) | ||||
r1 | reason = 'RHODECODE_AUTH' if user.is_authenticated \ | |||
else 'AUTH_TOKEN_AUTH' | ||||
if ip_access_valid and ( | ||||
user.is_authenticated or auth_token_access_valid): | ||||
r3061 | log.info('user %s authenticating with:%s IS authenticated on func %s', | |||
user, reason, loc) | ||||
r1 | ||||
return func(*fargs, **fkwargs) | ||||
else: | ||||
log.warning( | ||||
'user %s authenticating with:%s NOT authenticated on ' | ||||
r3061 | 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s', | |||
user, reason, loc, ip_access_valid, auth_token_access_valid) | ||||
r1 | # we preserve the get PARAM | |||
r2074 | came_from = get_came_from(request) | |||
r3061 | log.debug('redirecting to login page with %s', came_from) | |||
r1790 | raise HTTPFound( | |||
r35 | h.route_path('login', _query={'came_from': came_from})) | |||
r1 | ||||
class NotAnonymous(object): | ||||
""" | ||||
Must be logged in to execute this function else | ||||
r1775 | redirect to login page | |||
""" | ||||
r1 | ||||
def __call__(self, func): | ||||
return get_cython_compat_decorator(self.__wrapper, func) | ||||
r1791 | def _get_request(self): | |||
return get_request(self) | ||||
r1 | def __wrapper(self, func, *fargs, **fkwargs): | |||
r1499 | import rhodecode.lib.helpers as h | |||
r1 | cls = fargs[0] | |||
self.user = cls._rhodecode_user | ||||
r1791 | request = self._get_request() | |||
r2345 | _ = request.translate | |||
r3061 | log.debug('Checking if user is not anonymous @%s', cls) | |||
r1 | ||||
anonymous = self.user.username == User.DEFAULT_USER | ||||
if anonymous: | ||||
r2074 | came_from = get_came_from(request) | |||
r1 | h.flash(_('You need to be a registered user to ' | |||
'perform this action'), | ||||
category='warning') | ||||
r1790 | raise HTTPFound( | |||
r35 | h.route_path('login', _query={'came_from': came_from})) | |||
r1 | else: | |||
return func(*fargs, **fkwargs) | ||||
class PermsDecorator(object): | ||||
""" | ||||
Base class for controller decorators, we extract the current user from | ||||
the class itself, which has it stored in base controllers | ||||
""" | ||||
def __init__(self, *required_perms): | ||||
self.required_perms = set(required_perms) | ||||
def __call__(self, func): | ||||
return get_cython_compat_decorator(self.__wrapper, func) | ||||
r1494 | def _get_request(self): | |||
r1791 | return get_request(self) | |||
r1494 | ||||
r1 | def __wrapper(self, func, *fargs, **fkwargs): | |||
r1499 | import rhodecode.lib.helpers as h | |||
r1 | cls = fargs[0] | |||
_user = cls._rhodecode_user | ||||
r2345 | request = self._get_request() | |||
_ = request.translate | ||||
r1 | ||||
log.debug('checking %s permissions %s for %s %s', | ||||
self.__class__.__name__, self.required_perms, cls, _user) | ||||
if self.check_permissions(_user): | ||||
log.debug('Permission granted for %s %s', cls, _user) | ||||
return func(*fargs, **fkwargs) | ||||
else: | ||||
log.debug('Permission denied for %s %s', cls, _user) | ||||
anonymous = _user.username == User.DEFAULT_USER | ||||
if anonymous: | ||||
r2074 | came_from = get_came_from(self._get_request()) | |||
r1 | h.flash(_('You need to be signed in to view this page'), | |||
r1774 | category='warning') | |||
r1494 | raise HTTPFound( | |||
r35 | h.route_path('login', _query={'came_from': came_from})) | |||
r1 | ||||
else: | ||||
r1817 | # redirect with 404 to prevent resource discovery | |||
raise HTTPNotFound() | ||||
r1 | ||||
def check_permissions(self, user): | ||||
"""Dummy function for overriding""" | ||||
raise NotImplementedError( | ||||
'You have to write this function in child class') | ||||
class HasPermissionAllDecorator(PermsDecorator): | ||||
""" | ||||
Checks for access permission for all given predicates. All of them | ||||
have to be meet in order to fulfill the request | ||||
""" | ||||
def check_permissions(self, user): | ||||
perms = user.permissions_with_scope({}) | ||||
if self.required_perms.issubset(perms['global']): | ||||
return True | ||||
return False | ||||
class HasPermissionAnyDecorator(PermsDecorator): | ||||
""" | ||||
Checks for access permission for any of given predicates. In order to | ||||
fulfill the request any of predicates must be meet | ||||
""" | ||||
def check_permissions(self, user): | ||||
perms = user.permissions_with_scope({}) | ||||
if self.required_perms.intersection(perms['global']): | ||||
return True | ||||
return False | ||||
class HasRepoPermissionAllDecorator(PermsDecorator): | ||||
""" | ||||
Checks for access permission for all given predicates for specific | ||||
repository. All of them have to be meet in order to fulfill the request | ||||
""" | ||||
r1494 | def _get_repo_name(self): | |||
_request = self._get_request() | ||||
return get_repo_slug(_request) | ||||
r1 | ||||
def check_permissions(self, user): | ||||
perms = user.permissions | ||||
r1494 | repo_name = self._get_repo_name() | |||
r1774 | ||||
r1 | try: | |||
r2821 | user_perms = {perms['repositories'][repo_name]} | |||
r1 | except KeyError: | |||
r1774 | log.debug('cannot locate repo with name: `%s` in permissions defs', | |||
repo_name) | ||||
r1 | return False | |||
r1774 | ||||
log.debug('checking `%s` permissions for repo `%s`', | ||||
user_perms, repo_name) | ||||
r1 | if self.required_perms.issubset(user_perms): | |||
return True | ||||
return False | ||||
class HasRepoPermissionAnyDecorator(PermsDecorator): | ||||
""" | ||||
Checks for access permission for any of given predicates for specific | ||||
repository. In order to fulfill the request any of predicates must be meet | ||||
""" | ||||
r1494 | def _get_repo_name(self): | |||
_request = self._get_request() | ||||
return get_repo_slug(_request) | ||||
r1 | ||||
def check_permissions(self, user): | ||||
perms = user.permissions | ||||
r1494 | repo_name = self._get_repo_name() | |||
r1774 | ||||
r1 | try: | |||
r2821 | user_perms = {perms['repositories'][repo_name]} | |||
r1 | except KeyError: | |||
r1989 | log.debug( | |||
'cannot locate repo with name: `%s` in permissions defs', | ||||
repo_name) | ||||
r1 | return False | |||
r1774 | log.debug('checking `%s` permissions for repo `%s`', | |||
user_perms, repo_name) | ||||
r1 | if self.required_perms.intersection(user_perms): | |||
return True | ||||
return False | ||||
class HasRepoGroupPermissionAllDecorator(PermsDecorator): | ||||
""" | ||||
Checks for access permission for all given predicates for specific | ||||
repository group. All of them have to be meet in order to | ||||
fulfill the request | ||||
""" | ||||
r1494 | def _get_repo_group_name(self): | |||
_request = self._get_request() | ||||
return get_repo_group_slug(_request) | ||||
r1 | ||||
def check_permissions(self, user): | ||||
perms = user.permissions | ||||
r1494 | group_name = self._get_repo_group_name() | |||
r1 | try: | |||
r2821 | user_perms = {perms['repositories_groups'][group_name]} | |||
r1 | except KeyError: | |||
r1989 | log.debug( | |||
'cannot locate repo group with name: `%s` in permissions defs', | ||||
group_name) | ||||
r1 | return False | |||
r1774 | log.debug('checking `%s` permissions for repo group `%s`', | |||
user_perms, group_name) | ||||
r1 | if self.required_perms.issubset(user_perms): | |||
return True | ||||
return False | ||||
class HasRepoGroupPermissionAnyDecorator(PermsDecorator): | ||||
""" | ||||
Checks for access permission for any of given predicates for specific | ||||
repository group. In order to fulfill the request any | ||||
of predicates must be met | ||||
""" | ||||
r1494 | def _get_repo_group_name(self): | |||
_request = self._get_request() | ||||
return get_repo_group_slug(_request) | ||||
r1 | ||||
def check_permissions(self, user): | ||||
perms = user.permissions | ||||
r1494 | group_name = self._get_repo_group_name() | |||
r1774 | ||||
r1 | try: | |||
r2821 | user_perms = {perms['repositories_groups'][group_name]} | |||
r1 | except KeyError: | |||
r1989 | log.debug( | |||
'cannot locate repo group with name: `%s` in permissions defs', | ||||
group_name) | ||||
r1 | return False | |||
r1774 | log.debug('checking `%s` permissions for repo group `%s`', | |||
user_perms, group_name) | ||||
r1 | if self.required_perms.intersection(user_perms): | |||
return True | ||||
return False | ||||
class HasUserGroupPermissionAllDecorator(PermsDecorator): | ||||
""" | ||||
Checks for access permission for all given predicates for specific | ||||
user group. All of them have to be meet in order to fulfill the request | ||||
""" | ||||
r1494 | def _get_user_group_name(self): | |||
_request = self._get_request() | ||||
return get_user_group_slug(_request) | ||||
r1 | ||||
def check_permissions(self, user): | ||||
perms = user.permissions | ||||
r1494 | group_name = self._get_user_group_name() | |||
r1 | try: | |||
r2821 | user_perms = {perms['user_groups'][group_name]} | |||
r1 | except KeyError: | |||
return False | ||||
if self.required_perms.issubset(user_perms): | ||||
return True | ||||
return False | ||||
class HasUserGroupPermissionAnyDecorator(PermsDecorator): | ||||
""" | ||||
Checks for access permission for any of given predicates for specific | ||||
user group. In order to fulfill the request any of predicates must be meet | ||||
""" | ||||
r1494 | def _get_user_group_name(self): | |||
_request = self._get_request() | ||||
return get_user_group_slug(_request) | ||||
r1 | ||||
def check_permissions(self, user): | ||||
perms = user.permissions | ||||
r1494 | group_name = self._get_user_group_name() | |||
r1 | try: | |||
r2821 | user_perms = {perms['user_groups'][group_name]} | |||
r1 | except KeyError: | |||
return False | ||||
if self.required_perms.intersection(user_perms): | ||||
return True | ||||
return False | ||||
# CHECK FUNCTIONS | ||||
class PermsFunction(object): | ||||
"""Base function for other check functions""" | ||||
def __init__(self, *perms): | ||||
self.required_perms = set(perms) | ||||
self.repo_name = None | ||||
self.repo_group_name = None | ||||
self.user_group_name = None | ||||
def __bool__(self): | ||||
frame = inspect.currentframe() | ||||
stack_trace = traceback.format_stack(frame) | ||||
log.error('Checking bool value on a class instance of perm ' | ||||
r3061 | 'function is not allowed: %s', ''.join(stack_trace)) | |||
r1 | # rather than throwing errors, here we always return False so if by | |||
# accident someone checks truth for just an instance it will always end | ||||
# up in returning False | ||||
return False | ||||
__nonzero__ = __bool__ | ||||
def __call__(self, check_location='', user=None): | ||||
if not user: | ||||
log.debug('Using user attribute from global request') | ||||
r1775 | request = self._get_request() | |||
r1 | user = request.user | |||
# init auth user if not already given | ||||
if not isinstance(user, AuthUser): | ||||
log.debug('Wrapping user %s into AuthUser', user) | ||||
user = AuthUser(user.user_id) | ||||
cls_name = self.__class__.__name__ | ||||
check_scope = self._get_check_scope(cls_name) | ||||
check_location = check_location or 'unspecified location' | ||||
log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name, | ||||
self.required_perms, user, check_scope, check_location) | ||||
if not user: | ||||
log.warning('Empty user given for permission check') | ||||
return False | ||||
if self.check_permissions(user): | ||||
log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s', | ||||
check_scope, user, check_location) | ||||
return True | ||||
else: | ||||
log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s', | ||||
check_scope, user, check_location) | ||||
return False | ||||
r1494 | def _get_request(self): | |||
r1791 | return get_request(self) | |||
r1494 | ||||
r1 | def _get_check_scope(self, cls_name): | |||
return { | ||||
'HasPermissionAll': 'GLOBAL', | ||||
'HasPermissionAny': 'GLOBAL', | ||||
'HasRepoPermissionAll': 'repo:%s' % self.repo_name, | ||||
'HasRepoPermissionAny': 'repo:%s' % self.repo_name, | ||||
'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name, | ||||
'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name, | ||||
'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name, | ||||
'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name, | ||||
}.get(cls_name, '?:%s' % cls_name) | ||||
def check_permissions(self, user): | ||||
"""Dummy function for overriding""" | ||||
raise Exception('You have to write this function in child class') | ||||
class HasPermissionAll(PermsFunction): | ||||
def check_permissions(self, user): | ||||
perms = user.permissions_with_scope({}) | ||||
if self.required_perms.issubset(perms.get('global')): | ||||
return True | ||||
return False | ||||
class HasPermissionAny(PermsFunction): | ||||
def check_permissions(self, user): | ||||
perms = user.permissions_with_scope({}) | ||||
if self.required_perms.intersection(perms.get('global')): | ||||
return True | ||||
return False | ||||
class HasRepoPermissionAll(PermsFunction): | ||||
def __call__(self, repo_name=None, check_location='', user=None): | ||||
self.repo_name = repo_name | ||||
return super(HasRepoPermissionAll, self).__call__(check_location, user) | ||||
r1494 | def _get_repo_name(self): | |||
r1 | if not self.repo_name: | |||
r1494 | _request = self._get_request() | |||
self.repo_name = get_repo_slug(_request) | ||||
return self.repo_name | ||||
r1 | ||||
r1494 | def check_permissions(self, user): | |||
self.repo_name = self._get_repo_name() | ||||
r1 | perms = user.permissions | |||
try: | ||||
r2821 | user_perms = {perms['repositories'][self.repo_name]} | |||
r1 | except KeyError: | |||
return False | ||||
if self.required_perms.issubset(user_perms): | ||||
return True | ||||
return False | ||||
class HasRepoPermissionAny(PermsFunction): | ||||
def __call__(self, repo_name=None, check_location='', user=None): | ||||
self.repo_name = repo_name | ||||
return super(HasRepoPermissionAny, self).__call__(check_location, user) | ||||
r1494 | def _get_repo_name(self): | |||
r1 | if not self.repo_name: | |||
r1791 | _request = self._get_request() | |||
self.repo_name = get_repo_slug(_request) | ||||
r1494 | return self.repo_name | |||
r1 | ||||
r1494 | def check_permissions(self, user): | |||
self.repo_name = self._get_repo_name() | ||||
r1 | perms = user.permissions | |||
try: | ||||
r2821 | user_perms = {perms['repositories'][self.repo_name]} | |||
r1 | except KeyError: | |||
return False | ||||
if self.required_perms.intersection(user_perms): | ||||
return True | ||||
return False | ||||
class HasRepoGroupPermissionAny(PermsFunction): | ||||
def __call__(self, group_name=None, check_location='', user=None): | ||||
self.repo_group_name = group_name | ||||
return super(HasRepoGroupPermissionAny, self).__call__( | ||||
check_location, user) | ||||
def check_permissions(self, user): | ||||
perms = user.permissions | ||||
try: | ||||
r2821 | user_perms = {perms['repositories_groups'][self.repo_group_name]} | |||
r1 | except KeyError: | |||
return False | ||||
if self.required_perms.intersection(user_perms): | ||||
return True | ||||
return False | ||||
class HasRepoGroupPermissionAll(PermsFunction): | ||||
def __call__(self, group_name=None, check_location='', user=None): | ||||
self.repo_group_name = group_name | ||||
return super(HasRepoGroupPermissionAll, self).__call__( | ||||
check_location, user) | ||||
def check_permissions(self, user): | ||||
perms = user.permissions | ||||
try: | ||||
r2821 | user_perms = {perms['repositories_groups'][self.repo_group_name]} | |||
r1 | except KeyError: | |||
return False | ||||
if self.required_perms.issubset(user_perms): | ||||
return True | ||||
return False | ||||
class HasUserGroupPermissionAny(PermsFunction): | ||||
def __call__(self, user_group_name=None, check_location='', user=None): | ||||
self.user_group_name = user_group_name | ||||
return super(HasUserGroupPermissionAny, self).__call__( | ||||
check_location, user) | ||||
def check_permissions(self, user): | ||||
perms = user.permissions | ||||
try: | ||||
r2821 | user_perms = {perms['user_groups'][self.user_group_name]} | |||
r1 | except KeyError: | |||
return False | ||||
if self.required_perms.intersection(user_perms): | ||||
return True | ||||
return False | ||||
class HasUserGroupPermissionAll(PermsFunction): | ||||
def __call__(self, user_group_name=None, check_location='', user=None): | ||||
self.user_group_name = user_group_name | ||||
return super(HasUserGroupPermissionAll, self).__call__( | ||||
check_location, user) | ||||
def check_permissions(self, user): | ||||
perms = user.permissions | ||||
try: | ||||
r2821 | user_perms = {perms['user_groups'][self.user_group_name]} | |||
r1 | except KeyError: | |||
return False | ||||
if self.required_perms.issubset(user_perms): | ||||
return True | ||||
return False | ||||
# SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH | ||||
class HasPermissionAnyMiddleware(object): | ||||
def __init__(self, *perms): | ||||
self.required_perms = set(perms) | ||||
r2979 | def __call__(self, auth_user, repo_name): | |||
r1 | # repo_name MUST be unicode, since we handle keys in permission | |||
# dict by unicode | ||||
repo_name = safe_unicode(repo_name) | ||||
log.debug( | ||||
'Checking VCS protocol permissions %s for user:%s repo:`%s`', | ||||
r2979 | self.required_perms, auth_user, repo_name) | |||
if self.check_permissions(auth_user, repo_name): | ||||
r1 | log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s', | |||
r2979 | repo_name, auth_user, 'PermissionMiddleware') | |||
r1 | return True | |||
else: | ||||
log.debug('Permission to repo:`%s` DENIED for user:%s @ %s', | ||||
r2979 | repo_name, auth_user, 'PermissionMiddleware') | |||
r1 | return False | |||
def check_permissions(self, user, repo_name): | ||||
perms = user.permissions_with_scope({'repo_name': repo_name}) | ||||
try: | ||||
r2821 | user_perms = {perms['repositories'][repo_name]} | |||
r1 | except Exception: | |||
log.exception('Error while accessing user permissions') | ||||
return False | ||||
if self.required_perms.intersection(user_perms): | ||||
return True | ||||
return False | ||||
# SPECIAL VERSION TO HANDLE API AUTH | ||||
class _BaseApiPerm(object): | ||||
def __init__(self, *perms): | ||||
self.required_perms = set(perms) | ||||
def __call__(self, check_location=None, user=None, repo_name=None, | ||||
group_name=None, user_group_name=None): | ||||
cls_name = self.__class__.__name__ | ||||
check_scope = 'global:%s' % (self.required_perms,) | ||||
if repo_name: | ||||
check_scope += ', repo_name:%s' % (repo_name,) | ||||
if group_name: | ||||
check_scope += ', repo_group_name:%s' % (group_name,) | ||||
if user_group_name: | ||||
check_scope += ', user_group_name:%s' % (user_group_name,) | ||||
r3061 | log.debug('checking cls:%s %s %s @ %s', | |||
cls_name, self.required_perms, check_scope, check_location) | ||||
r1 | if not user: | |||
log.debug('Empty User passed into arguments') | ||||
return False | ||||
# process user | ||||
if not isinstance(user, AuthUser): | ||||
user = AuthUser(user.user_id) | ||||
if not check_location: | ||||
check_location = 'unspecified' | ||||
if self.check_permissions(user.permissions, repo_name, group_name, | ||||
user_group_name): | ||||
log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s', | ||||
check_scope, user, check_location) | ||||
return True | ||||
else: | ||||
log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s', | ||||
check_scope, user, check_location) | ||||
return False | ||||
def check_permissions(self, perm_defs, repo_name=None, group_name=None, | ||||
user_group_name=None): | ||||
""" | ||||
implement in child class should return True if permissions are ok, | ||||
False otherwise | ||||
:param perm_defs: dict with permission definitions | ||||
:param repo_name: repo name | ||||
""" | ||||
raise NotImplementedError() | ||||
class HasPermissionAllApi(_BaseApiPerm): | ||||
def check_permissions(self, perm_defs, repo_name=None, group_name=None, | ||||
user_group_name=None): | ||||
if self.required_perms.issubset(perm_defs.get('global')): | ||||
return True | ||||
return False | ||||
class HasPermissionAnyApi(_BaseApiPerm): | ||||
def check_permissions(self, perm_defs, repo_name=None, group_name=None, | ||||
user_group_name=None): | ||||
if self.required_perms.intersection(perm_defs.get('global')): | ||||
return True | ||||
return False | ||||
class HasRepoPermissionAllApi(_BaseApiPerm): | ||||
def check_permissions(self, perm_defs, repo_name=None, group_name=None, | ||||
user_group_name=None): | ||||
try: | ||||
r2821 | _user_perms = {perm_defs['repositories'][repo_name]} | |||
r1 | except KeyError: | |||
log.warning(traceback.format_exc()) | ||||
return False | ||||
if self.required_perms.issubset(_user_perms): | ||||
return True | ||||
return False | ||||
class HasRepoPermissionAnyApi(_BaseApiPerm): | ||||
def check_permissions(self, perm_defs, repo_name=None, group_name=None, | ||||
user_group_name=None): | ||||
try: | ||||
r2821 | _user_perms = {perm_defs['repositories'][repo_name]} | |||
r1 | except KeyError: | |||
log.warning(traceback.format_exc()) | ||||
return False | ||||
if self.required_perms.intersection(_user_perms): | ||||
return True | ||||
return False | ||||
class HasRepoGroupPermissionAnyApi(_BaseApiPerm): | ||||
def check_permissions(self, perm_defs, repo_name=None, group_name=None, | ||||
user_group_name=None): | ||||
try: | ||||
r2821 | _user_perms = {perm_defs['repositories_groups'][group_name]} | |||
r1 | except KeyError: | |||
log.warning(traceback.format_exc()) | ||||
return False | ||||
if self.required_perms.intersection(_user_perms): | ||||
return True | ||||
return False | ||||
class HasRepoGroupPermissionAllApi(_BaseApiPerm): | ||||
def check_permissions(self, perm_defs, repo_name=None, group_name=None, | ||||
user_group_name=None): | ||||
try: | ||||
r2821 | _user_perms = {perm_defs['repositories_groups'][group_name]} | |||
r1 | except KeyError: | |||
log.warning(traceback.format_exc()) | ||||
return False | ||||
if self.required_perms.issubset(_user_perms): | ||||
return True | ||||
return False | ||||
class HasUserGroupPermissionAnyApi(_BaseApiPerm): | ||||
def check_permissions(self, perm_defs, repo_name=None, group_name=None, | ||||
user_group_name=None): | ||||
try: | ||||
r2821 | _user_perms = {perm_defs['user_groups'][user_group_name]} | |||
r1 | except KeyError: | |||
log.warning(traceback.format_exc()) | ||||
return False | ||||
if self.required_perms.intersection(_user_perms): | ||||
return True | ||||
return False | ||||
def check_ip_access(source_ip, allowed_ips=None): | ||||
""" | ||||
Checks if source_ip is a subnet of any of allowed_ips. | ||||
:param source_ip: | ||||
:param allowed_ips: list of allowed ips together with mask | ||||
""" | ||||
r3061 | log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips) | |||
r1907 | source_ip_address = ipaddress.ip_address(safe_unicode(source_ip)) | |||
r1 | if isinstance(allowed_ips, (tuple, list, set)): | |||
for ip in allowed_ips: | ||||
r1907 | ip = safe_unicode(ip) | |||
r1 | try: | |||
network_address = ipaddress.ip_network(ip, strict=False) | ||||
if source_ip_address in network_address: | ||||
r3061 | log.debug('IP %s is network %s', source_ip_address, network_address) | |||
r1 | return True | |||
# for any case we cannot determine the IP, don't crash just | ||||
# skip it and log as error, we want to say forbidden still when | ||||
# sending bad IP | ||||
except Exception: | ||||
log.error(traceback.format_exc()) | ||||
continue | ||||
return False | ||||
def get_cython_compat_decorator(wrapper, func): | ||||
""" | ||||
Creates a cython compatible decorator. The previously used | ||||
decorator.decorator() function seems to be incompatible with cython. | ||||
:param wrapper: __wrapper method of the decorator class | ||||
:param func: decorated function | ||||
""" | ||||
@wraps(func) | ||||
def local_wrapper(*args, **kwds): | ||||
return wrapper(func, *args, **kwds) | ||||
local_wrapper.__wrapped__ = func | ||||
return local_wrapper | ||||
r1499 | ||||