230 |
230 |
231 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
231 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
232 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
232 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
233 # be specifying the version(s) of Mercurial they are tested with, or |
233 # be specifying the version(s) of Mercurial they are tested with, or |
234 # leave the attribute unspecified. |
234 # leave the attribute unspecified. |
235 testedwith = 'ships-with-hg-core' |
235 testedwith = b'ships-with-hg-core' |
236 |
236 |
237 configtable = {} |
237 configtable = {} |
238 configitem = registrar.configitem(configtable) |
238 configitem = registrar.configitem(configtable) |
239 |
239 |
240 # deprecated config: acl.config |
240 # deprecated config: acl.config |
241 configitem( |
241 configitem( |
242 'acl', 'config', default=None, |
242 b'acl', b'config', default=None, |
243 ) |
243 ) |
244 configitem( |
244 configitem( |
245 'acl.groups', '.*', default=None, generic=True, |
245 b'acl.groups', b'.*', default=None, generic=True, |
246 ) |
246 ) |
247 configitem( |
247 configitem( |
248 'acl.deny.branches', '.*', default=None, generic=True, |
248 b'acl.deny.branches', b'.*', default=None, generic=True, |
249 ) |
249 ) |
250 configitem( |
250 configitem( |
251 'acl.allow.branches', '.*', default=None, generic=True, |
251 b'acl.allow.branches', b'.*', default=None, generic=True, |
252 ) |
252 ) |
253 configitem( |
253 configitem( |
254 'acl.deny', '.*', default=None, generic=True, |
254 b'acl.deny', b'.*', default=None, generic=True, |
255 ) |
255 ) |
256 configitem( |
256 configitem( |
257 'acl.allow', '.*', default=None, generic=True, |
257 b'acl.allow', b'.*', default=None, generic=True, |
258 ) |
258 ) |
259 configitem( |
259 configitem( |
260 'acl', 'sources', default=lambda: ['serve'], |
260 b'acl', b'sources', default=lambda: [b'serve'], |
261 ) |
261 ) |
262 |
262 |
263 |
263 |
264 def _getusers(ui, group): |
264 def _getusers(ui, group): |
265 |
265 |
266 # First, try to use group definition from section [acl.groups] |
266 # First, try to use group definition from section [acl.groups] |
267 hgrcusers = ui.configlist('acl.groups', group) |
267 hgrcusers = ui.configlist(b'acl.groups', group) |
268 if hgrcusers: |
268 if hgrcusers: |
269 return hgrcusers |
269 return hgrcusers |
270 |
270 |
271 ui.debug('acl: "%s" not defined in [acl.groups]\n' % group) |
271 ui.debug(b'acl: "%s" not defined in [acl.groups]\n' % group) |
272 # If no users found in group definition, get users from OS-level group |
272 # If no users found in group definition, get users from OS-level group |
273 try: |
273 try: |
274 return util.groupmembers(group) |
274 return util.groupmembers(group) |
275 except KeyError: |
275 except KeyError: |
276 raise error.Abort(_("group '%s' is undefined") % group) |
276 raise error.Abort(_(b"group '%s' is undefined") % group) |
277 |
277 |
278 |
278 |
279 def _usermatch(ui, user, usersorgroups): |
279 def _usermatch(ui, user, usersorgroups): |
280 |
280 |
281 if usersorgroups == '*': |
281 if usersorgroups == b'*': |
282 return True |
282 return True |
283 |
283 |
284 for ug in usersorgroups.replace(',', ' ').split(): |
284 for ug in usersorgroups.replace(b',', b' ').split(): |
285 |
285 |
286 if ug.startswith('!'): |
286 if ug.startswith(b'!'): |
287 # Test for excluded user or group. Format: |
287 # Test for excluded user or group. Format: |
288 # if ug is a user name: !username |
288 # if ug is a user name: !username |
289 # if ug is a group name: !@groupname |
289 # if ug is a group name: !@groupname |
290 ug = ug[1:] |
290 ug = ug[1:] |
291 if ( |
291 if ( |
292 not ug.startswith('@') |
292 not ug.startswith(b'@') |
293 and user != ug |
293 and user != ug |
294 or ug.startswith('@') |
294 or ug.startswith(b'@') |
295 and user not in _getusers(ui, ug[1:]) |
295 and user not in _getusers(ui, ug[1:]) |
296 ): |
296 ): |
297 return True |
297 return True |
298 |
298 |
299 # Test for user or group. Format: |
299 # Test for user or group. Format: |
300 # if ug is a user name: username |
300 # if ug is a user name: username |
301 # if ug is a group name: @groupname |
301 # if ug is a group name: @groupname |
302 elif user == ug or ug.startswith('@') and user in _getusers(ui, ug[1:]): |
302 elif ( |
|
303 user == ug or ug.startswith(b'@') and user in _getusers(ui, ug[1:]) |
|
304 ): |
303 return True |
305 return True |
304 |
306 |
305 return False |
307 return False |
306 |
308 |
307 |
309 |
308 def buildmatch(ui, repo, user, key): |
310 def buildmatch(ui, repo, user, key): |
309 '''return tuple of (match function, list enabled).''' |
311 '''return tuple of (match function, list enabled).''' |
310 if not ui.has_section(key): |
312 if not ui.has_section(key): |
311 ui.debug('acl: %s not enabled\n' % key) |
313 ui.debug(b'acl: %s not enabled\n' % key) |
312 return None |
314 return None |
313 |
315 |
314 pats = [ |
316 pats = [ |
315 pat for pat, users in ui.configitems(key) if _usermatch(ui, user, users) |
317 pat for pat, users in ui.configitems(key) if _usermatch(ui, user, users) |
316 ] |
318 ] |
317 ui.debug( |
319 ui.debug( |
318 'acl: %s enabled, %d entries for user %s\n' % (key, len(pats), user) |
320 b'acl: %s enabled, %d entries for user %s\n' % (key, len(pats), user) |
319 ) |
321 ) |
320 |
322 |
321 # Branch-based ACL |
323 # Branch-based ACL |
322 if not repo: |
324 if not repo: |
323 if pats: |
325 if pats: |
324 # If there's an asterisk (meaning "any branch"), always return True; |
326 # If there's an asterisk (meaning "any branch"), always return True; |
325 # Otherwise, test if b is in pats |
327 # Otherwise, test if b is in pats |
326 if '*' in pats: |
328 if b'*' in pats: |
327 return util.always |
329 return util.always |
328 return lambda b: b in pats |
330 return lambda b: b in pats |
329 return util.never |
331 return util.never |
330 |
332 |
331 # Path-based ACL |
333 # Path-based ACL |
332 if pats: |
334 if pats: |
333 return match.match(repo.root, '', pats) |
335 return match.match(repo.root, b'', pats) |
334 return util.never |
336 return util.never |
335 |
337 |
336 |
338 |
337 def ensureenabled(ui): |
339 def ensureenabled(ui): |
338 """make sure the extension is enabled when used as hook |
340 """make sure the extension is enabled when used as hook |
340 When acl is used through hooks, the extension is never formally loaded and |
342 When acl is used through hooks, the extension is never formally loaded and |
341 enabled. This has some side effect, for example the config declaration is |
343 enabled. This has some side effect, for example the config declaration is |
342 never loaded. This function ensure the extension is enabled when running |
344 never loaded. This function ensure the extension is enabled when running |
343 hooks. |
345 hooks. |
344 """ |
346 """ |
345 if 'acl' in ui._knownconfig: |
347 if b'acl' in ui._knownconfig: |
346 return |
348 return |
347 ui.setconfig('extensions', 'acl', '', source='internal') |
349 ui.setconfig(b'extensions', b'acl', b'', source=b'internal') |
348 extensions.loadall(ui, ['acl']) |
350 extensions.loadall(ui, [b'acl']) |
349 |
351 |
350 |
352 |
351 def hook(ui, repo, hooktype, node=None, source=None, **kwargs): |
353 def hook(ui, repo, hooktype, node=None, source=None, **kwargs): |
352 |
354 |
353 ensureenabled(ui) |
355 ensureenabled(ui) |
354 |
356 |
355 if hooktype not in ['pretxnchangegroup', 'pretxncommit', 'prepushkey']: |
357 if hooktype not in [b'pretxnchangegroup', b'pretxncommit', b'prepushkey']: |
356 raise error.Abort( |
358 raise error.Abort( |
357 _( |
359 _( |
358 'config error - hook type "%s" cannot stop ' |
360 b'config error - hook type "%s" cannot stop ' |
359 'incoming changesets, commits, nor bookmarks' |
361 b'incoming changesets, commits, nor bookmarks' |
360 ) |
362 ) |
361 % hooktype |
363 % hooktype |
362 ) |
364 ) |
363 if hooktype == 'pretxnchangegroup' and source not in ui.configlist( |
365 if hooktype == b'pretxnchangegroup' and source not in ui.configlist( |
364 'acl', 'sources' |
366 b'acl', b'sources' |
365 ): |
367 ): |
366 ui.debug('acl: changes have source "%s" - skipping\n' % source) |
368 ui.debug(b'acl: changes have source "%s" - skipping\n' % source) |
367 return |
369 return |
368 |
370 |
369 user = None |
371 user = None |
370 if source == 'serve' and r'url' in kwargs: |
372 if source == b'serve' and r'url' in kwargs: |
371 url = kwargs[r'url'].split(':') |
373 url = kwargs[r'url'].split(b':') |
372 if url[0] == 'remote' and url[1].startswith('http'): |
374 if url[0] == b'remote' and url[1].startswith(b'http'): |
373 user = urlreq.unquote(url[3]) |
375 user = urlreq.unquote(url[3]) |
374 |
376 |
375 if user is None: |
377 if user is None: |
376 user = procutil.getuser() |
378 user = procutil.getuser() |
377 |
379 |
378 ui.debug('acl: checking access for user "%s"\n' % user) |
380 ui.debug(b'acl: checking access for user "%s"\n' % user) |
379 |
381 |
380 if hooktype == 'prepushkey': |
382 if hooktype == b'prepushkey': |
381 _pkhook(ui, repo, hooktype, node, source, user, **kwargs) |
383 _pkhook(ui, repo, hooktype, node, source, user, **kwargs) |
382 else: |
384 else: |
383 _txnhook(ui, repo, hooktype, node, source, user, **kwargs) |
385 _txnhook(ui, repo, hooktype, node, source, user, **kwargs) |
384 |
386 |
385 |
387 |
386 def _pkhook(ui, repo, hooktype, node, source, user, **kwargs): |
388 def _pkhook(ui, repo, hooktype, node, source, user, **kwargs): |
387 if kwargs[r'namespace'] == 'bookmarks': |
389 if kwargs[r'namespace'] == b'bookmarks': |
388 bookmark = kwargs[r'key'] |
390 bookmark = kwargs[r'key'] |
389 ctx = kwargs[r'new'] |
391 ctx = kwargs[r'new'] |
390 allowbookmarks = buildmatch(ui, None, user, 'acl.allow.bookmarks') |
392 allowbookmarks = buildmatch(ui, None, user, b'acl.allow.bookmarks') |
391 denybookmarks = buildmatch(ui, None, user, 'acl.deny.bookmarks') |
393 denybookmarks = buildmatch(ui, None, user, b'acl.deny.bookmarks') |
392 |
394 |
393 if denybookmarks and denybookmarks(bookmark): |
395 if denybookmarks and denybookmarks(bookmark): |
394 raise error.Abort( |
396 raise error.Abort( |
395 _('acl: user "%s" denied on bookmark "%s"' ' (changeset "%s")') |
397 _( |
|
398 b'acl: user "%s" denied on bookmark "%s"' |
|
399 b' (changeset "%s")' |
|
400 ) |
396 % (user, bookmark, ctx) |
401 % (user, bookmark, ctx) |
397 ) |
402 ) |
398 if allowbookmarks and not allowbookmarks(bookmark): |
403 if allowbookmarks and not allowbookmarks(bookmark): |
399 raise error.Abort( |
404 raise error.Abort( |
400 _( |
405 _( |
401 'acl: user "%s" not allowed on bookmark "%s"' |
406 b'acl: user "%s" not allowed on bookmark "%s"' |
402 ' (changeset "%s")' |
407 b' (changeset "%s")' |
403 ) |
408 ) |
404 % (user, bookmark, ctx) |
409 % (user, bookmark, ctx) |
405 ) |
410 ) |
406 ui.debug( |
411 ui.debug( |
407 'acl: bookmark access granted: "%s" on bookmark "%s"\n' |
412 b'acl: bookmark access granted: "%s" on bookmark "%s"\n' |
408 % (ctx, bookmark) |
413 % (ctx, bookmark) |
409 ) |
414 ) |
410 |
415 |
411 |
416 |
412 def _txnhook(ui, repo, hooktype, node, source, user, **kwargs): |
417 def _txnhook(ui, repo, hooktype, node, source, user, **kwargs): |
413 # deprecated config: acl.config |
418 # deprecated config: acl.config |
414 cfg = ui.config('acl', 'config') |
419 cfg = ui.config(b'acl', b'config') |
415 if cfg: |
420 if cfg: |
416 ui.readconfig( |
421 ui.readconfig( |
417 cfg, |
422 cfg, |
418 sections=[ |
423 sections=[ |
419 'acl.groups', |
424 b'acl.groups', |
420 'acl.allow.branches', |
425 b'acl.allow.branches', |
421 'acl.deny.branches', |
426 b'acl.deny.branches', |
422 'acl.allow', |
427 b'acl.allow', |
423 'acl.deny', |
428 b'acl.deny', |
424 ], |
429 ], |
425 ) |
430 ) |
426 |
431 |
427 allowbranches = buildmatch(ui, None, user, 'acl.allow.branches') |
432 allowbranches = buildmatch(ui, None, user, b'acl.allow.branches') |
428 denybranches = buildmatch(ui, None, user, 'acl.deny.branches') |
433 denybranches = buildmatch(ui, None, user, b'acl.deny.branches') |
429 allow = buildmatch(ui, repo, user, 'acl.allow') |
434 allow = buildmatch(ui, repo, user, b'acl.allow') |
430 deny = buildmatch(ui, repo, user, 'acl.deny') |
435 deny = buildmatch(ui, repo, user, b'acl.deny') |
431 |
436 |
432 for rev in pycompat.xrange(repo[node].rev(), len(repo)): |
437 for rev in pycompat.xrange(repo[node].rev(), len(repo)): |
433 ctx = repo[rev] |
438 ctx = repo[rev] |
434 branch = ctx.branch() |
439 branch = ctx.branch() |
435 if denybranches and denybranches(branch): |
440 if denybranches and denybranches(branch): |
436 raise error.Abort( |
441 raise error.Abort( |
437 _('acl: user "%s" denied on branch "%s"' ' (changeset "%s")') |
442 _(b'acl: user "%s" denied on branch "%s"' b' (changeset "%s")') |
438 % (user, branch, ctx) |
443 % (user, branch, ctx) |
439 ) |
444 ) |
440 if allowbranches and not allowbranches(branch): |
445 if allowbranches and not allowbranches(branch): |
441 raise error.Abort( |
446 raise error.Abort( |
442 _( |
447 _( |
443 'acl: user "%s" not allowed on branch "%s"' |
448 b'acl: user "%s" not allowed on branch "%s"' |
444 ' (changeset "%s")' |
449 b' (changeset "%s")' |
445 ) |
450 ) |
446 % (user, branch, ctx) |
451 % (user, branch, ctx) |
447 ) |
452 ) |
448 ui.debug( |
453 ui.debug( |
449 'acl: branch access granted: "%s" on branch "%s"\n' % (ctx, branch) |
454 b'acl: branch access granted: "%s" on branch "%s"\n' % (ctx, branch) |
450 ) |
455 ) |
451 |
456 |
452 for f in ctx.files(): |
457 for f in ctx.files(): |
453 if deny and deny(f): |
458 if deny and deny(f): |
454 raise error.Abort( |
459 raise error.Abort( |
455 _('acl: user "%s" denied on "%s"' ' (changeset "%s")') |
460 _(b'acl: user "%s" denied on "%s"' b' (changeset "%s")') |
456 % (user, f, ctx) |
461 % (user, f, ctx) |
457 ) |
462 ) |
458 if allow and not allow(f): |
463 if allow and not allow(f): |
459 raise error.Abort( |
464 raise error.Abort( |
460 _('acl: user "%s" not allowed on "%s"' ' (changeset "%s")') |
465 _( |
|
466 b'acl: user "%s" not allowed on "%s"' |
|
467 b' (changeset "%s")' |
|
468 ) |
461 % (user, f, ctx) |
469 % (user, f, ctx) |
462 ) |
470 ) |
463 ui.debug('acl: path access granted: "%s"\n' % ctx) |
471 ui.debug(b'acl: path access granted: "%s"\n' % ctx) |