##// END OF EJS Templates
acl: add support for branch-based access control
Elifarley Callado Coelho Cruz -
r11092:2dd91779 default
parent child Browse files
Show More
@@ -1,160 +1,224 b''
1 1 # acl.py - changeset access control for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''hooks for controlling repository access
9 9
10 This hook makes it possible to allow or deny write access to portions
11 of a repository when receiving incoming changesets via pretxnchangegroup and
12 pretxncommit.
10 This hook makes it possible to allow or deny write access to given branches and
11 paths of a repository when receiving incoming changesets via pretxnchangegroup
12 and pretxncommit.
13 13
14 14 The authorization is matched based on the local user name on the
15 15 system where the hook runs, and not the committer of the original
16 16 changeset (since the latter is merely informative).
17 17
18 18 The acl hook is best used along with a restricted shell like hgsh,
19 19 preventing authenticating users from doing anything other than
20 20 pushing or pulling. The hook is not safe to use if users have
21 21 interactive shell access, as they can then disable the hook.
22 22 Nor is it safe if remote users share an account, because then there
23 23 is no way to distinguish them.
24 24
25 The deny list is checked before the allow list.
25 The order in which access checks are performed is:
26 1) Deny list for branches (section [acl.deny.branches])
27 2) Allow list for branches (section [acl.allow.branches])
28 3) Deny list for paths (section [acl.deny])
29 4) Allow list for paths (section [acl.allow])
26 30
27 The allow and deny sections take key-value pairs, having a subtree pattern
28 as key (with a glob syntax by default). The corresponding value can be either:
31 The allow and deny sections take key-value pairs.
32
33 --- Branch-based Access Control ---
34
35 Use the [acl.deny.branches] and [acl.allow.branches] sections to have
36 branch-based access control.
29 37
30 1) an asterisk, to match everyone;
31 2) a comma-separated list containing users and groups.
38 Keys in these sections can be either:
39 1) a branch name
40 2) an asterisk, to match any branch;
41
42 The corresponding values can be either:
43 1) a comma-separated list containing users and groups.
44 2) an asterisk, to match anyone;
32 45
33 Group names must be prefixed with an ``@`` symbol.
46 --- Path-based Access Control ---
47
48 Use the [acl.deny] and [acl.allow] sections to have path-based access control.
49 Keys in these sections accept a subtree pattern (with a glob syntax by default).
50 The corresponding values follow the same syntax as the other sections above.
51
52 --- Groups ---
53
54 Group names must be prefixed with an @ symbol.
34 55 Specifying a group name has the same effect as specifying all the users in
35 56 that group.
36
37 To use this hook, configure the acl extension in your hgrc like this::
57 The set of users for a group is taken from "grp.getgrnam"
58 (see http://docs.python.org/library/grp.html#grp.getgrnam).
38 59
39 [extensions]
40 acl =
60 --- Example Configuration ---
41 61
42 62 [hooks]
43 63
44 # Use this if you want to check access restrictions at commit time.
64 # Use this if you want to check access restrictions at commit time
45 65 pretxncommit.acl = python:hgext.acl.hook
46 66
47 67 # Use this if you want to check access restrictions for pull, push, bundle
48 68 # and serve.
49 69 pretxnchangegroup.acl = python:hgext.acl.hook
50 70
51 71 [acl]
52 # Check whether the source of incoming changes is in this list where
53 # "serve" == ssh or http, and "push", "pull" and "bundle" are the
54 # corresponding hg commands.
72 # Check whether the source of incoming changes is in this list
73 # ("serve" == ssh or http, "push", "pull", "bundle")
55 74 sources = serve
56 75
76 [acl.deny.branches]
77
78 # Everyone is denied to the frozen branch:
79 frozen-branch = *
80
81 # A bad user is denied on all branches:
82 * = bad-user
83
84 [acl.allow.branches]
85
86 # A few users are allowed on branch-a:
87 branch-a = user-1, user-2, user-3
88
89 # Only one user is allowed on branch-b:
90 branch-b = user-1
91
92 # The super user is allowed on any branch:
93 * = super-user
94
95 # Everyone is allowed on branch-for-tests:
96 branch-for-tests = *
97
57 98 [acl.deny]
58 # This list is checked first. If a match is found, 'acl.allow' will not be
59 # checked. All users are granted access if acl.deny is not present.
60 # Format for both lists: glob pattern = user, ..., @group, ...
99 # If a match is found, "acl.allow" will not be checked.
100 # if acl.deny is not present, no users denied by default
101 # empty acl.deny = all users allowed
102 # Format for both lists: glob pattern = user4, user5, @group1
61 103
62 104 # To match everyone, use an asterisk for the user:
63 105 # my/glob/pattern = *
64 106
65 107 # user6 will not have write access to any file:
66 108 ** = user6
67 109
68 110 # Group "hg-denied" will not have write access to any file:
69 111 ** = @hg-denied
70 112
71 113 # Nobody will be able to change "DONT-TOUCH-THIS.txt", despite everyone being
72 114 # able to change all other files. See below.
73 115 src/main/resources/DONT-TOUCH-THIS.txt = *
74 116
75 117 [acl.allow]
76 118 # if acl.allow not present, all users allowed by default
77 119 # empty acl.allow = no users allowed
78 120
79 121 # User "doc_writer" has write access to any file under the "docs" folder:
80 122 docs/** = doc_writer
81 123
82 124 # User "jack" and group "designers" have write access to any file under the
83 125 # "images" folder:
84 126 images/** = jack, @designers
85 127
86 128 # Everyone (except for "user6" - see "acl.deny" above) will have write access
87 # to any file under the "resources" folder (except for 1 file. See "acl.deny"):
129 to any file under the "resources" folder (except for 1 file. See "acl.deny"):
88 130 src/main/resources/** = *
89 131
90 132 .hgtags = release_engineer
91 133
92 134 '''
93 135
94 136 from mercurial.i18n import _
95 137 from mercurial import util, match
96 138 import getpass, urllib, grp
97 139
98 140 def _getusers(group):
99 141 return grp.getgrnam(group).gr_mem
100 142
101 143 def _usermatch(user, usersorgroups):
102 144
103 145 if usersorgroups == '*':
104 146 return True
105 147
106 148 for ug in usersorgroups.replace(',', ' ').split():
107 149 if user == ug or ug.find('@') == 0 and user in _getusers(ug[1:]):
108 150 return True
109 151
110 152 return False
111 153
112 154 def buildmatch(ui, repo, user, key):
113 155 '''return tuple of (match function, list enabled).'''
114 156 if not ui.has_section(key):
115 157 ui.debug('acl: %s not enabled\n' % key)
116 158 return None
117 159
118 160 pats = [pat for pat, users in ui.configitems(key)
119 161 if _usermatch(user, users)]
120 162 ui.debug('acl: %s enabled, %d entries for user %s\n' %
121 163 (key, len(pats), user))
164
165 if not repo:
166 if pats:
167 return lambda b: '*' in pats or b in pats
168 return lambda b: False
169
122 170 if pats:
123 171 return match.match(repo.root, '', pats)
124 172 return match.exact(repo.root, '', [])
125 173
126 174
127 175 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
128 176 if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
129 177 raise util.Abort(_('config error - hook type "%s" cannot stop '
130 178 'incoming changesets nor commits') % hooktype)
131 179 if (hooktype == 'pretxnchangegroup' and
132 180 source not in ui.config('acl', 'sources', 'serve').split()):
133 181 ui.debug('acl: changes have source "%s" - skipping\n' % source)
134 182 return
135 183
136 184 user = None
137 185 if source == 'serve' and 'url' in kwargs:
138 186 url = kwargs['url'].split(':')
139 187 if url[0] == 'remote' and url[1].startswith('http'):
140 188 user = urllib.unquote(url[3])
141 189
142 190 if user is None:
143 191 user = getpass.getuser()
144 192
145 193 cfg = ui.config('acl', 'config')
146 194 if cfg:
147 ui.readconfig(cfg, sections = ['acl.allow', 'acl.deny'])
195 ui.readconfig(cfg, sections = ['acl.allow.branches',
196 'acl.deny.branches', 'acl.allow', 'acl.deny'])
197
198 allowbranches = buildmatch(ui, None, user, 'acl.allow.branches')
199 denybranches = buildmatch(ui, None, user, 'acl.deny.branches')
148 200 allow = buildmatch(ui, repo, user, 'acl.allow')
149 201 deny = buildmatch(ui, repo, user, 'acl.deny')
150 202
151 203 for rev in xrange(repo[node], len(repo)):
152 204 ctx = repo[rev]
205 branch = ctx.branch()
206 if denybranches and denybranches(branch):
207 raise util.Abort(_('acl: user "%s" denied on branch "%s"'
208 ' (changeset "%s")')
209 % (user, branch, ctx))
210 if allowbranches and not allowbranches(branch):
211 raise util.Abort(_('acl: user "%s" not allowed on branch "%s"'
212 ' (changeset "%s")')
213 % (user, branch, ctx))
214 ui.debug('acl: branch access granted: "%s" on branch "%s"\n'
215 % (ctx, branch))
216
153 217 for f in ctx.files():
154 218 if deny and deny(f):
155 219 ui.debug('acl: user %s denied on %s\n' % (user, f))
156 220 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
157 221 if allow and not allow(f):
158 222 ui.debug('acl: user %s not allowed on %s\n' % (user, f))
159 223 raise util.Abort(_('acl: access denied for changeset %s') % ctx)
160 224 ui.debug('acl: allowing changeset %s\n' % ctx)
General Comments 0
You need to be logged in to leave comments. Login now