Skip to content
Snippets Groups Projects
Commit a9d9308d authored by Georges Racinet's avatar Georges Racinet
Browse files

hooks.perm: new module for permission check hooks

For now we just extract the generic part of `check_publish`.
parent 7132b76b
No related branches found
No related tags found
1 merge request!6New hook to check write permissions outside of HTTP contexts
from mercurial import (
error,
scmutil,
configitems,
phases,
)
......@@ -3,8 +2,9 @@
scmutil,
configitems,
phases,
)
from . import perm
if 'allow-publish' not in configitems.coreitems['web']:
configitems._register(configitems.coreitems,
......@@ -16,14 +16,10 @@
def check_publish(repo, *args, **kwargs):
hooktype = kwargs.get('hooktype')
if hooktype != "pretxnclose":
msg = 'check_publish must be used as "pretxnclose" hook (not "%s")'
raise error.ProgrammingError(msg % hooktype)
remoteuser = repo.ui.environ.get("REMOTE_USER", None)
allowed = repo.ui.configlist('web', 'allow-publish')
perm.validate_hook_type('pretxnclose', **kwargs)
remoteuser = perm.get_remote_user(repo)
if remoteuser is not None:
repo.ui.debug(
'check_publish detected push from user: %r\n' % remoteuser)
......@@ -25,12 +21,8 @@
if remoteuser is not None:
repo.ui.debug(
'check_publish detected push from user: %r\n' % remoteuser)
# remoteuser `None` is for command-line operations. In the context
# of Heptapod, that's from other GitLab components that perform
# their own permission checks (e.g., merge request from the UI)
if remoteuser is None or '*' in allowed or remoteuser in allowed:
repo.ui.debug('check_publish user %r is allowed, allowed=%r' % (
remoteuser, allowed))
if perm.allowed(repo, remoteuser, 'web', 'allow-publish'):
# we have nothing more to check
return 0
......@@ -36,4 +28,5 @@
return 0
tr = repo.currenttransaction()
assert tr is not None and tr.running()
phaseschanges = tr.changes.get("phases", {})
......
from mercurial import (
error,
)
def validate_hook_type(expected, hooktype=None, **kw):
"""Check that the hook has the expected type, and raise if not.
It is really important that permission check hooks get executed
in the right position (pretxnopen, pretxclose).
For example, it's better to reject invalid writes right away than to
rollback them, for efficiency and security (smaller attack surface).
It's also a matter of having the right information at disposal.
Example usage::
>>> from heptapod.hooks import perm
>>> def the_hook(repo, *args, **kwargs):
... perm.validate_hook_type('precommit', **kwargs)
... return 0 # success
``repo`` is not needed for our example, and actually that proves it won't
be used at all::
>>> repo = None
>>> the_hook(None, hooktype='precommit')
0
>>> the_hook(None, hooktype='postxn') # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
ProgrammingError: This hook must be used as 'precommit', not 'postxn'.
:param expected: the proper hook type, e.g, ``pretxnopen``.
:param hooktype: should be passed straight from hook kwargs
"""
if hooktype == expected:
return
raise error.ProgrammingError(
"This hook must be used as a %r, not %r" % (expected, hooktype))
def get_remote_user(repo):
"""Return remote user name, or `None`
within Heptapod, the absence of a remote user means that `hg` is invoked
from the command line, or directly by a server-side process, such as
the Rails application performing a merge.
Hence `None` implies to skip all permission checks.
"""
return repo.ui.environ.get("REMOTE_USER", None)
def allowed(repo, remote_user, config_group, config_item):
"""Read from repo config list to check if given user is allowed.
within Heptapod, ``remote_user`` being ``None`` means that `hg` is invoked
from the command line, or directly by a server-side process, such as
the Rails application performing a merge.
In these cases, the permission
checking responsibility is on the caller. Hence a ``None`` remote user
is always allowed.
"""
allowed = repo.ui.configlist(config_group, config_item)
if remote_user is None or '*' in allowed or remote_user in allowed:
repo.ui.debug('user %r is allowed by %s.%s=%r' % (
remote_user, config_group, config_item, allowed))
return True
return False
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment