Patchwork [06,of,14,"] hgweb: add support to explicitly access hidden changesets

login
register
mail settings
Submitter Pierre-Yves David
Date April 13, 2019, 11:40 p.m.
Message ID <8abe74c8c131e862ec35.1555198836@nodosa.octopoid.net>
Download mbox | patch
Permalink /patch/39607/
State Accepted
Headers show

Comments

Pierre-Yves David - April 13, 2019, 11:40 p.m.
# HG changeset patch
# User Pierre-Yves David <pierre-yves.david@octobus.net>
# Date 1555111076 -7200
#      Sat Apr 13 01:17:56 2019 +0200
# Node ID 8abe74c8c131e862ec35cc9ab35c7f853033d739
# Parent  c69f95648b63e2ff430ed3650bface987908d5fb
# EXP-Topic hgweb-obsolete
# Available At https://bitbucket.org/octobus/mercurial-devel/
#              hg pull https://bitbucket.org/octobus/mercurial-devel/ -r 8abe74c8c131
hgweb: add support to explicitly access hidden changesets

This changeset adds a "global" `access-hidden` argument to hgweb. This argument
lift the "hidden" filtering. This means the request has access to hidden (eg:
obsolete) changesets. Secret changesets remains filtered.

This feature has multiple applications. The first main use case is to allow the
hgweb interface to display more obsolescence related data, such as the Anton
Shestakov work to add `obslog` support to hgweb.

The second foreseen usecase is support for a `--remote-hidden` argument to `hg
pull` and `hg clone`. This flag will make it possible to retrieve hidden
(typically obsolete) changeset under some conditions. This is useful when
digging up obsolescence history or when doing full mirroring. More on this
feature coming in later changesets.

To avoid exposing information by mistake, access to this feature is currently
controlled with the `experimental.server.allow-hidden-access` config option. The
option works the same way as `web.allow-push`. The current default is to not
allow any hidden access. However we might change it before the feature stop
being experimental.

Patch

diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -434,6 +434,9 @@  coreconfigitem('email', 'to',
 coreconfigitem('experimental', 'archivemetatemplate',
     default=dynamicdefault,
 )
+coreconfigitem('experimental', 'server.allow-hidden-access',
+    default=list,
+)
 coreconfigitem('experimental', 'auto-publish',
     default='publish',
 )
diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py
+++ b/mercurial/hgweb/common.py
@@ -14,6 +14,8 @@  import mimetypes
 import os
 import stat
 
+from ..i18n import _
+
 from .. import (
     encoding,
     pycompat,
@@ -44,6 +46,26 @@  def ismember(ui, username, userlist):
     """
     return userlist == ['*'] or username in userlist
 
+def hashiddenaccess(repo, req):
+    if bool(req.qsparams.get('access-hidden')):
+        # Disable this by default for now. Main risk is to get critical
+        # information exposed through this. This is expecially risky if
+        # someone decided to make a changeset secret for good reason, but
+        # its predecessors are still draft.
+        #
+        # The feature is currently experimental, so we can still decide to
+        # change the default.
+        ui = repo.ui
+        allow = ui.configlist('experimental', 'server.allow-hidden-access')
+        user = req.remoteuser
+        if (allow and ismember(ui, user, allow)):
+            return True
+        else:
+            msg = _('ignoring request to access hidden changeset by '
+                    'unauthorized user: %s\n') % user
+            ui.warn(msg)
+    return False
+
 def checkauthz(hgweb, req, op):
     '''Check permission for operation based on request data (including
     authentication info). Return if op allowed, else raise an ErrorResponse
diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py
+++ b/mercurial/hgweb/hgweb_mod.py
@@ -39,6 +39,7 @@  from .. import (
 )
 
 from . import (
+    common,
     request as requestmod,
     webcommands,
     webutil,
@@ -91,6 +92,13 @@  class requestcontext(object):
         self.req = req
         self.res = res
 
+        # Only works if the filter actually support being upgraded to show
+        # visible changesets
+        current_filter = repo.filtername
+        if (common.hashiddenaccess(repo, req) and current_filter is not None
+                and current_filter + '.hidden' in repoview.filtertable):
+            self.repo = self.repo.filtered(repo.filtername + '.hidden')
+
         self.maxchanges = self.configint('web', 'maxchanges')
         self.stripecount = self.configint('web', 'stripes')
         self.maxshortchanges = self.configint('web', 'maxshortchanges')
diff --git a/tests/test-remote-hidden.t b/tests/test-remote-hidden.t
--- a/tests/test-remote-hidden.t
+++ b/tests/test-remote-hidden.t
@@ -110,3 +110,47 @@  changesets in secret and higher phases a
   revision:    0
 
   $ killdaemons.py
+
+Test accessing hidden changeset through hgweb
+---------------------------------------------
+
+  $ hg -R repo-with-hidden serve -p $HGPORT -d --pid-file hg.pid --config "experimental.server.allow-hidden-access=*" -E error.log --accesslog access.log
+  $ cat hg.pid >> $DAEMON_PIDS
+
+Hidden changeset are hidden by default:
+
+  $ get-with-headers.py localhost:$HGPORT 'log?style=raw' | grep revision:
+  revision:    2
+  revision:    0
+
+Hidden changeset are visible when requested:
+
+  $ get-with-headers.py localhost:$HGPORT 'log?style=raw&access-hidden=1' | grep revision:
+  revision:    3
+  revision:    2
+  revision:    1
+  revision:    0
+
+Same check on a server that do not allow hidden access:
+```````````````````````````````````````````````````````
+
+  $ hg -R repo-with-hidden serve -p $HGPORT1 -d --pid-file hg2.pid --config "experimental.server.allow-hidden-access=" -E error.log --accesslog access.log
+  $ cat hg2.pid >> $DAEMON_PIDS
+
+Hidden changeset are hidden by default:
+
+  $ get-with-headers.py localhost:$HGPORT1 'log?style=raw' | grep revision:
+  revision:    2
+  revision:    0
+
+Hidden changeset are still hidden despite being the hidden access request:
+
+  $ get-with-headers.py localhost:$HGPORT1 'log?style=raw&access-hidden=1' | grep revision:
+  revision:    2
+  revision:    0
+
+=============
+Final cleanup
+=============
+
+  $ killdaemons.py