]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/hgext/acl.py
hgwebfs: write headers individually, so they are not limited by webfs iounit (thanks...
[plan9front.git] / sys / lib / python / hgext / acl.py
1 # acl.py - changeset access control for mercurial
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
7 #
8
9 '''hooks for controlling repository access
10
11 This hook makes it possible to allow or deny write access to portions
12 of a repository when receiving incoming changesets.
13
14 The authorization is matched based on the local user name on the
15 system where the hook runs, and not the committer of the original
16 changeset (since the latter is merely informative).
17
18 The acl hook is best used along with a restricted shell like hgsh,
19 preventing authenticating users from doing anything other than
20 pushing or pulling. The hook is not safe to use if users have
21 interactive shell access, as they can then disable the hook.
22 Nor is it safe if remote users share an account, because then there
23 is no way to distinguish them.
24
25 To use this hook, configure the acl extension in your hgrc like this::
26
27   [extensions]
28   hgext.acl =
29
30   [hooks]
31   pretxnchangegroup.acl = python:hgext.acl.hook
32
33   [acl]
34   # Check whether the source of incoming changes is in this list
35   # ("serve" == ssh or http, "push", "pull", "bundle")
36   sources = serve
37
38 The allow and deny sections take a subtree pattern as key (with a glob
39 syntax by default), and a comma separated list of users as the
40 corresponding value. The deny list is checked before the allow list
41 is. ::
42
43   [acl.allow]
44   # If acl.allow is not present, all users are allowed by default.
45   # An empty acl.allow section means no users allowed.
46   docs/** = doc_writer
47   .hgtags = release_engineer
48
49   [acl.deny]
50   # If acl.deny is not present, no users are refused by default.
51   # An empty acl.deny section means all users allowed.
52   glob pattern = user4, user5
53    ** = user6
54 '''
55
56 from mercurial.i18n import _
57 from mercurial import util, match
58 import getpass, urllib
59
60 def buildmatch(ui, repo, user, key):
61     '''return tuple of (match function, list enabled).'''
62     if not ui.has_section(key):
63         ui.debug(_('acl: %s not enabled\n') % key)
64         return None
65
66     pats = [pat for pat, users in ui.configitems(key)
67             if user in users.replace(',', ' ').split()]
68     ui.debug(_('acl: %s enabled, %d entries for user %s\n') %
69              (key, len(pats), user))
70     if pats:
71         return match.match(repo.root, '', pats)
72     return match.exact(repo.root, '', [])
73
74
75 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
76     if hooktype != 'pretxnchangegroup':
77         raise util.Abort(_('config error - hook type "%s" cannot stop '
78                            'incoming changesets') % hooktype)
79     if source not in ui.config('acl', 'sources', 'serve').split():
80         ui.debug(_('acl: changes have source "%s" - skipping\n') % source)
81         return
82
83     user = None
84     if source == 'serve' and 'url' in kwargs:
85         url = kwargs['url'].split(':')
86         if url[0] == 'remote' and url[1].startswith('http'):
87             user = urllib.unquote(url[3])
88
89     if user is None:
90         user = getpass.getuser()
91
92     cfg = ui.config('acl', 'config')
93     if cfg:
94         ui.readconfig(cfg, sections = ['acl.allow', 'acl.deny'])
95     allow = buildmatch(ui, repo, user, 'acl.allow')
96     deny = buildmatch(ui, repo, user, 'acl.deny')
97
98     for rev in xrange(repo[node], len(repo)):
99         ctx = repo[rev]
100         for f in ctx.files():
101             if deny and deny(f):
102                 ui.debug(_('acl: user %s denied on %s\n') % (user, f))
103                 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
104             if allow and not allow(f):
105                 ui.debug(_('acl: user %s not allowed on %s\n') % (user, f))
106                 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
107         ui.debug(_('acl: allowing changeset %s\n') % ctx)