hgext/acl.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43117 8ff1ecfadcd1
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
   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)