[PATCH] support for deny_read/allow_read options

Nilton Volpato nilton.volpato at gmail.com
Tue Dec 11 20:56:01 CST 2007


Hi all,

I had implemented some time ago (Oct 20) a feature for allowing
finer-grained access. However the solution was not following much of
the project philosophy. Alexis answered me with some hints, this is a
follow up to that earlier message.

I've implemented a counterpart of deny_push/allow_push options to be
used in the web section of a .hg/hgrc file of a repository: the
deny_read/allow_read.

The patch is below. If a user don't have read permission for a repo,
then it won't show on hgwebdir list and he won't be able to access the
repo dir also.

I've replaced the check_perm method with is_op_allowed function in
common.py and changed when allow_by_default variable matters. I think
it was wrong before, but there was no problem with this because the
old method (check_perm) wasn't called with default=True.

Please, take a look at this patch and if possible merge to the mercurial repo.

-- Nilton


# HG changeset patch
# User Nilton Volpato <nilton.volpato at gmail.com>
# Date 1197426375 7200
# Node ID 4fd078545b10e17a094c6122cbec96f65466d782
# Parent  feac5b0bf9bad2c125ebd5f3e133bcd46ecb8c7c
Added support for deny_read/allow_read options for finer grained access control

diff -r feac5b0bf9ba -r 4fd078545b10 mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py Wed Nov 28 13:58:31 2007 -0800
+++ b/mercurial/hgweb/common.py Wed Dec 12 00:26:15 2007 -0200
@@ -97,3 +97,21 @@ def paritygen(stripecount, offset=0):
             parity = 1 - parity
             count = 0

+def is_op_allowed(req, ui, op, allow_by_default=False):
+    """Return True if the current authenticated user is allowed
+    to execute operation op, otherwise return False.
+    By setting allow by default, the any user will be allowed to
+    execute op, unless explicitly stated otherwise.
+
+    This is currently used with deny_push/allow_push and
+    deny_read/allow_read config options.
+    """
+    user = req.env.get('REMOTE_USER')
+
+    deny = ui.configlist('web', 'deny_' + op, default=None, untrusted=True)
+    if deny and (not user or deny == ['*'] or user in deny):
+        return False
+
+    if allow_by_default: return allow_by_default
+    allow = ui.configlist('web', 'allow_' + op, default=None, untrusted=True)
+    return allow and (allow == ['*'] or user in allow)
diff -r feac5b0bf9ba -r 4fd078545b10 mercurial/hgweb/hgweb_mod.py
--- a/mercurial/hgweb/hgweb_mod.py      Wed Nov 28 13:58:31 2007 -0800
+++ b/mercurial/hgweb/hgweb_mod.py      Wed Dec 12 00:26:15 2007 -0200
@@ -12,7 +12,8 @@ from mercurial.i18n import gettext as _
 from mercurial.i18n import gettext as _
 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
 from mercurial import revlog, templater
-from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
+from common import ErrorResponse, get_mtime, staticfile, style_map, \
+     paritygen, is_op_allowed

 def _up(p):
     if p[0] != "/":
@@ -843,6 +844,12 @@ class hgweb(object):
                                                })

         try:
+            if not is_op_allowed(req, self.repo.ui, 'read',
+                                 allow_by_default=True):
+                req.respond('403 Forbidden',
+                            self.t('error', error='403 Forbidden'))
+                return
+
             if not req.form.has_key('cmd'):
                 req.form['cmd'] = [self.t.cache['default']]

@@ -1071,20 +1078,6 @@ class hgweb(object):
         req.httphdr("application/mercurial-0.1", length=len(resp))
         req.write(resp)

-    def check_perm(self, req, op, default):
-        '''check permission for operation based on user auth.
-        return true if op allowed, else false.
-        default is policy to use if no config given.'''
-
-        user = req.env.get('REMOTE_USER')
-
-        deny = self.configlist('web', 'deny_' + op)
-        if deny and (not user or deny == ['*'] or user in deny):
-            return False
-
-        allow = self.configlist('web', 'allow_' + op)
-        return (allow and (allow == ['*'] or user in allow)) or default
-
     def do_unbundle(self, req):
         def bail(response, headers={}):
             length = int(req.env['CONTENT_LENGTH'])
@@ -1108,9 +1101,9 @@ class hgweb(object):
             proto = 'http'

         # do not allow push unless explicitly allowed
-        if not self.check_perm(req, 'push', False):
+        if not is_op_allowed(req, self.repo.ui, 'push'):
             bail(_('push not authorized\n'),
-                 headers={'status': '401 Unauthorized'})
+                 headers={'status': '403 Forbidden'})
             return

         their_heads = req.form['heads'][0].split(' ')
diff -r feac5b0bf9ba -r 4fd078545b10 mercurial/hgweb/hgwebdir_mod.py
--- a/mercurial/hgweb/hgwebdir_mod.py   Wed Nov 28 13:58:31 2007 -0800
+++ b/mercurial/hgweb/hgwebdir_mod.py   Wed Dec 12 00:26:15 2007 -0200
@@ -9,7 +9,8 @@ import os, mimetools, cStringIO
 import os, mimetools, cStringIO
 from mercurial.i18n import gettext as _
 from mercurial import ui, hg, util, templater
-from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
+from common import ErrorResponse, get_mtime, staticfile, style_map, \
+     paritygen, is_op_allowed
 from hgweb_mod import hgweb

 # This is a stopgap
@@ -154,6 +155,8 @@ class hgwebdir(object):

                 if u.configbool("web", "hidden", untrusted=True):
                     continue
+                if not is_op_allowed(req, u, 'read', allow_by_default=True):
+                    continue

                 url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
                        .replace("//", "/")) + '/'
# HG changeset patch
# User Nilton Volpato <nilton.volpato at gmail.com>
# Date 1197427772 7200
# Node ID a97ec8971abf2eab851b5cfadf0402ec63ba9a63
# Parent  4fd078545b10e17a094c6122cbec96f65466d782
Fixed semantics of is_op_allowed: allow_by_default matters only if no
explicit allow is given

diff -r 4fd078545b10 -r a97ec8971abf mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py Wed Dec 12 00:26:15 2007 -0200
+++ b/mercurial/hgweb/common.py Wed Dec 12 00:49:32 2007 -0200
@@ -112,6 +112,7 @@ def is_op_allowed(req, ui, op, allow_by_
     if deny and (not user or deny == ['*'] or user in deny):
         return False

-    if allow_by_default: return allow_by_default
     allow = ui.configlist('web', 'allow_' + op, default=None, untrusted=True)
+    if not allow and allow_by_default:
+        return True
     return allow and (allow == ['*'] or user in allow)


More information about the Mercurial-devel mailing list