branch-permissions: handle vcs operations and branch permissions.

- use new exceptions for branch permissions
- detect if we need to check permissions
- calculate super-admin the same way as regular users for override capabilities
- added new tests for branch permissions
This commit is contained in:
Marcin Kuzminski 2018-08-29 20:22:43 +02:00
parent 77f19f237a
commit d3afc86451
12 changed files with 640 additions and 87 deletions

View file

@ -404,14 +404,14 @@ class PermissionCalculator(object):
def __init__(
self, user_id, scope, user_is_admin,
user_inherit_default_permissions, explicit, algo,
calculate_super_admin=False):
calculate_super_admin_as_user=False):
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
self.calculate_super_admin = calculate_super_admin
self.calculate_super_admin_as_user = calculate_super_admin_as_user
scope = scope or {}
self.scope_repo_id = scope.get('repo_id')
@ -440,8 +440,8 @@ class PermissionCalculator(object):
self.default_user_id, self.scope_repo_id)
def calculate(self):
if self.user_is_admin and not self.calculate_super_admin:
return self._admin_permissions()
if self.user_is_admin and not self.calculate_super_admin_as_user:
return self._calculate_admin_permissions()
self._calculate_global_default_permissions()
self._calculate_global_permissions()
@ -452,7 +452,7 @@ class PermissionCalculator(object):
self._calculate_user_group_permissions()
return self._permission_structure()
def _admin_permissions(self):
def _calculate_admin_permissions(self):
"""
admin user have all default rights for repositories
and groups set to admin
@ -479,13 +479,11 @@ class PermissionCalculator(object):
self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
# branch permissions
# TODO(marcink): validate this, especially
# how this should work using multiple patterns specified ??
# looks ok, but still needs double check !!
for perm in self.default_branch_repo_perms:
r_k = perm.UserRepoToPerm.repository.repo_name
p = 'branch.push_force'
self.permissions_repository_branches[r_k] = '*', p, PermOrigin.SUPER_ADMIN
# 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()
return self._permission_structure()
@ -571,27 +569,7 @@ class PermissionCalculator(object):
for perm in user_perms:
self.permissions_global.add(perm.permission.permission_name)
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 for repositories, taken from `default` user permissions
def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
for perm in self.default_repo_perms:
r_k = perm.UserRepoToPerm.repository.repo_name
p = perm.Permission.permission_name
@ -624,7 +602,7 @@ class PermissionCalculator(object):
o = PermOrigin.SUPER_ADMIN
self.permissions_repositories[r_k] = p, o
# default permissions branch for repositories, taken from `default` user permissions
def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
for perm in self.default_branch_repo_perms:
r_k = perm.UserRepoToPerm.repository.repo_name
@ -641,7 +619,7 @@ class PermissionCalculator(object):
# special dict that aggregates entries
self.permissions_repository_branches[r_k] = pattern, p, o
# default permissions for repository groups taken from `default` user permission
def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
for perm in self.default_repo_groups_perms:
rg_k = perm.UserRepoGroupToPerm.group.group_name
p = perm.Permission.permission_name
@ -666,7 +644,7 @@ class PermissionCalculator(object):
o = PermOrigin.SUPER_ADMIN
self.permissions_repository_groups[rg_k] = p, o
# default permissions for user groups taken from `default` user permission
def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
for perm in self.default_user_group_perms:
u_k = perm.UserUserGroupToPerm.user_group.users_group_name
p = perm.Permission.permission_name
@ -691,6 +669,39 @@ class PermissionCalculator(object):
o = PermOrigin.SUPER_ADMIN
self.permissions_user_groups[u_k] = p, o
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)
def _calculate_repository_permissions(self):
"""
Repository permissions for the current user.
@ -783,6 +794,7 @@ class PermissionCalculator(object):
# any specified by the group permission
user_repo_branch_perms = Permission.get_default_repo_branch_perms(
self.user_id, self.scope_repo_id)
for perm in user_repo_branch_perms:
r_k = perm.UserRepoToPerm.repository.repo_name
@ -799,7 +811,6 @@ class PermissionCalculator(object):
# special dict that aggregates entries
self.permissions_repository_branches[r_k] = pattern, p, o
def _calculate_repository_group_permissions(self):
"""
Repository group permissions for the current user.
@ -1036,14 +1047,14 @@ class AuthUser(object):
@LazyProperty
def permissions(self):
return self.get_perms(user=self, cache=False)
return self.get_perms(user=self, cache=None)
@LazyProperty
def permissions_safe(self):
"""
Filtered permissions excluding not allowed repositories
"""
perms = self.get_perms(user=self, cache=False)
perms = self.get_perms(user=self, cache=None)
perms['repositories'] = {
k: v for k, v in perms['repositories'].items()
@ -1062,7 +1073,7 @@ class AuthUser(object):
@LazyProperty
def permissions_full_details(self):
return self.get_perms(
user=self, cache=False, calculate_super_admin=True)
user=self, cache=None, calculate_super_admin=True)
def permissions_with_scope(self, scope):
"""
@ -1088,7 +1099,7 @@ class AuthUser(object):
# store in cache to mimic how the @LazyProperty works,
# the difference here is that we use the unique key calculated
# from params and values
return self.get_perms(user=self, cache=False, scope=_scope)
return self.get_perms(user=self, cache=None, scope=_scope)
def get_instance(self):
return User.get(self.user_id)
@ -1143,7 +1154,7 @@ class AuthUser(object):
log.debug('AuthUser: propagated user is now %s', self)
def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
calculate_super_admin=False, cache=False):
calculate_super_admin=False, cache=None):
"""
Fills user permission attribute with permissions taken from database
works for permissions given for repositories, and for permissions that
@ -1158,6 +1169,9 @@ class AuthUser(object):
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
: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
"""
user_id = user.user_id
user_is_admin = user.is_admin
@ -1168,7 +1182,12 @@ class AuthUser(object):
cache_seconds = safe_int(
rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
cache_on = cache or cache_seconds > 0
if cache is None:
# let the backend cache decide
cache_on = cache_seconds > 0
else:
cache_on = cache
log.debug(
'Computing PERMISSION tree for user %s scope `%s` '
'with caching: %s[TTL: %ss]' % (user, scope, cache_on, cache_seconds or 0))
@ -1186,9 +1205,10 @@ class AuthUser(object):
explicit, algo, calculate_super_admin)
start = time.time()
result = compute_perm_tree('permissions', user_id, scope, user_is_admin,
user_inherit_default_permissions, explicit, algo,
calculate_super_admin)
result = compute_perm_tree(
'permissions', user_id, scope, user_is_admin,
user_inherit_default_permissions, explicit, algo,
calculate_super_admin)
result_repr = []
for k in result:
@ -1340,6 +1360,35 @@ class AuthUser(object):
'not in %s' % (ip_addr, user_id, allowed_ips))
return False
def get_branch_permissions(self, repo_name, perms=None):
perms = perms or self.permissions_with_scope({'repo_name': repo_name})
branch_perms = perms.get('repository_branches')
return branch_perms
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 = ''
branch_perms = self.get_branch_permissions(repo_name=repo_name)
if not branch_perms:
return rule, default_perm
repo_branch_perms = branch_perms.get(repo_name)
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
def __repr__(self):
return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
% (self.user_id, self.username, self.ip_addr, self.is_authenticated)
@ -2084,23 +2133,22 @@ class HasPermissionAnyMiddleware(object):
def __init__(self, *perms):
self.required_perms = set(perms)
def __call__(self, user, repo_name):
def __call__(self, auth_user, repo_name):
# repo_name MUST be unicode, since we handle keys in permission
# dict by unicode
repo_name = safe_unicode(repo_name)
user = AuthUser(user.user_id)
log.debug(
'Checking VCS protocol permissions %s for user:%s repo:`%s`',
self.required_perms, user, repo_name)
self.required_perms, auth_user, repo_name)
if self.check_permissions(user, repo_name):
if self.check_permissions(auth_user, repo_name):
log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
repo_name, user, 'PermissionMiddleware')
repo_name, auth_user, 'PermissionMiddleware')
return True
else:
log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
repo_name, user, 'PermissionMiddleware')
repo_name, auth_user, 'PermissionMiddleware')
return False
def check_permissions(self, user, repo_name):

View file

@ -153,7 +153,7 @@ def get_user_agent(environ):
def vcs_operation_context(
environ, repo_name, username, action, scm, check_locking=True,
is_shadow_repo=False):
is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
"""
Generate the context for a vcs operation, e.g. push or pull.
@ -193,6 +193,8 @@ def vcs_operation_context(
'user_agent': get_user_agent(environ),
'hooks': get_enabled_hook_classes(ui_settings),
'is_shadow_repo': is_shadow_repo,
'detect_force_push': detect_force_push,
'check_branch_perms': check_branch_perms,
}
return extras

View file

@ -107,6 +107,21 @@ class HTTPLockedRC(HTTPClientError):
self.args = (message, )
class HTTPBranchProtected(HTTPClientError):
"""
Special Exception For Indicating that branch is protected in RhodeCode, the
return code can be overwritten by _code keyword argument passed into constructors
"""
code = 403
title = explanation = 'Branch Protected'
reason = None
def __init__(self, message, *args, **kwargs):
self.title = self.explanation = message
super(HTTPBranchProtected, self).__init__(*args, **kwargs)
self.args = (message, )
class IMCCommitError(Exception):
pass

View file

@ -32,7 +32,8 @@ from rhodecode import events
from rhodecode.lib import helpers as h
from rhodecode.lib import audit_logger
from rhodecode.lib.utils2 import safe_str
from rhodecode.lib.exceptions import HTTPLockedRC, UserCreationError
from rhodecode.lib.exceptions import (
HTTPLockedRC, HTTPBranchProtected, UserCreationError)
from rhodecode.model.db import Repository, User
log = logging.getLogger(__name__)
@ -94,9 +95,9 @@ def pre_push(extras):
It bans pushing when the repository is locked.
"""
usr = User.get_by_username(extras.username)
user = User.get_by_username(extras.username)
output = ''
if extras.locked_by[0] and usr.user_id != int(extras.locked_by[0]):
if extras.locked_by[0] and user.user_id != int(extras.locked_by[0]):
locked_by = User.get(extras.locked_by[0]).username
reason = extras.locked_by[2]
# this exception is interpreted in git/hg middlewares and based
@ -109,9 +110,48 @@ def pre_push(extras):
else:
raise _http_ret
# Propagate to external components. This is done after checking the
# lock, for consistent behavior.
if not is_shadow_repo(extras):
if extras.commit_ids and extras.check_branch_perms:
auth_user = user.AuthUser()
repo = Repository.get_by_repo_name(extras.repository)
affected_branches = []
if repo.repo_type == 'hg':
for entry in extras.commit_ids:
if entry['type'] == 'branch':
is_forced = bool(entry['multiple_heads'])
affected_branches.append([entry['name'], is_forced])
elif repo.repo_type == 'git':
for entry in extras.commit_ids:
if entry['type'] == 'heads':
is_forced = bool(entry['pruned_sha'])
affected_branches.append([entry['name'], is_forced])
for branch_name, is_forced in affected_branches:
rule, branch_perm = auth_user.get_rule_and_branch_permission(
extras.repository, branch_name)
if not branch_perm:
# no branch permission found for this branch, just keep checking
continue
if branch_perm == 'branch.push_force':
continue
elif branch_perm == 'branch.push' and is_forced is False:
continue
elif branch_perm == 'branch.push' and is_forced is True:
halt_message = 'Branch `{}` changes rejected by rule {}. ' \
'FORCE PUSH FORBIDDEN.'.format(branch_name, rule)
else:
halt_message = 'Branch `{}` changes rejected by rule {}.'.format(
branch_name, rule)
if halt_message:
_http_ret = HTTPBranchProtected(halt_message)
raise _http_ret
# Propagate to external components. This is done after checking the
# lock, for consistent behavior.
pre_push_extension(repo_store_path=Repository.base_path(), **extras)
events.trigger(events.RepoPrePushEvent(
repo_name=extras.repository, extras=extras))

View file

@ -29,6 +29,7 @@ from BaseHTTPServer import BaseHTTPRequestHandler
from SocketServer import TCPServer
import rhodecode
from rhodecode.lib.exceptions import HTTPLockedRC, HTTPBranchProtected
from rhodecode.model import meta
from rhodecode.lib.base import bootstrap_request, bootstrap_config
from rhodecode.lib import hooks_base
@ -285,9 +286,20 @@ class Hooks(object):
try:
result = hook(extras)
except Exception as error:
exc_tb = traceback.format_exc()
log.exception('Exception when handling hook %s', hook)
except HTTPBranchProtected as handled_error:
# Those special cases doesn't need error reporting. It's a case of
# locked repo or protected branch
result = AttributeDict({
'status': handled_error.code,
'output': handled_error.explanation
})
except (HTTPLockedRC, Exception) as error:
# locked needs different handling since we need to also
# handle PULL operations
exc_tb = ''
if not isinstance(error, HTTPLockedRC):
exc_tb = traceback.format_exc()
log.exception('Exception when handling hook %s', hook)
error_args = error.args
return {
'status': 128,

View file

@ -297,7 +297,7 @@ class SimpleVCS(object):
def is_shadow_repo_dir(self):
return os.path.isdir(self.vcs_repo_name)
def _check_permission(self, action, user, repo_name, ip_addr=None,
def _check_permission(self, action, user, auth_user, repo_name, ip_addr=None,
plugin_id='', plugin_cache_active=False, cache_ttl=0):
"""
Checks permissions using action (push/pull) user and repository
@ -335,14 +335,14 @@ class SimpleVCS(object):
if action == 'push':
perms = ('repository.write', 'repository.admin')
if not HasPermissionAnyMiddleware(*perms)(user, repo_name):
if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
return False
else:
# any other action need at least read permission
perms = (
'repository.read', 'repository.write', 'repository.admin')
if not HasPermissionAnyMiddleware(*perms)(user, repo_name):
if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
return False
return True
@ -441,14 +441,17 @@ class SimpleVCS(object):
# ======================================================================
# CHECK ANONYMOUS PERMISSION
# ======================================================================
detect_force_push = False
check_branch_perms = False
if action in ['pull', 'push']:
anonymous_user = User.get_default_user()
user_obj = anonymous_user = User.get_default_user()
auth_user = user_obj.AuthUser()
username = anonymous_user.username
if anonymous_user.active:
plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
# ONLY check permissions if the user is activated
anonymous_perm = self._check_permission(
action, anonymous_user, self.acl_repo_name, ip_addr,
action, anonymous_user, auth_user, self.acl_repo_name, ip_addr,
plugin_id='anonymous_access',
plugin_cache_active=plugin_cache_active,
cache_ttl=cache_ttl,
@ -525,6 +528,7 @@ class SimpleVCS(object):
# check user attributes for password change flag
user_obj = user
auth_user = user_obj.AuthUser()
if user_obj and user_obj.username != User.DEFAULT_USER and \
user_obj.user_data.get('force_password_change'):
reason = 'password change required'
@ -533,19 +537,27 @@ class SimpleVCS(object):
# check permissions for this repository
perm = self._check_permission(
action, user, self.acl_repo_name, ip_addr,
action, user, auth_user, self.acl_repo_name, ip_addr,
plugin, plugin_cache_active, cache_ttl)
if not perm:
return HTTPForbidden()(environ, start_response)
environ['rc_auth_user_id'] = user_id
if action == 'push':
perms = auth_user.get_branch_permissions(self.acl_repo_name)
if perms:
check_branch_perms = True
detect_force_push = True
# extras are injected into UI object and later available
# in hooks executed by RhodeCode
check_locking = _should_check_locking(environ.get('QUERY_STRING'))
extras = vcs_operation_context(
environ, repo_name=self.acl_repo_name, username=username,
action=action, scm=self.SCM, check_locking=check_locking,
is_shadow_repo=self.is_shadow_repo
is_shadow_repo=self.is_shadow_repo, check_branch_perms=check_branch_perms,
detect_force_push=detect_force_push
)
# ======================================================================

View file

@ -419,7 +419,7 @@ def test_permission_calculator_admin_permissions(
calculator = auth.PermissionCalculator(
user.user_id, {}, False, False, True, 'higherwin')
permissions = calculator._admin_permissions()
permissions = calculator._calculate_admin_permissions()
assert permissions['repositories_groups'][repo_group.group_name] == \
'group.admin'

View file

@ -77,22 +77,18 @@ class Command(object):
assert self.process.returncode == 0
def _add_files_and_push(vcs, dest, clone_url=None, tags=None, **kwargs):
"""
Generate some files, add it to DEST repo and push back
vcs is git or hg and defines what VCS we want to make those files for
"""
# commit some stuff into this repo
tags = tags or []
def _add_files(vcs, dest, clone_url=None, tags=None, target_branch=None,
new_branch=False, **kwargs):
git_ident = "git config user.name {} && git config user.email {}".format(
'Marcin Kuźminski', 'me@email.com')
cwd = path = jn(dest)
tags = tags or []
added_file = jn(path, '%ssetup.py' % tempfile._RandomNameSequence().next())
Command(cwd).execute('touch %s' % added_file)
Command(cwd).execute('%s add %s' % (vcs, added_file))
author_str = 'Marcin Kuźminski <me@email.com>'
git_ident = "git config user.name {} && git config user.email {}".format(
'Marcin Kuźminski', 'me@email.com')
for i in range(kwargs.get('files_no', 3)):
cmd = """echo 'added_line%s' >> %s""" % (i, added_file)
Command(cwd).execute(cmd)
@ -107,30 +103,55 @@ def _add_files_and_push(vcs, dest, clone_url=None, tags=None, **kwargs):
for tag in tags:
if vcs == 'hg':
stdout, stderr = Command(cwd).execute(
Command(cwd).execute(
'hg tag', tag['name'])
elif vcs == 'git':
if tag['commit']:
# annotated tag
stdout, stderr = Command(cwd).execute(
_stdout, _stderr = Command(cwd).execute(
"""%s && git tag -a %s -m "%s" """ % (
git_ident, tag['name'], tag['commit']))
else:
# lightweight tag
stdout, stderr = Command(cwd).execute(
_stdout, _stderr = Command(cwd).execute(
"""%s && git tag %s""" % (
git_ident, tag['name']))
def _add_files_and_push(vcs, dest, clone_url=None, tags=None, target_branch=None,
new_branch=False, **kwargs):
"""
Generate some files, add it to DEST repo and push back
vcs is git or hg and defines what VCS we want to make those files for
"""
git_ident = "git config user.name {} && git config user.email {}".format(
'Marcin Kuźminski', 'me@email.com')
cwd = path = jn(dest)
# commit some stuff into this repo
_add_files(vcs, dest, clone_url, tags, target_branch, new_branch, **kwargs)
default_target_branch = {
'git': 'master',
'hg': 'default'
}.get(vcs)
target_branch = target_branch or default_target_branch
# PUSH it back
stdout = stderr = None
if vcs == 'hg':
maybe_new_branch = ''
if new_branch:
maybe_new_branch = '--new-branch'
stdout, stderr = Command(cwd).execute(
'hg push --verbose', clone_url)
'hg push --verbose {} -r {} {}'.format(maybe_new_branch, target_branch, clone_url)
)
elif vcs == 'git':
stdout, stderr = Command(cwd).execute(
"""%s &&
git push --verbose --tags %s master""" % (
git_ident, clone_url))
"""{} &&
git push --verbose --tags {} {}""".format(git_ident, clone_url, target_branch)
)
return stdout, stderr

View file

@ -33,7 +33,8 @@ import textwrap
import pytest
from rhodecode import events
from rhodecode.model.db import Integration
from rhodecode.model.db import Integration, UserRepoToPerm, Permission, \
UserToRepoBranchPermission, User
from rhodecode.model.integration import IntegrationModel
from rhodecode.model.db import Repository
from rhodecode.model.meta import Session
@ -267,3 +268,74 @@ def enable_webhook_push_integration(request):
Session().delete(integration)
Session().commit()
@pytest.fixture
def branch_permission_setter(request):
"""
def my_test(branch_permission_setter)
branch_permission_setter(repo_name, username, pattern='*', permission='branch.push')
"""
rule_id = None
write_perm_id = None
def _branch_permissions_setter(
repo_name, username, pattern='*', permission='branch.push_force'):
global rule_id, write_perm_id
repo = Repository.get_by_repo_name(repo_name)
repo_id = repo.repo_id
user = User.get_by_username(username)
user_id = user.user_id
rule_perm_obj = Permission.get_by_key(permission)
write_perm = None
# add new entry, based on existing perm entry
perm = UserRepoToPerm.query() \
.filter(UserRepoToPerm.repository_id == repo_id) \
.filter(UserRepoToPerm.user_id == user_id) \
.first()
if not perm:
# such user isn't defined in Permissions for repository
# we now on-the-fly add new permission
write_perm = UserRepoToPerm()
write_perm.permission = Permission.get_by_key('repository.write')
write_perm.repository_id = repo_id
write_perm.user_id = user_id
Session().add(write_perm)
Session().flush()
perm = write_perm
rule = UserToRepoBranchPermission()
rule.rule_to_perm_id = perm.repo_to_perm_id
rule.branch_pattern = pattern
rule.rule_order = 10
rule.permission = rule_perm_obj
rule.repository_id = repo_id
Session().add(rule)
Session().commit()
global rule, write_perm
return rule
@request.addfinalizer
def cleanup():
if rule:
Session().delete(rule)
Session().commit()
if write_perm:
Session().delete(write_perm)
Session().commit()
return _branch_permissions_setter

View file

@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010-2018 RhodeCode GmbH
#
# 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/
import os
import pytest
from rhodecode.tests import (
TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
from rhodecode.tests.vcs_operations import (
Command, _check_proper_hg_push, _check_proper_git_push, _add_files_and_push)
@pytest.mark.usefixtures("disable_anonymous_user")
class TestVCSOperations(object):
@pytest.mark.parametrize('username, password', [
(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS),
(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS),
])
@pytest.mark.parametrize('branch_perm', [
'branch.none',
'branch.merge',
'branch.push',
'branch.push_force',
])
def test_push_to_protected_branch_fails_with_message_hg(
self, rc_web_server, tmpdir, branch_perm, user_util,
branch_permission_setter, username, password):
repo = user_util.create_repo(repo_type='hg')
repo_name = repo.repo_name
branch_permission_setter(repo_name, username, permission=branch_perm)
clone_url = rc_web_server.repo_clone_url(
repo.repo_name, user=username, passwd=password)
Command(os.path.dirname(tmpdir.strpath)).execute(
'hg clone', clone_url, tmpdir.strpath)
stdout, stderr = _add_files_and_push(
'hg', tmpdir.strpath, clone_url=clone_url)
if branch_perm in ['branch.push', 'branch.push_force']:
_check_proper_hg_push(stdout, stderr)
else:
msg = "Branch `default` changes rejected by rule `*`=>{}".format(branch_perm)
assert msg in stdout
assert "transaction abort" in stdout
@pytest.mark.parametrize('username, password', [
(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS),
(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS),
])
@pytest.mark.parametrize('branch_perm', [
'branch.none',
'branch.merge',
'branch.push',
'branch.push_force',
])
def test_push_to_protected_branch_fails_with_message_git(
self, rc_web_server, tmpdir, branch_perm, user_util,
branch_permission_setter, username, password):
repo = user_util.create_repo(repo_type='git')
repo_name = repo.repo_name
branch_permission_setter(repo_name, username, permission=branch_perm)
clone_url = rc_web_server.repo_clone_url(
repo.repo_name, user=username, passwd=password)
Command(os.path.dirname(tmpdir.strpath)).execute(
'git clone', clone_url, tmpdir.strpath)
stdout, stderr = _add_files_and_push(
'git', tmpdir.strpath, clone_url=clone_url)
if branch_perm in ['branch.push', 'branch.push_force']:
_check_proper_git_push(stdout, stderr)
else:
msg = "Branch `master` changes rejected by rule `*`=>{}".format(branch_perm)
assert msg in stderr
assert "(pre-receive hook declined)" in stderr

View file

@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010-2018 RhodeCode GmbH
#
# 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/
import os
import pytest
from rhodecode.tests import TEST_USER_ADMIN_LOGIN
from rhodecode.tests.vcs_operations import (
Command, _check_proper_hg_push, _check_proper_git_push,
_add_files, _add_files_and_push)
@pytest.mark.usefixtures("disable_anonymous_user")
class TestVCSOperations(object):
def test_push_force_hg(self, rc_web_server, tmpdir, user_util):
repo = user_util.create_repo(repo_type='hg')
clone_url = rc_web_server.repo_clone_url(repo.repo_name)
Command(os.path.dirname(tmpdir.strpath)).execute(
'hg clone', clone_url, tmpdir.strpath)
stdout, stderr = _add_files_and_push(
'hg', tmpdir.strpath, clone_url=clone_url)
_check_proper_hg_push(stdout, stderr)
# rewrite history, and push with force
Command(tmpdir.strpath).execute(
'hg checkout -r 1 && hg commit -m "starting new head"')
_add_files('hg', tmpdir.strpath, clone_url=clone_url)
stdout, stderr = Command(tmpdir.strpath).execute(
'hg push --verbose -f {}'.format(clone_url))
_check_proper_hg_push(stdout, stderr)
def test_push_force_git(self, rc_web_server, tmpdir, user_util):
repo = user_util.create_repo(repo_type='git')
clone_url = rc_web_server.repo_clone_url(repo.repo_name)
Command(os.path.dirname(tmpdir.strpath)).execute(
'git clone', clone_url, tmpdir.strpath)
stdout, stderr = _add_files_and_push(
'git', tmpdir.strpath, clone_url=clone_url)
_check_proper_git_push(stdout, stderr)
# rewrite history, and push with force
Command(tmpdir.strpath).execute(
'git reset --hard HEAD~2')
stdout, stderr = Command(tmpdir.strpath).execute(
'git push -f {} master'.format(clone_url))
assert '(forced update)' in stderr
def test_push_force_hg_blocked_by_branch_permissions(
self, rc_web_server, tmpdir, user_util, branch_permission_setter):
repo = user_util.create_repo(repo_type='hg')
repo_name = repo.repo_name
username = TEST_USER_ADMIN_LOGIN
branch_permission_setter(repo_name, username, permission='branch.push')
clone_url = rc_web_server.repo_clone_url(repo.repo_name)
Command(os.path.dirname(tmpdir.strpath)).execute(
'hg clone', clone_url, tmpdir.strpath)
stdout, stderr = _add_files_and_push(
'hg', tmpdir.strpath, clone_url=clone_url)
_check_proper_hg_push(stdout, stderr)
# rewrite history, and push with force
Command(tmpdir.strpath).execute(
'hg checkout -r 1 && hg commit -m "starting new head"')
_add_files('hg', tmpdir.strpath, clone_url=clone_url)
stdout, stderr = Command(tmpdir.strpath).execute(
'hg push --verbose -f {}'.format(clone_url))
assert "Branch `default` changes rejected by rule `*`=>branch.push" in stdout
assert "FORCE PUSH FORBIDDEN" in stdout
assert "transaction abort" in stdout
def test_push_force_git_blocked_by_branch_permissions(
self, rc_web_server, tmpdir, user_util, branch_permission_setter):
repo = user_util.create_repo(repo_type='git')
repo_name = repo.repo_name
username = TEST_USER_ADMIN_LOGIN
branch_permission_setter(repo_name, username, permission='branch.push')
clone_url = rc_web_server.repo_clone_url(repo.repo_name)
Command(os.path.dirname(tmpdir.strpath)).execute(
'git clone', clone_url, tmpdir.strpath)
stdout, stderr = _add_files_and_push(
'git', tmpdir.strpath, clone_url=clone_url)
_check_proper_git_push(stdout, stderr)
# rewrite history, and push with force
Command(tmpdir.strpath).execute(
'git reset --hard HEAD~2')
stdout, stderr = Command(tmpdir.strpath).execute(
'git push -f {} master'.format(clone_url))
assert "Branch `master` changes rejected by rule `*`=>branch.push" in stderr
assert "FORCE PUSH FORBIDDEN" in stderr
assert "(pre-receive hook declined)" in stderr

View file

@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2010-2018 RhodeCode GmbH
#
# 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/
import os
import pytest
from rhodecode.tests import TEST_USER_ADMIN_LOGIN
from rhodecode.tests.vcs_operations import (
Command, _check_proper_hg_push, _check_proper_git_push, _add_files_and_push)
@pytest.mark.usefixtures("disable_anonymous_user")
class TestVCSOperations(object):
def test_push_new_branch_hg(self, rc_web_server, tmpdir, user_util):
repo = user_util.create_repo(repo_type='hg')
clone_url = rc_web_server.repo_clone_url(repo.repo_name)
Command(os.path.dirname(tmpdir.strpath)).execute(
'hg clone', clone_url, tmpdir.strpath)
stdout, stderr = _add_files_and_push(
'hg', tmpdir.strpath, clone_url=clone_url)
_check_proper_hg_push(stdout, stderr)
# start new branch, and push file into it
Command(tmpdir.strpath).execute(
'hg branch dev && hg commit -m "starting dev branch"')
stdout, stderr = _add_files_and_push(
'hg', tmpdir.strpath, clone_url=clone_url, target_branch='dev',
new_branch=True)
_check_proper_hg_push(stdout, stderr)
def test_push_new_branch_git(self, rc_web_server, tmpdir, user_util):
repo = user_util.create_repo(repo_type='git')
clone_url = rc_web_server.repo_clone_url(repo.repo_name)
Command(os.path.dirname(tmpdir.strpath)).execute(
'git clone', clone_url, tmpdir.strpath)
stdout, stderr = _add_files_and_push(
'git', tmpdir.strpath, clone_url=clone_url)
_check_proper_git_push(stdout, stderr)
# start new branch, and push file into it
Command(tmpdir.strpath).execute('git checkout -b dev')
stdout, stderr = _add_files_and_push(
'git', tmpdir.strpath, clone_url=clone_url, target_branch='dev',
new_branch=True)
_check_proper_git_push(stdout, stderr, branch='dev')
def test_push_new_branch_hg_with_branch_permissions_no_force_push(
self, rc_web_server, tmpdir, user_util, branch_permission_setter):
repo = user_util.create_repo(repo_type='hg')
repo_name = repo.repo_name
username = TEST_USER_ADMIN_LOGIN
branch_permission_setter(repo_name, username, permission='branch.push')
clone_url = rc_web_server.repo_clone_url(repo.repo_name)
Command(os.path.dirname(tmpdir.strpath)).execute(
'hg clone', clone_url, tmpdir.strpath)
stdout, stderr = _add_files_and_push(
'hg', tmpdir.strpath, clone_url=clone_url)
_check_proper_hg_push(stdout, stderr)
# start new branch, and push file into it
Command(tmpdir.strpath).execute(
'hg branch dev && hg commit -m "starting dev branch"')
stdout, stderr = _add_files_and_push(
'hg', tmpdir.strpath, clone_url=clone_url, target_branch='dev',
new_branch=True)
_check_proper_hg_push(stdout, stderr)
def test_push_new_branch_git_with_branch_permissions_no_force_push(
self, rc_web_server, tmpdir, user_util, branch_permission_setter):
repo = user_util.create_repo(repo_type='git')
repo_name = repo.repo_name
username = TEST_USER_ADMIN_LOGIN
branch_permission_setter(repo_name, username, permission='branch.push')
clone_url = rc_web_server.repo_clone_url(repo.repo_name)
Command(os.path.dirname(tmpdir.strpath)).execute(
'git clone', clone_url, tmpdir.strpath)
stdout, stderr = _add_files_and_push(
'git', tmpdir.strpath, clone_url=clone_url)
_check_proper_git_push(stdout, stderr)
# start new branch, and push file into it
Command(tmpdir.strpath).execute('git checkout -b dev')
stdout, stderr = _add_files_and_push(
'git', tmpdir.strpath, clone_url=clone_url, target_branch='dev',
new_branch=True)
_check_proper_git_push(stdout, stderr, branch='dev')