comparison hgext/acl.py @ 2344:ae12e5a2c4a3

add acl extension, to limit who can push to subdirs of central repo.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Tue, 23 May 2006 14:58:30 -0700
parents
children 5e39ad2c8b52
comparison
equal deleted inserted replaced
2343:af81d8770620 2344:ae12e5a2c4a3
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
6 # of the GNU General Public License, incorporated herein by reference.
7 #
8 # this hook allows to allow or deny access to parts of a repo when
9 # taking incoming changesets.
10 #
11 # authorization is against local user name on system where hook is
12 # run, not committer of original changeset (since that is easy to
13 # spoof).
14 #
15 # acl hook is best to use if you use hgsh to set up restricted shells
16 # for authenticated users to only push to / pull from. not safe if
17 # user has interactive shell access, because they can disable hook.
18 # also not safe if remote users share one local account, because then
19 # no way to tell remote users apart.
20 #
21 # to use, configure acl extension in hgrc like this:
22 #
23 # [extensions]
24 # hgext.acl =
25 #
26 # [hooks]
27 # pretxnchangegroup.acl = python:hgext.acl.hook
28 #
29 # [acl]
30 # sources = serve # check if source of incoming changes in this list
31 # # ("serve" == ssh or http, "push", "pull", "bundle")
32 #
33 # allow and deny lists have subtree pattern (default syntax is glob)
34 # on left, user names on right. deny list checked before allow list.
35 #
36 # [acl.allow]
37 # # if acl.allow not present, all users allowed by default
38 # # empty acl.allow = no users allowed
39 # docs/** = doc_writer
40 # .hgtags = release_engineer
41 #
42 # [acl.deny]
43 # # if acl.deny not present, no users denied by default
44 # # empty acl.deny = all users allowed
45 # glob pattern = user4, user5
46 # ** = user6
47
48 from mercurial.demandload import *
49 from mercurial.i18n import gettext as _
50 from mercurial.node import *
51 demandload(globals(), 'getpass mercurial:util')
52
53 class checker(object):
54 '''acl checker.'''
55
56 def buildmatch(self, key):
57 '''return tuple of (match function, list enabled).'''
58 if not self.ui.has_config(key):
59 self.ui.debug(_('acl: %s not enabled\n') % key)
60 return None, False
61
62 thisuser = self.getuser()
63 pats = [pat for pat, user in self.ui.configitems(key)
64 if user == thisuser]
65 self.ui.debug(_('acl: %s enabled, %d entries for user %s\n') %
66 (key, len(pats), thisuser))
67 if pats:
68 match = util.matcher(self.repo.root, names=pats)[1]
69 else:
70 match = util.never
71 return match, True
72
73 def getuser(self):
74 '''return name of authenticated user.'''
75 return self.user
76
77 def __init__(self, ui, repo):
78 self.ui = ui
79 self.repo = repo
80 self.user = getpass.getuser()
81 cfg = self.ui.config('acl', 'config')
82 if cfg:
83 self.ui.readconfig(cfg)
84 self.allow, self.allowable = self.buildmatch('acl.allow')
85 self.deny, self.deniable = self.buildmatch('acl.deny')
86
87 def skipsource(self, source):
88 '''true if incoming changes from this source should be skipped.'''
89 ok_sources = self.ui.config('acl', 'sources', 'serve').split()
90 return source not in ok_sources
91
92 def check(self, node):
93 '''return if access allowed, raise exception if not.'''
94 files = self.repo.changelog.read(node)[3]
95 if self.deniable:
96 for f in files:
97 if self.deny(f):
98 self.ui.debug(_('acl: user %s denied on %s\n') %
99 (self.getuser(), f))
100 raise util.Abort(_('acl: access denied for changeset %s') %
101 short(node))
102 if self.allowable:
103 for f in files:
104 if not self.allow(f):
105 self.ui.debug(_('acl: user %s not allowed on %s\n') %
106 (self.getuser(), f))
107 raise util.Abort(_('acl: access denied for changeset %s') %
108 short(node))
109 self.ui.debug(_('acl: allowing changeset %s\n') % short(node))
110
111 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
112 if hooktype != 'pretxnchangegroup':
113 raise util.Abort(_('config error - hook type "%s" cannot stop '
114 'incoming changesets') % hooktype)
115
116 c = checker(ui, repo)
117 if c.skipsource(source):
118 ui.debug(_('acl: changes have source "%s" - skipping\n') % source)
119 return
120
121 start = repo.changelog.rev(bin(node))
122 end = repo.changelog.count()
123 for rev in xrange(start, end):
124 c.check(repo.changelog.node(rev))