##// END OF EJS Templates
py3: pass ctx.rev() instead of ctx in range()
Pulkit Goyal -
r36399:2827fa74 default
parent child Browse files
Show More
@@ -1,380 +1,380
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 10 This hook makes it possible to allow or deny write access to given
11 11 branches and paths of a repository when receiving incoming changesets
12 12 via pretxnchangegroup 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 pushing
20 20 or pulling. The hook is not safe to use if users have interactive
21 21 shell access, as they can then disable the hook. Nor is it safe if
22 22 remote users share an account, because then there is no way to
23 23 distinguish them.
24 24
25 25 The order in which access checks are performed is:
26 26
27 27 1) Deny list for branches (section ``acl.deny.branches``)
28 28 2) Allow list for branches (section ``acl.allow.branches``)
29 29 3) Deny list for paths (section ``acl.deny``)
30 30 4) Allow list for paths (section ``acl.allow``)
31 31
32 32 The allow and deny sections take key-value pairs.
33 33
34 34 Branch-based Access Control
35 35 ---------------------------
36 36
37 37 Use the ``acl.deny.branches`` and ``acl.allow.branches`` sections to
38 38 have branch-based access control. Keys in these sections can be
39 39 either:
40 40
41 41 - a branch name, or
42 42 - an asterisk, to match any branch;
43 43
44 44 The corresponding values can be either:
45 45
46 46 - a comma-separated list containing users and groups, or
47 47 - an asterisk, to match anyone;
48 48
49 49 You can add the "!" prefix to a user or group name to invert the sense
50 50 of the match.
51 51
52 52 Path-based Access Control
53 53 -------------------------
54 54
55 55 Use the ``acl.deny`` and ``acl.allow`` sections to have path-based
56 56 access control. Keys in these sections accept a subtree pattern (with
57 57 a glob syntax by default). The corresponding values follow the same
58 58 syntax as the other sections above.
59 59
60 60 Groups
61 61 ------
62 62
63 63 Group names must be prefixed with an ``@`` symbol. Specifying a group
64 64 name has the same effect as specifying all the users in that group.
65 65
66 66 You can define group members in the ``acl.groups`` section.
67 67 If a group name is not defined there, and Mercurial is running under
68 68 a Unix-like system, the list of users will be taken from the OS.
69 69 Otherwise, an exception will be raised.
70 70
71 71 Example Configuration
72 72 ---------------------
73 73
74 74 ::
75 75
76 76 [hooks]
77 77
78 78 # Use this if you want to check access restrictions at commit time
79 79 pretxncommit.acl = python:hgext.acl.hook
80 80
81 81 # Use this if you want to check access restrictions for pull, push,
82 82 # bundle and serve.
83 83 pretxnchangegroup.acl = python:hgext.acl.hook
84 84
85 85 [acl]
86 86 # Allow or deny access for incoming changes only if their source is
87 87 # listed here, let them pass otherwise. Source is "serve" for all
88 88 # remote access (http or ssh), "push", "pull" or "bundle" when the
89 89 # related commands are run locally.
90 90 # Default: serve
91 91 sources = serve
92 92
93 93 [acl.deny.branches]
94 94
95 95 # Everyone is denied to the frozen branch:
96 96 frozen-branch = *
97 97
98 98 # A bad user is denied on all branches:
99 99 * = bad-user
100 100
101 101 [acl.allow.branches]
102 102
103 103 # A few users are allowed on branch-a:
104 104 branch-a = user-1, user-2, user-3
105 105
106 106 # Only one user is allowed on branch-b:
107 107 branch-b = user-1
108 108
109 109 # The super user is allowed on any branch:
110 110 * = super-user
111 111
112 112 # Everyone is allowed on branch-for-tests:
113 113 branch-for-tests = *
114 114
115 115 [acl.deny]
116 116 # This list is checked first. If a match is found, acl.allow is not
117 117 # checked. All users are granted access if acl.deny is not present.
118 118 # Format for both lists: glob pattern = user, ..., @group, ...
119 119
120 120 # To match everyone, use an asterisk for the user:
121 121 # my/glob/pattern = *
122 122
123 123 # user6 will not have write access to any file:
124 124 ** = user6
125 125
126 126 # Group "hg-denied" will not have write access to any file:
127 127 ** = @hg-denied
128 128
129 129 # Nobody will be able to change "DONT-TOUCH-THIS.txt", despite
130 130 # everyone being able to change all other files. See below.
131 131 src/main/resources/DONT-TOUCH-THIS.txt = *
132 132
133 133 [acl.allow]
134 134 # if acl.allow is not present, all users are allowed by default
135 135 # empty acl.allow = no users allowed
136 136
137 137 # User "doc_writer" has write access to any file under the "docs"
138 138 # folder:
139 139 docs/** = doc_writer
140 140
141 141 # User "jack" and group "designers" have write access to any file
142 142 # under the "images" folder:
143 143 images/** = jack, @designers
144 144
145 145 # Everyone (except for "user6" and "@hg-denied" - see acl.deny above)
146 146 # will have write access to any file under the "resources" folder
147 147 # (except for 1 file. See acl.deny):
148 148 src/main/resources/** = *
149 149
150 150 .hgtags = release_engineer
151 151
152 152 Examples using the "!" prefix
153 153 .............................
154 154
155 155 Suppose there's a branch that only a given user (or group) should be able to
156 156 push to, and you don't want to restrict access to any other branch that may
157 157 be created.
158 158
159 159 The "!" prefix allows you to prevent anyone except a given user or group to
160 160 push changesets in a given branch or path.
161 161
162 162 In the examples below, we will:
163 163 1) Deny access to branch "ring" to anyone but user "gollum"
164 164 2) Deny access to branch "lake" to anyone but members of the group "hobbit"
165 165 3) Deny access to a file to anyone but user "gollum"
166 166
167 167 ::
168 168
169 169 [acl.allow.branches]
170 170 # Empty
171 171
172 172 [acl.deny.branches]
173 173
174 174 # 1) only 'gollum' can commit to branch 'ring';
175 175 # 'gollum' and anyone else can still commit to any other branch.
176 176 ring = !gollum
177 177
178 178 # 2) only members of the group 'hobbit' can commit to branch 'lake';
179 179 # 'hobbit' members and anyone else can still commit to any other branch.
180 180 lake = !@hobbit
181 181
182 182 # You can also deny access based on file paths:
183 183
184 184 [acl.allow]
185 185 # Empty
186 186
187 187 [acl.deny]
188 188 # 3) only 'gollum' can change the file below;
189 189 # 'gollum' and anyone else can still change any other file.
190 190 /misty/mountains/cave/ring = !gollum
191 191
192 192 '''
193 193
194 194 from __future__ import absolute_import
195 195
196 196 import getpass
197 197
198 198 from mercurial.i18n import _
199 199 from mercurial import (
200 200 error,
201 201 extensions,
202 202 match,
203 203 pycompat,
204 204 registrar,
205 205 util,
206 206 )
207 207
208 208 urlreq = util.urlreq
209 209
210 210 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
211 211 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
212 212 # be specifying the version(s) of Mercurial they are tested with, or
213 213 # leave the attribute unspecified.
214 214 testedwith = 'ships-with-hg-core'
215 215
216 216 configtable = {}
217 217 configitem = registrar.configitem(configtable)
218 218
219 219 # deprecated config: acl.config
220 220 configitem('acl', 'config',
221 221 default=None,
222 222 )
223 223 configitem('acl.groups', '.*',
224 224 default=None,
225 225 generic=True,
226 226 )
227 227 configitem('acl.deny.branches', '.*',
228 228 default=None,
229 229 generic=True,
230 230 )
231 231 configitem('acl.allow.branches', '.*',
232 232 default=None,
233 233 generic=True,
234 234 )
235 235 configitem('acl.deny', '.*',
236 236 default=None,
237 237 generic=True,
238 238 )
239 239 configitem('acl.allow', '.*',
240 240 default=None,
241 241 generic=True,
242 242 )
243 243 configitem('acl', 'sources',
244 244 default=lambda: ['serve'],
245 245 )
246 246
247 247 def _getusers(ui, group):
248 248
249 249 # First, try to use group definition from section [acl.groups]
250 250 hgrcusers = ui.configlist('acl.groups', group)
251 251 if hgrcusers:
252 252 return hgrcusers
253 253
254 254 ui.debug('acl: "%s" not defined in [acl.groups]\n' % group)
255 255 # If no users found in group definition, get users from OS-level group
256 256 try:
257 257 return util.groupmembers(group)
258 258 except KeyError:
259 259 raise error.Abort(_("group '%s' is undefined") % group)
260 260
261 261 def _usermatch(ui, user, usersorgroups):
262 262
263 263 if usersorgroups == '*':
264 264 return True
265 265
266 266 for ug in usersorgroups.replace(',', ' ').split():
267 267
268 268 if ug.startswith('!'):
269 269 # Test for excluded user or group. Format:
270 270 # if ug is a user name: !username
271 271 # if ug is a group name: !@groupname
272 272 ug = ug[1:]
273 273 if not ug.startswith('@') and user != ug \
274 274 or ug.startswith('@') and user not in _getusers(ui, ug[1:]):
275 275 return True
276 276
277 277 # Test for user or group. Format:
278 278 # if ug is a user name: username
279 279 # if ug is a group name: @groupname
280 280 elif user == ug \
281 281 or ug.startswith('@') and user in _getusers(ui, ug[1:]):
282 282 return True
283 283
284 284 return False
285 285
286 286 def buildmatch(ui, repo, user, key):
287 287 '''return tuple of (match function, list enabled).'''
288 288 if not ui.has_section(key):
289 289 ui.debug('acl: %s not enabled\n' % key)
290 290 return None
291 291
292 292 pats = [pat for pat, users in ui.configitems(key)
293 293 if _usermatch(ui, user, users)]
294 294 ui.debug('acl: %s enabled, %d entries for user %s\n' %
295 295 (key, len(pats), user))
296 296
297 297 # Branch-based ACL
298 298 if not repo:
299 299 if pats:
300 300 # If there's an asterisk (meaning "any branch"), always return True;
301 301 # Otherwise, test if b is in pats
302 302 if '*' in pats:
303 303 return util.always
304 304 return lambda b: b in pats
305 305 return util.never
306 306
307 307 # Path-based ACL
308 308 if pats:
309 309 return match.match(repo.root, '', pats)
310 310 return util.never
311 311
312 312 def ensureenabled(ui):
313 313 """make sure the extension is enabled when used as hook
314 314
315 315 When acl is used through hooks, the extension is never formally loaded and
316 316 enabled. This has some side effect, for example the config declaration is
317 317 never loaded. This function ensure the extension is enabled when running
318 318 hooks.
319 319 """
320 320 if 'acl' in ui._knownconfig:
321 321 return
322 322 ui.setconfig('extensions', 'acl', '', source='internal')
323 323 extensions.loadall(ui, ['acl'])
324 324
325 325 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
326 326
327 327 ensureenabled(ui)
328 328
329 329 if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
330 330 raise error.Abort(_('config error - hook type "%s" cannot stop '
331 331 'incoming changesets nor commits') % hooktype)
332 332 if (hooktype == 'pretxnchangegroup' and
333 333 source not in ui.configlist('acl', 'sources')):
334 334 ui.debug('acl: changes have source "%s" - skipping\n' % source)
335 335 return
336 336
337 337 user = None
338 338 if source == 'serve' and r'url' in kwargs:
339 339 url = kwargs[r'url'].split(':')
340 340 if url[0] == 'remote' and url[1].startswith('http'):
341 341 user = urlreq.unquote(url[3])
342 342
343 343 if user is None:
344 344 user = pycompat.bytestr(getpass.getuser())
345 345
346 346 ui.debug('acl: checking access for user "%s"\n' % user)
347 347
348 348 # deprecated config: acl.config
349 349 cfg = ui.config('acl', 'config')
350 350 if cfg:
351 351 ui.readconfig(cfg, sections=['acl.groups', 'acl.allow.branches',
352 352 'acl.deny.branches', 'acl.allow', 'acl.deny'])
353 353
354 354 allowbranches = buildmatch(ui, None, user, 'acl.allow.branches')
355 355 denybranches = buildmatch(ui, None, user, 'acl.deny.branches')
356 356 allow = buildmatch(ui, repo, user, 'acl.allow')
357 357 deny = buildmatch(ui, repo, user, 'acl.deny')
358 358
359 for rev in xrange(repo[node], len(repo)):
359 for rev in xrange(repo[node].rev(), len(repo)):
360 360 ctx = repo[rev]
361 361 branch = ctx.branch()
362 362 if denybranches and denybranches(branch):
363 363 raise error.Abort(_('acl: user "%s" denied on branch "%s"'
364 364 ' (changeset "%s")')
365 365 % (user, branch, ctx))
366 366 if allowbranches and not allowbranches(branch):
367 367 raise error.Abort(_('acl: user "%s" not allowed on branch "%s"'
368 368 ' (changeset "%s")')
369 369 % (user, branch, ctx))
370 370 ui.debug('acl: branch access granted: "%s" on branch "%s"\n'
371 371 % (ctx, branch))
372 372
373 373 for f in ctx.files():
374 374 if deny and deny(f):
375 375 raise error.Abort(_('acl: user "%s" denied on "%s"'
376 376 ' (changeset "%s")') % (user, f, ctx))
377 377 if allow and not allow(f):
378 378 raise error.Abort(_('acl: user "%s" not allowed on "%s"'
379 379 ' (changeset "%s")') % (user, f, ctx))
380 380 ui.debug('acl: path access granted: "%s"\n' % ctx)
General Comments 0
You need to be logged in to leave comments. Login now