[PATCH 1 of 2] acl: add support for branch-based access control; more informative messages

elifarley at gmail.com elifarley at gmail.com
Fri Apr 30 13:58:57 CDT 2010


 hgext/acl.py |  145 ++++++++++++++++++++++++++++++++++++++++++++--------------
 1 files changed, 110 insertions(+), 35 deletions(-)


# HG changeset patch
# User Elifarley Callado Coelho Cruz <elifarley at gmail.com>
# Date 1272653362 10800
# Node ID 1e8aa8003350427354c21e74220152c19b9ab975
# Parent  bf7e63fedca12ab725af8a5cb0d0eced403bc7ec
acl: add support for branch-based access control; more informative messages.

diff --git a/hgext/acl.py b/hgext/acl.py
--- a/hgext/acl.py
+++ b/hgext/acl.py
@@ -7,9 +7,9 @@
 
 '''hooks for controlling repository access
 
-This hook makes it possible to allow or deny write access to portions
-of a repository when receiving incoming changesets via pretxnchangegroup and
-pretxncommit.
+This hook makes it possible to allow or deny write access to given branches and
+paths of a repository when receiving incoming changesets via pretxnchangegroup
+and pretxncommit.
 
 The authorization is matched based on the local user name on the
 system where the hook runs, and not the committer of the original
@@ -22,12 +22,34 @@
 Nor is it safe if remote users share an account, because then there
 is no way to distinguish them.
 
-The deny list is checked before the allow list is.
+The order in which access checks are performed is:
+1) Deny  list for branches (section [acl.deny.branches])
+2) Allow list for branches (section [acl.allow.branches])
+3) Deny  list for paths    (section [acl.deny])
+4) Allow list for paths    (section [acl.allow])
 
-The allow and deny sections take key-value pairs, having a subtree pattern
-as key (with a glob syntax by default). The corresponding value can be either:
-1) an asterisk, to match everyone;
-2) a comma-separated list containing users and groups.
+The allow and deny sections take key-value pairs.
+
+--- Branch-based Access Control ---
+
+Use the [acl.deny.branches] and [acl.allow.branches] sections to have
+branch-based access control.
+
+Keys in these sections can be either:
+1) a branch name
+2) an asterisk, to match any branch;
+
+The corresponding values can be either:
+1) a comma-separated list containing users and groups.
+2) an asterisk, to match anyone;
+
+--- Path-based Access Control ---
+
+Use the [acl.deny] and [acl.allow] sections to have path-based access control.
+Keys in these sections accept a subtree pattern (with a glob syntax by default).
+The corresponding values follow the same syntax as the other sections above.
+
+--- Groups ---
 
 Group names must be prefixed with an @ symbol.
 Specifying a group name has the same effect as specifying all the users in
@@ -35,10 +57,7 @@
 The set of users for a group is taken from "grp.getgrnam"
 (see http://docs.python.org/library/grp.html#grp.getgrnam).
 
-To use this hook, configure the acl extension in your hgrc like this:
-
-  [extensions]
-  acl =
+--- Example Configuration ---
 
   [hooks]
 
@@ -54,9 +73,30 @@
   # ("serve" == ssh or http, "push", "pull", "bundle")
   sources = serve
 
+  [acl.deny.branches] 
+  
+  # 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]
-  # This list is checked first. If a match is found, 'acl.allow' will not be
-  # checked.
+  # If a match is found, 'acl.allow' will not be checked.
   # if acl.deny is not present, no users denied by default
   # empty acl.deny = all users allowed
   # Format for both lists: glob pattern = user4, user5, @group1
@@ -97,6 +137,11 @@
 from mercurial import util, match
 import getpass, urllib, grp
 
+DENY_BRANCHES = 'acl.deny.branches'
+ALLOW_BRANCHES = 'acl.allow.branches'
+DENY_FILES = 'acl.deny'
+ALLOW_FILES = 'acl.allow'
+
 def _getusers(group):
     return grp.getgrnam(group).gr_mem
 
@@ -111,25 +156,31 @@
 
     return False
 
-def buildmatch(ui, repo, user, key):
+def _buildmatch(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)
+        ui.debug('acl: [%s] not defined\n' % key)
         return None
 
-    pats = [pat for pat, users in ui.configitems(key)
+    items = [item for item, users in ui.configitems(key)
             if _usermatch(user, users)]
-    ui.debug('acl: %s enabled, %d entries for user %s\n' %
-             (key, len(pats), user))
-    if pats:
-        return match.match(repo.root, '', pats)
-    return match.exact(repo.root, '', [])
+    ui.debug('acl: [%s] has %d entries\n' % (key, len(items)))
 
+    if not items:
+        return lambda b: False
+
+    if repo:
+        return match.match(repo.root, '', items)
+
+    return lambda b: '*' in items or b in items
 
 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
+
     if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
         raise util.Abort(_('config error - hook type "%s" cannot stop '
                            'incoming changesets nor commits') % hooktype)
+
     if (hooktype == 'pretxnchangegroup' and
         source not in ui.config('acl', 'sources', 'serve').split()):
         ui.debug('acl: changes have source "%s" - skipping\n' % source)
@@ -144,19 +195,43 @@
     if user is None:
         user = getpass.getuser()
 
+    ui.debug('acl: checking access for user "%s"\n' % user)
+
     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')
+        ui.readconfig(cfg, sections = [ALLOW_BRANCHES, DENY_BRANCHES,
+                                       ALLOW_FILES, DENY_FILES])
 
-    for rev in xrange(repo[node], len(repo)):
-        ctx = repo[rev]
-        for f in ctx.files():
-            if deny and deny(f):
-                ui.debug('acl: user %s denied on %s\n' % (user, f))
-                raise util.Abort(_('acl: access denied for changeset %s') % ctx)
-            if allow and not allow(f):
-                ui.debug('acl: user %s not allowed on %s\n' % (user, f))
-                raise util.Abort(_('acl: access denied for changeset %s') % ctx)
-        ui.debug('acl: allowing changeset %s\n' % ctx)
+    allow = _buildmatch(ui, None, user, ALLOW_BRANCHES)
+    deny = _buildmatch(ui, None, user, DENY_BRANCHES)
+
+    if deny or allow:
+        ui.debug('acl: checking branch access\n')
+        for rev in xrange(repo[node], len(repo)):
+            branch = repo[rev].branch()
+            if deny and deny(branch):
+                raise util.Abort(_('acl: user "%s" denied on branch "%s"'
+                                   ' (changeset "%s")')
+                                   % (user, branch, repo[rev]))
+            if allow and not allow(branch):
+                raise util.Abort(_('acl: user "%s" not allowed on branch "%s"'
+                                   ' (changeset "%s")')
+                                   % (user, branch, repo[rev]))
+            ui.debug('acl: access granted: "%s" on branch "%s"\n'
+            % (repo[rev], branch))
+
+    allow = _buildmatch(ui, repo, user, ALLOW_FILES)
+    deny = _buildmatch(ui, repo, user, DENY_FILES)
+
+    if deny or allow:
+        ui.debug('acl: checking path access\n')
+        for rev in xrange(repo[node], len(repo)):
+            ctx = repo[rev]
+            for f in ctx.files():
+                if deny and deny(f):
+                    raise util.Abort(_('acl: user "%s" denied on "%s"'
+                    ' (changeset "%s")') % (user, f, ctx))
+                if allow and not allow(f):
+                    raise util.Abort(_('acl: user "%s" not allowed on "%s"'
+                    ' (changeset "%s")') % (user, f, ctx))
+            ui.debug('acl: access granted: "%s"\n' % ctx)


More information about the Mercurial-devel mailing list