[PATCH - BACL] Branch access control extension

Elifarley Callado Coelho Cruz elifarley at gmail.com
Tue Apr 13 12:24:05 CDT 2010


Here's an example configuration:

[hooks]
pretxncommit.acl = python:hgext.acl.hook
pretxnchangegroup.acl = python:hgext.acl.hook

[acl]
# Works as before.
# Branch checks are performed before file checks.
sources = serve

[acl.deny.branches]
# acl.deny.branches is checked first.

# Everyone is denied to the frozen branch:
#frozen-branch = *

# A bad user is denied on all branches:
#* = bad-user

[acl.allow.branches]

# A few users are allowed on branch-a:
#branch-a = user-1, user-2, user-3

# Only one user is allowed on branch-b:
#branch-b = user-1

# The super user is allowed on any branch:
#* = super-user

# Everyone is allowed on branch-for-tests:
#branch-for-tests = *

[acl.deny]
# Works as before.

[acl.allow]
# Works as before.


-------



And here's the patch (taken from
http://bitbucket.org/elifarley/hg-ecc/changeset/2816980e6012/raw/hg-ecc-2816980e6012.diff
):
-------

# HG changeset patch -- Bitbucket.org
# Project hg-ecc
# URL http://bitbucket.org/elifarley/hg-ecc/overview/
# User Elifarley Callado Coelho Cruz <elifarley at gmail.com>
# Date 1271165117 10800
# Node ID 2816980e6012a30d3a225437e76a78130117e753
# Parent  17fd494d0792452204d82b1ea9df25500a310041
Added support for branch access control. Now it is possible to allow
or deny write access to named branches of a repository when receiving
incoming changesets (via 'pretxncommit' and 'pretxnchangegroup').

--- a/acl.py
+++ b/acl.py
@@ -56,7 +56,61 @@ from mercurial.i18n import _
 from mercurial import util, match
 import getpass, urllib

-def buildmatch(ui, repo, user, key):
+ALLOW_BRANCHES = 'acl.allow.branches'
+DENY_BRANCHES = 'acl.deny.branches'
+
+def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
+    hook_branches(ui, repo, hooktype, node, source, **kwargs)
+    hook_files(ui, repo, hooktype, node, source, **kwargs)
+
+def buildmatch_branches(ui, user, key):
+    '''return tuple of (match function, list enabled).'''
+    if not ui.has_section(key):
+        ui.debug('bacl: "%s" not enabled\n' % key)
+        return None
+
+    branches = [branch for branch, users in ui.configitems(key)
+            if users == '*' or user in users.replace(',', ' ').split()]
+    ui.debug('bacl: "%s" enabled, %d entries for user "%s"\n' %
+             (key, len(branches), user))
+    if branches:
+        return lambda b: '*' in branches or b in branches
+    return lambda b: False
+
+def hook_branches(ui, repo, hooktype, node=None, source=None, **kwargs):
+    if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
+        raise util.Abort(_('config error - hook_branches type "%s"
cannot stop '
+                           'incoming changesets nor commits') % hooktype)
+    if 'pretxnchangegroup' == hooktype and source not in
ui.config('acl', 'sources', 'serve').split():
+        ui.debug('bacl: changes have source "%s" - skipping\n' % source)
+        return
+
+    user = None
+    if source == 'serve' and 'url' in kwargs:
+        url = kwargs['url'].split(':')
+        if url[0] == 'remote' and url[1].startswith('http'):
+            user = urllib.unquote(url[3])
+            ui.debug('bacl: got user "%s" from url.\n' % user)
+
+    if user is None:
+        user = getpass.getuser()
+        ui.debug('bacl: got user "%s" from getpass.\n')
+
+    cfg = ui.config('acl', 'config')
+    if cfg:
+        ui.readconfig(cfg, sections = [ALLOW_BRANCHES, DENY_BRANCHES])
+    allow = buildmatch_branches(ui, user, ALLOW_BRANCHES)
+    deny = buildmatch_branches(ui, user, DENY_BRANCHES)
+
+    for rev in xrange(repo[node], len(repo)):
+        branch = repo[rev].branch()
+        if deny and deny(branch):
+            raise util.Abort(_('bacl: user "%s" denied on branch
"%s"') % (user, branch))
+        if allow and not allow(branch):
+            raise util.Abort(_('bacl: user "%s" not allowed on branch
"%s"') % (user, branch))
+        ui.debug('bacl: allowing user "%s" on branch "%s"\n' % (user, branch))
+
+def buildmatch_files(ui, repo, user, key):
     '''return tuple of (match function, list enabled).'''
     if not ui.has_section(key):
         ui.debug('acl: %s not enabled\n' % key)
@@ -70,8 +124,7 @@ def buildmatch(ui, repo, user, key):
         return match.match(repo.root, '', pats)
     return match.exact(repo.root, '', [])

-
-def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
+def hook_files(ui, repo, hooktype, node=None, source=None, **kwargs):
     if hooktype != 'pretxnchangegroup':
         raise util.Abort(_('config error - hook type "%s" cannot stop '
                            'incoming changesets') % hooktype)
@@ -91,8 +144,8 @@ def hook(ui, repo, hooktype, node=None,
     cfg = ui.config('acl', 'config')
     if cfg:
         ui.readconfig(cfg, sections = ['acl.allow', 'acl.deny'])
-    allow = buildmatch(ui, repo, user, 'acl.allow')
-    deny = buildmatch(ui, repo, user, 'acl.deny')
+    allow = buildmatch_files(ui, repo, user, 'acl.allow')
+    deny = buildmatch_files(ui, repo, user, 'acl.deny')

     for rev in xrange(repo[node], len(repo)):
         ctx = repo[rev]


More information about the Mercurial-devel mailing list