##// END OF EJS Templates
release: Start preparation for 4.19.1
release: Start preparation for 4.19.1

File last commit:

r4306:09801de9 default
r4365:8743873d stable
Show More
hooks_base.py
509 lines | 18.6 KiB | text/x-python | PythonLexer
project: added all source files and assets
r1 # -*- coding: utf-8 -*-
code: update copyrights to 2020
r4306 # Copyright (C) 2013-2020 RhodeCode GmbH
project: added all source files and assets
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/
"""
Set of hooks run by RhodeCode Enterprise
"""
import os
hooks: added extra logging.
r1455 import logging
project: added all source files and assets
r1
import rhodecode
dan
events: add event system for RepoEvents
r375 from rhodecode import events
project: added all source files and assets
r1 from rhodecode.lib import helpers as h
audit-logs: add push/pull actions to audit logs.
r1736 from rhodecode.lib import audit_logger
project: added all source files and assets
r1 from rhodecode.lib.utils2 import safe_str
branch-permissions: handle vcs operations and branch permissions....
r2979 from rhodecode.lib.exceptions import (
HTTPLockedRC, HTTPBranchProtected, UserCreationError)
project: added all source files and assets
r1 from rhodecode.model.db import Repository, User
hooks: added extra logging.
r1455 log = logging.getLogger(__name__)
project: added all source files and assets
r1
rcextensions: new builtin rcextensions....
r3133 class HookResponse(object):
def __init__(self, status, output):
self.status = status
self.output = output
def __add__(self, other):
other_status = getattr(other, 'status', 0)
new_status = max(self.status, other_status)
other_output = getattr(other, 'output', '')
new_output = self.output + other_output
return HookResponse(new_status, new_output)
def __bool__(self):
return self.status == 0
project: added all source files and assets
r1
Martin Bornhold
vcs: Do not trigger external hook in case of actions on shadow respositories.
r900 def is_shadow_repo(extras):
"""
Returns ``True`` if this is an action executed against a shadow repository.
"""
return extras['is_shadow_repo']
project: added all source files and assets
r1 def _get_scm_size(alias, root_path):
if not alias.startswith('.'):
alias += '.'
size_scm, size_root = 0, 0
for path, unused_dirs, files in os.walk(safe_str(root_path)):
if path.find(alias) != -1:
for f in files:
try:
size_scm += os.path.getsize(os.path.join(path, f))
except OSError:
pass
else:
for f in files:
try:
size_root += os.path.getsize(os.path.join(path, f))
except OSError:
pass
size_scm_f = h.format_byte_size_binary(size_scm)
size_root_f = h.format_byte_size_binary(size_root)
size_total_f = h.format_byte_size_binary(size_root + size_scm)
return size_scm_f, size_root_f, size_total_f
# actual hooks called by Mercurial internally, and GIT by our Python Hooks
def repo_size(extras):
"""Present size of repository after push."""
repo = Repository.get_by_repo_name(extras.repository)
vcs_part = safe_str(u'.%s' % repo.repo_type)
size_vcs, size_root, size_total = _get_scm_size(vcs_part,
repo.repo_full_path)
msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
% (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
return HookResponse(0, msg)
def pre_push(extras):
"""
Hook executed before pushing code.
It bans pushing when the repository is locked.
"""
extensions: adapt hooks to latest version of extensions (pre-push)
r1456
branch-permissions: handle vcs operations and branch permissions....
r2979 user = User.get_by_username(extras.username)
project: added all source files and assets
r1 output = ''
branch-permissions: handle vcs operations and branch permissions....
r2979 if extras.locked_by[0] and user.user_id != int(extras.locked_by[0]):
project: added all source files and assets
r1 locked_by = User.get(extras.locked_by[0]).username
reason = extras.locked_by[2]
# this exception is interpreted in git/hg middlewares and based
# on that proper return code is server to client
_http_ret = HTTPLockedRC(
_locked_by_explanation(extras.repository, locked_by, reason))
if str(_http_ret.code).startswith('2'):
# 2xx Codes don't raise exceptions
output = _http_ret.title
else:
raise _http_ret
rcextensions: new builtin rcextensions....
r3133 hook_response = ''
Martin Bornhold
vcs: Do not trigger external hook in case of actions on shadow respositories.
r900 if not is_shadow_repo(extras):
branch-permissions: handle vcs operations and branch permissions....
r2979 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.
rcextensions: new builtin rcextensions....
r3133 hook_response = pre_push_extension(
repo_store_path=Repository.base_path(), **extras)
Martin Bornhold
vcs: Do not trigger external hook in case of actions on shadow respositories.
r900 events.trigger(events.RepoPrePushEvent(
repo_name=extras.repository, extras=extras))
dan
events: add event system for RepoEvents
r375
rcextensions: new builtin rcextensions....
r3133 return HookResponse(0, output) + hook_response
project: added all source files and assets
r1
def pre_pull(extras):
"""
Hook executed before pulling the code.
It bans pulling when the repository is locked.
"""
output = ''
if 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
# on that proper return code is server to client
_http_ret = HTTPLockedRC(
_locked_by_explanation(extras.repository, locked_by, reason))
if str(_http_ret.code).startswith('2'):
# 2xx Codes don't raise exceptions
output = _http_ret.title
else:
raise _http_ret
Martin Bornhold
vcs: Do not trigger external hook in case of actions on shadow respositories.
r900 # Propagate to external components. This is done after checking the
# lock, for consistent behavior.
rcextensions: new builtin rcextensions....
r3133 hook_response = ''
Martin Bornhold
vcs: Do not trigger external hook in case of actions on shadow respositories.
r900 if not is_shadow_repo(extras):
rcextensions: new builtin rcextensions....
r3133 extras.hook_type = extras.hook_type or 'pre_pull'
hook_response = pre_pull_extension(
repo_store_path=Repository.base_path(), **extras)
Martin Bornhold
vcs: Do not trigger external hook in case of actions on shadow respositories.
r900 events.trigger(events.RepoPrePullEvent(
repo_name=extras.repository, extras=extras))
project: added all source files and assets
r1
rcextensions: new builtin rcextensions....
r3133 return HookResponse(0, output) + hook_response
project: added all source files and assets
r1
def post_pull(extras):
"""Hook executed after client pulls the code."""
audit-logs: add push/pull actions to audit logs.
r1736 audit_user = audit_logger.UserWrap(
username=extras.username,
ip_addr=extras.ip)
audit-logs: store repository name on pull action.
r1737 repo = audit_logger.RepoWrap(repo_name=extras.repository)
audit-logs: add push/pull actions to audit logs.
r1736 audit_logger.store(
rcextensions: new builtin rcextensions....
r3133 'user.pull', action_data={'user_agent': extras.user_agent},
audit-logs: store repository name on pull action.
r1737 user=audit_user, repo=repo, commit=True)
audit-logs: add push/pull actions to audit logs.
r1736
project: added all source files and assets
r1 output = ''
# make lock is a tri state False, True, None. We only make lock on True
Martin Bornhold
vcs: Do not trigger external hook in case of actions on shadow respositories.
r900 if extras.make_lock is True and not is_shadow_repo(extras):
hooks: deprecate old action_logger
r1754 user = User.get_by_username(extras.username)
project: added all source files and assets
r1 Repository.lock(Repository.get_by_repo_name(extras.repository),
user.user_id,
lock_reason=Repository.LOCK_PULL)
msg = 'Made lock on repo `%s`' % (extras.repository,)
output += msg
if extras.locked_by[0]:
locked_by = User.get(extras.locked_by[0]).username
reason = extras.locked_by[2]
_http_ret = HTTPLockedRC(
_locked_by_explanation(extras.repository, locked_by, reason))
if str(_http_ret.code).startswith('2'):
# 2xx Codes don't raise exceptions
output += _http_ret.title
rcextensions: new builtin rcextensions....
r3133 # Propagate to external components.
hook_response = ''
if not is_shadow_repo(extras):
extras.hook_type = extras.hook_type or 'post_pull'
hook_response = post_pull_extension(
repo_store_path=Repository.base_path(), **extras)
events.trigger(events.RepoPullEvent(
repo_name=extras.repository, extras=extras))
return HookResponse(0, output) + hook_response
project: added all source files and assets
r1
def post_push(extras):
"""Hook executed after user pushes to the repository."""
hooks: deprecate old action_logger
r1754 commit_ids = extras.commit_ids
project: added all source files and assets
r1
hooks: deprecate old action_logger
r1754 # log the push call
audit-logs: add push/pull actions to audit logs.
r1736 audit_user = audit_logger.UserWrap(
hooks: deprecate old action_logger
r1754 username=extras.username, ip_addr=extras.ip)
audit-logs: add push/pull actions to audit logs.
r1736 repo = audit_logger.RepoWrap(repo_name=extras.repository)
audit_logger.store(
audit-logs: implemented full audit logs across application....
r1829 'user.push', action_data={
audit-logs: add push/pull actions to audit logs.
r1736 'user_agent': extras.user_agent,
audit-logs: use stricter limit on how much data the commits key can hold....
r1964 'commit_ids': commit_ids[:400]},
audit-logs: add push/pull actions to audit logs.
r1736 user=audit_user, repo=repo, commit=True)
Martin Bornhold
vcs: Do not trigger external hook in case of actions on shadow respositories.
r900 # Propagate to external components.
project: added all source files and assets
r1 output = ''
# make lock is a tri state False, True, None. We only release lock on False
Martin Bornhold
vcs: Do not trigger external hook in case of actions on shadow respositories.
r900 if extras.make_lock is False and not is_shadow_repo(extras):
project: added all source files and assets
r1 Repository.unlock(Repository.get_by_repo_name(extras.repository))
hooks: fixed again unicode problems with new pull-request link generator
r3503 msg = 'Released lock on repo `{}`\n'.format(safe_str(extras.repository))
project: added all source files and assets
r1 output += msg
if extras.locked_by[0]:
locked_by = User.get(extras.locked_by[0]).username
reason = extras.locked_by[2]
_http_ret = HTTPLockedRC(
_locked_by_explanation(extras.repository, locked_by, reason))
# TODO: johbo: if not?
if str(_http_ret.code).startswith('2'):
# 2xx Codes don't raise exceptions
output += _http_ret.title
hooks: expose refs on push....
r1755 if extras.new_refs:
hooks: fixed again unicode problems with new pull-request link generator
r3503 tmpl = '{}/{}/pull-request/new?{{ref_type}}={{ref_name}}'.format(
safe_str(extras.server_url), safe_str(extras.repository))
hooks: handle non-ascii characters in hooks new pull-requests open template.
r3331
hooks: expose refs on push....
r1755 for branch_name in extras.new_refs['branches']:
output += 'RhodeCode: open pull request link: {}\n'.format(
hooks: handle non-ascii characters in hooks new pull-requests open template.
r3331 tmpl.format(ref_type='branch', ref_name=safe_str(branch_name)))
hooks: expose refs on push....
r1755
for book_name in extras.new_refs['bookmarks']:
output += 'RhodeCode: open pull request link: {}\n'.format(
hooks: handle non-ascii characters in hooks new pull-requests open template.
r3331 tmpl.format(ref_type='bookmark', ref_name=safe_str(book_name)))
hooks: expose refs on push....
r1755
rcextensions: new builtin rcextensions....
r3133 hook_response = ''
if not is_shadow_repo(extras):
hook_response = post_push_extension(
repo_store_path=Repository.base_path(),
**extras)
events.trigger(events.RepoPushEvent(
repo_name=extras.repository, pushed_commit_ids=commit_ids, extras=extras))
project: added all source files and assets
r1 output += 'RhodeCode: push completed\n'
rcextensions: new builtin rcextensions....
r3133 return HookResponse(0, output) + hook_response
project: added all source files and assets
r1
def _locked_by_explanation(repo_name, user_name, reason):
message = (
'Repository `%s` locked by user `%s`. Reason:`%s`'
% (repo_name, user_name, reason))
return message
def check_allowed_create_user(user_dict, created_by, **kwargs):
# pre create hooks
if pre_create_user.is_active():
rcextensions: new builtin rcextensions....
r3133 hook_result = pre_create_user(created_by=created_by, **user_dict)
allowed = hook_result.status == 0
project: added all source files and assets
r1 if not allowed:
rcextensions: new builtin rcextensions....
r3133 reason = hook_result.output
project: added all source files and assets
r1 raise UserCreationError(reason)
class ExtensionCallback(object):
"""
Forwards a given call to rcextensions, sanitizes keyword arguments.
Does check if there is an extension active for that hook. If it is
there, it will forward all `kwargs_keys` keyword arguments to the
extension callback.
"""
def __init__(self, hook_name, kwargs_keys):
self._hook_name = hook_name
self._kwargs_keys = set(kwargs_keys)
def __call__(self, *args, **kwargs):
hooks: use safer way to validate kwargs. If rcextensions are disable we shouldn't...
r3200 log.debug('Calling extension callback for `%s`', self._hook_name)
callback = self._get_callback()
if not callback:
log.debug('extension callback `%s` not found, skipping...', self._hook_name)
return
rcextensions: new builtin rcextensions....
r3133 kwargs_to_pass = {}
for key in self._kwargs_keys:
try:
kwargs_to_pass[key] = kwargs[key]
except KeyError:
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 log.error('Failed to fetch %s key from given kwargs. '
'Expected keys: %s', key, self._kwargs_keys)
rcextensions: new builtin rcextensions....
r3133 raise
hooks: added extra logging.
r1455
hooks: use safer way to validate kwargs. If rcextensions are disable we shouldn't...
r3200 # backward compat for removed api_key for old hooks. This was it works
auth-tokens: fixed tests
r1482 # with older rcextensions that require api_key present
if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
kwargs_to_pass['api_key'] = '_DEPRECATED_'
hooks: use safer way to validate kwargs. If rcextensions are disable we shouldn't...
r3200 return callback(**kwargs_to_pass)
project: added all source files and assets
r1
def is_active(self):
return hasattr(rhodecode.EXTENSIONS, self._hook_name)
def _get_callback(self):
return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
pre_pull_extension = ExtensionCallback(
hook_name='PRE_PULL_HOOK',
kwargs_keys=(
'server_url', 'config', 'scm', 'username', 'ip', 'action',
rcextensions: new builtin rcextensions....
r3133 'repository', 'hook_type', 'user_agent', 'repo_store_path',))
project: added all source files and assets
r1
post_pull_extension = ExtensionCallback(
hook_name='PULL_HOOK',
kwargs_keys=(
'server_url', 'config', 'scm', 'username', 'ip', 'action',
rcextensions: new builtin rcextensions....
r3133 'repository', 'hook_type', 'user_agent', 'repo_store_path',))
project: added all source files and assets
r1
pre_push_extension = ExtensionCallback(
hook_name='PRE_PUSH_HOOK',
kwargs_keys=(
'server_url', 'config', 'scm', 'username', 'ip', 'action',
rcextensions: new builtin rcextensions....
r3133 'repository', 'repo_store_path', 'commit_ids', 'hook_type', 'user_agent',))
project: added all source files and assets
r1
post_push_extension = ExtensionCallback(
hook_name='PUSH_HOOK',
kwargs_keys=(
'server_url', 'config', 'scm', 'username', 'ip', 'action',
rcextensions: new builtin rcextensions....
r3133 'repository', 'repo_store_path', 'commit_ids', 'hook_type', 'user_agent',))
project: added all source files and assets
r1
pre_create_user = ExtensionCallback(
hook_name='PRE_CREATE_USER_HOOK',
kwargs_keys=(
'username', 'password', 'email', 'firstname', 'lastname', 'active',
'admin', 'created_by'))
log_create_pull_request = ExtensionCallback(
hook_name='CREATE_PULL_REQUEST',
kwargs_keys=(
'server_url', 'config', 'scm', 'username', 'ip', 'action',
'repository', 'pull_request_id', 'url', 'title', 'description',
'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
'mergeable', 'source', 'target', 'author', 'reviewers'))
log_merge_pull_request = ExtensionCallback(
hook_name='MERGE_PULL_REQUEST',
kwargs_keys=(
'server_url', 'config', 'scm', 'username', 'ip', 'action',
'repository', 'pull_request_id', 'url', 'title', 'description',
'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
'mergeable', 'source', 'target', 'author', 'reviewers'))
log_close_pull_request = ExtensionCallback(
hook_name='CLOSE_PULL_REQUEST',
kwargs_keys=(
'server_url', 'config', 'scm', 'username', 'ip', 'action',
'repository', 'pull_request_id', 'url', 'title', 'description',
'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
'mergeable', 'source', 'target', 'author', 'reviewers'))
log_review_pull_request = ExtensionCallback(
hook_name='REVIEW_PULL_REQUEST',
kwargs_keys=(
'server_url', 'config', 'scm', 'username', 'ip', 'action',
'repository', 'pull_request_id', 'url', 'title', 'description',
'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
'mergeable', 'source', 'target', 'author', 'reviewers'))
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 log_comment_pull_request = ExtensionCallback(
hook_name='COMMENT_PULL_REQUEST',
kwargs_keys=(
'server_url', 'config', 'scm', 'username', 'ip', 'action',
'repository', 'pull_request_id', 'url', 'title', 'description',
'status', 'comment', 'created_on', 'updated_on', 'commit_ids', 'review_status',
'mergeable', 'source', 'target', 'author', 'reviewers'))
project: added all source files and assets
r1 log_update_pull_request = ExtensionCallback(
hook_name='UPDATE_PULL_REQUEST',
kwargs_keys=(
'server_url', 'config', 'scm', 'username', 'ip', 'action',
'repository', 'pull_request_id', 'url', 'title', 'description',
'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
'mergeable', 'source', 'target', 'author', 'reviewers'))
log_create_user = ExtensionCallback(
hook_name='CREATE_USER_HOOK',
kwargs_keys=(
'username', 'full_name_or_username', 'full_contact', 'user_id',
'name', 'firstname', 'short_contact', 'admin', 'lastname',
'ip_addresses', 'extern_type', 'extern_name',
auth-tokens: fixed tests
r1482 'email', 'api_keys', 'last_login',
project: added all source files and assets
r1 'full_name', 'active', 'password', 'emails',
'inherit_default_permissions', 'created_by', 'created_on'))
log_delete_user = ExtensionCallback(
hook_name='DELETE_USER_HOOK',
kwargs_keys=(
'username', 'full_name_or_username', 'full_contact', 'user_id',
'name', 'firstname', 'short_contact', 'admin', 'lastname',
'ip_addresses',
auth-tokens: fixed tests
r1482 'email', 'last_login',
project: added all source files and assets
r1 'full_name', 'active', 'password', 'emails',
'inherit_default_permissions', 'deleted_by'))
log_create_repository = ExtensionCallback(
hook_name='CREATE_REPO_HOOK',
kwargs_keys=(
'repo_name', 'repo_type', 'description', 'private', 'created_on',
'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
'clone_uri', 'fork_id', 'group_id', 'created_by'))
log_delete_repository = ExtensionCallback(
hook_name='DELETE_REPO_HOOK',
kwargs_keys=(
'repo_name', 'repo_type', 'description', 'private', 'created_on',
'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
dan
hooks: added new hooks for comments on pull requests and commits....
r4305 log_comment_commit_repository = ExtensionCallback(
hook_name='COMMENT_COMMIT_REPO_HOOK',
kwargs_keys=(
'repo_name', 'repo_type', 'description', 'private', 'created_on',
'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
'clone_uri', 'fork_id', 'group_id',
'repository', 'created_by', 'comment', 'commit'))
project: added all source files and assets
r1 log_create_repository_group = ExtensionCallback(
hook_name='CREATE_REPO_GROUP_HOOK',
kwargs_keys=(
'group_name', 'group_parent_id', 'group_description',
'group_id', 'user_id', 'created_by', 'created_on',
'enable_locking'))