hgweb: support for deny_read/allow_read options
reimplementation of a patch provided by Nilton Volpato.
Folded into a single patch by Thomas Arendsen Hein.
--- a/doc/hgrc.5.txt Fri Nov 07 18:42:43 2008 +0100
+++ b/doc/hgrc.5.txt Fri Oct 31 15:28:06 2008 +0100
@@ -676,6 +676,16 @@
must be present in this list (separated by whitespace or ",").
The contents of the allow_push list are examined after the
deny_push list.
+ allow_read;;
+ If the user has not already been denied repository access due to the
+ contents of deny_read, this list determines whether to grant repository
+ access to the user. If this list is not empty, and the user is
+ unauthenticated or not present in the list (separated by whitespace or ","),
+ then access is denied for the user. If the list is empty or not set, then
+ access is permitted to all users by default. Setting allow_read to the
+ special value "*" is equivalent to it not being set (i.e. access is
+ permitted to all users). The contents of the allow_read list are examined
+ after the deny_read list.
allowzip;;
(DEPRECATED) Whether to allow .zip downloading of repo revisions.
Default is false. This feature creates temporary files.
@@ -693,6 +703,18 @@
and any authenticated user name present in this list (separated by
whitespace or ",") is also denied. The contents of the deny_push
list are examined before the allow_push list.
+ deny_read;;
+ Whether to deny reading/viewing of the repository. If this list is not
+ empty, unauthenticated users are all denied, and any authenticated user name
+ present in this list (separated by whitespace or ",") is also denied access
+ to the repository. If set to the special value "*", all remote users are
+ denied access (rarely needed ;). If deny_read is empty or not set, the
+ determination of repository access depends on the presence and content of
+ the allow_read list (see description). If both deny_read and allow_read are
+ empty or not set, then access is permitted to all users by default. If the
+ repository is being served via hgwebdir, denied users will not be able to
+ see it in the list of repositories. The contents of the deny_read list have
+ priority over (are examined before) the contents of the allow_read list.
description;;
Textual description of the repository's purpose or contents.
Default is "unknown".
--- a/mercurial/hgweb/hgweb_mod.py Fri Nov 07 18:42:43 2008 +0100
+++ b/mercurial/hgweb/hgweb_mod.py Fri Oct 31 15:28:06 2008 +0100
@@ -161,11 +161,13 @@
# process the web interface request
try:
-
tmpl = self.templater(req)
ctype = tmpl('mimetype', encoding=self.encoding)
ctype = templater.stringify(ctype)
+ # check allow_read / deny_read config options
+ self.check_perm(req, None)
+
if cmd == '':
req.form['cmd'] = [tmpl.cache['default']]
cmd = req.form['cmd'][0]
@@ -278,11 +280,24 @@
def check_perm(self, req, op):
'''Check permission for operation based on request data (including
- authentication info. Return true if op allowed, else false.'''
+ authentication info). Return if op allowed, else raise an ErrorResponse
+ exception.'''
+
+ user = req.env.get('REMOTE_USER')
+
+ deny_read = self.configlist('web', 'deny_read')
+ if deny_read and (not user or deny_read == ['*'] or user in deny_read):
+ raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
+
+ allow_read = self.configlist('web', 'allow_read')
+ result = (not allow_read) or (allow_read == ['*']) or (user in allow_read)
+ if not result:
+ raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
if op == 'pull' and not self.allowpull:
raise ErrorResponse(HTTP_OK, '')
- elif op == 'pull':
+ # op is None when checking allow/deny_read permissions for a web-browser request
+ elif op == 'pull' or op is None:
return
# enforce that you can only push using POST requests
@@ -296,8 +311,6 @@
if self.configbool('web', 'push_ssl', True) and scheme != 'https':
raise ErrorResponse(HTTP_OK, 'ssl required')
- user = req.env.get('REMOTE_USER')
-
deny = self.configlist('web', 'deny_push')
if deny and (not user or deny == ['*'] or user in deny):
raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
--- a/mercurial/hgweb/hgwebdir_mod.py Fri Nov 07 18:42:43 2008 +0100
+++ b/mercurial/hgweb/hgwebdir_mod.py Fri Oct 31 15:28:06 2008 +0100
@@ -72,6 +72,28 @@
req = wsgirequest(env, respond)
return self.run_wsgi(req)
+ def read_allowed(self, ui, req):
+ """Check allow_read and deny_read config options of a repo's ui object
+ to determine user permissions. By default, with neither option set (or
+ both empty), allow all users to read the repo. There are two ways a
+ user can be denied read access: (1) deny_read is not empty, and the
+ user is unauthenticated or deny_read contains user (or *), and (2)
+ allow_read is not empty and the user is not in allow_read. Return True
+ if user is allowed to read the repo, else return False."""
+
+ user = req.env.get('REMOTE_USER')
+
+ deny_read = ui.configlist('web', 'deny_read', default=None, untrusted=True)
+ if deny_read and (not user or deny_read == ['*'] or user in deny_read):
+ return False
+
+ allow_read = ui.configlist('web', 'allow_read', default=None, untrusted=True)
+ # by default, allow reading if no allow_read option has been set
+ if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
+ return True
+
+ return False
+
def run_wsgi(self, req):
try:
@@ -175,6 +197,9 @@
if u.configbool("web", "hidden", untrusted=True):
continue
+ if not self.read_allowed(u, req):
+ continue
+
parts = [name]
if 'PATH_INFO' in req.env:
parts.insert(0, req.env['PATH_INFO'].rstrip('/'))