From patchwork Fri Mar 4 00:08:46 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: acl: add support for bookmarks [RFC] From: timeless@mozdev.org X-Patchwork-Id: 13587 Message-Id: To: mercurial-devel@mercurial-scm.org Date: Thu, 03 Mar 2016 18:08:46 -0600 # HG changeset patch # User timeless # Date 1457047766 0 # Thu Mar 03 23:29:26 2016 +0000 # Node ID ffccdee9c9c6e3e86991043c0d56d58f26e746f6 # Parent e00e57d836535aadcb13337613d2f891492d8e04 acl: add support for bookmarks [RFC] Someone asked about whether acl could support bookmarks. Before this, it couldn't. This code isn't particularly well tested, but it seems to work. It's also a template for using acl to control `phases`. diff --git a/hgext/acl.py b/hgext/acl.py --- a/hgext/acl.py +++ b/hgext/acl.py @@ -57,6 +57,27 @@ a glob syntax by default). The corresponding values follow the same syntax as the other sections above. +Bookmark-based Access Control +----------------------------- +Use the ``acl.deny.bookmarks`` and ``acl.allow.bookmarks`` sections to +have bookmark-based access control. Keys in these sections can be +either: + +- a bookmark name, or +- an asterisk, to match any bookmark; + +The corresponding values can be either: + +- a comma-separated list containing users and groups, or +- an asterisk, to match anyone; + +You can add the "!" prefix to a user or group name to invert the sense +of the match. + +Note: for bundle2 requests, a rejection will reject the entire push, +for bundle1 requests, the commit transactions will already be accepted, +and only the bookmark moveent will be rejected. + Groups ------ @@ -275,9 +296,9 @@ return util.never def hook(ui, repo, hooktype, node=None, source=None, **kwargs): - if hooktype not in ['pretxnchangegroup', 'pretxncommit']: + if hooktype not in ['pretxnchangegroup', 'pretxncommit', 'prepushkey']: raise error.Abort(_('config error - hook type "%s" cannot stop ' - 'incoming changesets nor commits') % hooktype) + 'incoming changesets, commits, nor bookmarks') % hooktype) if (hooktype == 'pretxnchangegroup' and source not in ui.config('acl', 'sources', 'serve').split()): ui.debug('acl: changes have source "%s" - skipping\n' % source) @@ -294,6 +315,30 @@ ui.debug('acl: checking access for user "%s"\n' % user) + if hooktype == 'prepushkey': + _pkhook(ui, repo, hooktype, node, source, user, **kwargs) + else: + _txnhook(ui, repo, hooktype, node, source, user, **kwargs) + +def _pkhook(ui, repo, hooktype, node, source, user, **kwargs): + if kwargs['namespace'] == 'bookmarks': + bookmark = kwargs['key'] + ctx = kwargs['new'] + allowbookmarks = buildmatch(ui, None, user, 'acl.allow.bookmarks') + denybookmarks = buildmatch(ui, None, user, 'acl.deny.bookmarks') + + if denybookmarks and denybookmarks(bookmark): + raise error.Abort(_('acl: user "%s" denied on bookmark "%s"' + ' (changeset "%s")') + % (user, bookmark, ctx)) + if allowbookmarks and not allowbookmarks(bookmark): + raise error.Abort(_('acl: user "%s" not allowed on bookmark "%s"' + ' (changeset "%s")') + % (user, bookmark, ctx)) + ui.debug('acl: bookmark access granted: "%s" on bookmark "%s"\n' + % (ctx, bookmark)) + +def _txnhook(ui, repo, hooktype, node, source, user, **kwargs): # deprecated config: acl.config cfg = ui.config('acl', 'config') if cfg: diff --git a/tests/test-acl.t b/tests/test-acl.t --- a/tests/test-acl.t +++ b/tests/test-acl.t @@ -15,7 +15,7 @@ > # LOGNAME=$user hg --cws a --debug push ../b > # fails with "This variable is read only." > # Use env to work around this. - > env LOGNAME=$user hg --cwd a --debug push ../b + > env LOGNAME=$user hg --cwd a --debug push ../b $* > hg --cwd b rollback > hg --cwd b --quiet tip > echo @@ -37,6 +37,7 @@ > cat > $config < [hooks] > pretxnchangegroup.acl = python:hgext.acl.hook + > prepushkey.acl = python:hgext.acl.hook > [acl] > sources = push > [extensions] @@ -138,6 +139,7 @@ $ echo '[hooks]' >> $config $ echo 'pretxnchangegroup.acl = python:hgext.acl.hook' >> $config + $ echo 'prepushkey.acl = python:hgext.acl.hook' >> $config Extension disabled for lack of acl.sources @@ -146,6 +148,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook """ pushing to ../b query 1; heads @@ -186,6 +189,8 @@ acl: changes have source "push" - skipping bundle2-input-part: total payload size 1606 bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "fred" pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" bundle2-input-bundle: 3 parts total updating the branch cache @@ -210,6 +215,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push """ @@ -262,6 +268,8 @@ acl: path access granted: "911600dab2ae" bundle2-input-part: total payload size 1606 bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "fred" pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" bundle2-input-bundle: 3 parts total updating the branch cache @@ -285,6 +293,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [acl.allow] @@ -349,6 +358,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [acl.allow] @@ -418,6 +428,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [acl.allow] @@ -484,6 +495,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [acl.allow] @@ -555,6 +567,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [acl.allow] @@ -624,6 +637,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [acl.allow] @@ -684,6 +698,175 @@ 0:6675d58eff77 +fred is not blocked from moving bookmarks + + $ hg -R a book -q moving-bookmark -r 1 + $ hg -R b book -q moving-bookmark -r 0 + $ cp $config normalconfig + $ do_push fred -r 1 + Pushing as user fred + hgrc = """ + [hooks] + pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook + [acl] + sources = push + [acl.allow] + foo/** = fred + [acl.deny] + foo/bar/** = fred + foo/Bar/** = fred + """ + pushing to ../b + query 1; heads + searching for changes + all remote heads known locally + listing keys for "phases" + checking for updated bookmarks + listing keys for "bookmarks" + invalid branchheads cache (served): tip differs + listing keys for "bookmarks" + 1 changesets found + list of changesets: + ef1ea85a6374b77d6da9dcda9541f498f2d17df7 + bundle2-output-bundle: "HG20", 5 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported + adding changesets + add changeset ef1ea85a6374 + adding manifests + adding file changes + adding foo/file.txt revisions + added 1 changesets with 1 changes to 1 files + calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "fred" + acl: acl.allow.branches not enabled + acl: acl.deny.branches not enabled + acl: acl.allow enabled, 1 entries for user fred + acl: acl.deny enabled, 2 entries for user fred + acl: branch access granted: "ef1ea85a6374" on branch "default" + acl: path access granted: "ef1ea85a6374" + invalid branchheads cache (served): tip differs + bundle2-input-part: total payload size 537 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "fred" + pushing key for "phases:ef1ea85a6374b77d6da9dcda9541f498f2d17df7" + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "fred" + acl: acl.allow.bookmarks not enabled + acl: acl.deny.bookmarks not enabled + acl: bookmark access granted: "ef1ea85a6374b77d6da9dcda9541f498f2d17df7" on bookmark "moving-bookmark" + pushing key for "bookmarks:moving-bookmark" + bundle2-input-bundle: 4 parts total + updating the branch cache + bundle2-output-bundle: "HG20", 3 parts total + bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported + bundle2-input-bundle: 2 parts total + updating bookmark moving-bookmark + listing keys for "phases" + repository tip rolled back to revision 0 (undo push) + 0:6675d58eff77 + +fred is not allowed to move bookmarks + + $ echo '[acl.deny.bookmarks]' >> $config + $ echo '* = fred' >> $config + $ do_push fred -r 1 + Pushing as user fred + hgrc = """ + [hooks] + pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook + [acl] + sources = push + [acl.allow] + foo/** = fred + [acl.deny] + foo/bar/** = fred + foo/Bar/** = fred + [acl.deny.bookmarks] + * = fred + """ + pushing to ../b + query 1; heads + searching for changes + all remote heads known locally + listing keys for "phases" + checking for updated bookmarks + listing keys for "bookmarks" + invalid branchheads cache (served): tip differs + listing keys for "bookmarks" + 1 changesets found + list of changesets: + ef1ea85a6374b77d6da9dcda9541f498f2d17df7 + bundle2-output-bundle: "HG20", 5 parts total + bundle2-output-part: "replycaps" 155 bytes payload + bundle2-output-part: "check:heads" streamed payload + bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload + bundle2-input-bundle: with-transaction + bundle2-input-part: "replycaps" supported + bundle2-input-part: total payload size 155 + bundle2-input-part: "check:heads" supported + bundle2-input-part: total payload size 20 + bundle2-input-part: "changegroup" (params: 1 mandatory) supported + adding changesets + add changeset ef1ea85a6374 + adding manifests + adding file changes + adding foo/file.txt revisions + added 1 changesets with 1 changes to 1 files + calling hook pretxnchangegroup.acl: hgext.acl.hook + acl: checking access for user "fred" + acl: acl.allow.branches not enabled + acl: acl.deny.branches not enabled + acl: acl.allow enabled, 1 entries for user fred + acl: acl.deny enabled, 2 entries for user fred + acl: branch access granted: "ef1ea85a6374" on branch "default" + acl: path access granted: "ef1ea85a6374" + invalid branchheads cache (served): tip differs + bundle2-input-part: total payload size 537 + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "fred" + pushing key for "phases:ef1ea85a6374b77d6da9dcda9541f498f2d17df7" + bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "fred" + acl: acl.allow.bookmarks not enabled + acl: acl.deny.bookmarks enabled, 1 entries for user fred + error: prepushkey.acl hook failed: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7") + bundle2-input-bundle: 4 parts total + transaction abort! + rollback completed + abort: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7") + no rollback information available + 0:6675d58eff77 + +cleanup bookmark stuff + + $ hg book -R a -d moving-bookmark + $ hg book -R b -d moving-bookmark + $ cp normalconfig $config + barney is allowed everywhere $ echo '[acl.allow]' >> $config @@ -693,6 +876,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [acl.allow] @@ -711,6 +895,7 @@ checking for updated bookmarks listing keys for "bookmarks" invalid branchheads cache (served): tip differs + invalid branchheads cache (base): tip differs listing keys for "bookmarks" 3 changesets found list of changesets: @@ -752,6 +937,8 @@ acl: path access granted: "911600dab2ae" bundle2-input-part: total payload size 1606 bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "barney" pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" bundle2-input-bundle: 3 parts total updating the branch cache @@ -775,6 +962,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [acl.allow] @@ -851,6 +1039,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [acl.allow] @@ -920,6 +1109,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [acl.allow] @@ -1002,6 +1192,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [acl.allow] @@ -1070,6 +1261,8 @@ acl: path access granted: "911600dab2ae" bundle2-input-part: total payload size 1606 bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "barney" pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" bundle2-input-bundle: 3 parts total updating the branch cache @@ -1101,6 +1294,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions] @@ -1156,6 +1350,8 @@ acl: path access granted: "911600dab2ae" bundle2-input-part: total payload size 1606 bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "fred" pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" bundle2-input-bundle: 3 parts total updating the branch cache @@ -1181,6 +1377,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions] @@ -1259,6 +1456,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions] @@ -1315,6 +1513,8 @@ acl: path access granted: "911600dab2ae" bundle2-input-part: total payload size 1606 bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "fred" pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" bundle2-input-bundle: 3 parts total updating the branch cache @@ -1340,6 +1540,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions] @@ -1459,6 +1660,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions] @@ -1517,8 +1719,12 @@ acl: path access granted: "e8fc755d4d82" bundle2-input-part: total payload size 2101 bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "astro" pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "astro" pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01" bundle2-input-bundle: 4 parts total updating the branch cache @@ -1545,6 +1751,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions] @@ -1620,6 +1827,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions] @@ -1689,6 +1897,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions] @@ -1753,6 +1962,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions] @@ -1813,8 +2023,12 @@ acl: path access granted: "e8fc755d4d82" bundle2-input-part: total payload size 2101 bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "george" pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "george" pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01" bundle2-input-bundle: 4 parts total updating the branch cache @@ -1845,6 +2059,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions] @@ -1906,8 +2121,12 @@ acl: path access granted: "e8fc755d4d82" bundle2-input-part: total payload size 2101 bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "george" pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "george" pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01" bundle2-input-bundle: 4 parts total updating the branch cache @@ -1936,6 +2155,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions] @@ -2007,6 +2227,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions] @@ -2067,8 +2288,12 @@ acl: path access granted: "e8fc755d4d82" bundle2-input-part: total payload size 2101 bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "astro" pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955" bundle2-input-part: "pushkey" (params: 4 mandatory) supported + calling hook prepushkey.acl: hgext.acl.hook + acl: checking access for user "astro" pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01" bundle2-input-bundle: 4 parts total updating the branch cache @@ -2093,6 +2318,7 @@ hgrc = """ [hooks] pretxnchangegroup.acl = python:hgext.acl.hook + prepushkey.acl = python:hgext.acl.hook [acl] sources = push [extensions]