# HG changeset patch # User Mark Edgington # Date 1225463286 -3600 # Node ID 2dc868712dcc59dec7c8a77a7d28af533a5b6f37 # Parent 866d2715aff5716b2cd863c112c478704fb8c8b1 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. diff -r 866d2715aff5 -r 2dc868712dcc doc/hgrc.5.txt --- 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". diff -r 866d2715aff5 -r 2dc868712dcc mercurial/hgweb/hgweb_mod.py --- 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') diff -r 866d2715aff5 -r 2dc868712dcc mercurial/hgweb/hgwebdir_mod.py --- 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('/'))