# HG changeset patch # User Dirkjan Ochtman # Date 1214732106 -7200 # Node ID d3147b4e3e8a4b0278f3905bce3629768994d550 # Parent 959efdac4a9c7aa0e0018b3652e77bd894401a66 hgweb: centralize permission checks for protocol commands Consistently enforces authorization checks set up in hgrc up front, so that the actual commands don't have to worry about them and implementers of hgweb alternatives can easily implement their own permission checks. diff -r 959efdac4a9c -r d3147b4e3e8a mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py Sun Jun 29 11:02:19 2008 +0200 +++ b/mercurial/hgweb/hgweb_mod.py Sun Jun 29 11:35:06 2008 +0200 @@ -16,6 +16,13 @@ from request import wsgirequest import webcommands, protocol, webutil +perms = { + 'changegroup': 'pull', + 'changegroupsubset': 'pull', + 'unbundle': 'push', + 'stream_out': 'pull', +} + class hgweb(object): def __init__(self, repo, name=None): if isinstance(repo, str): @@ -95,6 +102,8 @@ cmd = req.form.get('cmd', [''])[0] if cmd and cmd in protocol.__all__: + if cmd in perms and not self.check_perm(req, perms[cmd]): + return method = getattr(protocol, cmd) method(self, req) return @@ -343,16 +352,39 @@ 'zip': ('application/zip', 'zip', '.zip', None), } - 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.''' + def check_perm(self, req, op): + '''Check permission for operation based on request data (including + authentication info. Return true if op allowed, else false.''' + + def error(status, message): + req.respond(status, protocol.HGTYPE) + req.write('0\n%s\n' % message) + + if op == 'pull': + return self.allowpull + + # enforce that you can only push using POST requests + if req.env['REQUEST_METHOD'] != 'POST': + error('405 Method Not Allowed', 'push requires POST request') + return False + + # require ssl by default for pushing, auth info cannot be sniffed + # and replayed + scheme = req.env.get('wsgi.url_scheme') + if self.configbool('web', 'push_ssl', True) and scheme != 'https': + error(HTTP_OK, 'ssl required') + return False user = req.env.get('REMOTE_USER') - deny = self.configlist('web', 'deny_' + op) + deny = self.configlist('web', 'deny_push') if deny and (not user or deny == ['*'] or user in deny): + error('401 Unauthorized', 'push not authorized') return False - allow = self.configlist('web', 'allow_' + op) - return (allow and (allow == ['*'] or user in allow)) or default + allow = self.configlist('web', 'allow_push') + result = allow and (allow == ['*'] or user in allow) + if not result: + error('401 Unauthorized', 'push not authorized') + + return result diff -r 959efdac4a9c -r d3147b4e3e8a mercurial/hgweb/protocol.py --- a/mercurial/hgweb/protocol.py Sun Jun 29 11:02:19 2008 +0200 +++ b/mercurial/hgweb/protocol.py Sun Jun 29 11:35:06 2008 +0200 @@ -62,8 +62,6 @@ def changegroup(web, req): req.respond(HTTP_OK, HGTYPE) nodes = [] - if not web.allowpull: - return if 'roots' in req.form: nodes = map(bin, req.form['roots'][0].split(" ")) @@ -82,8 +80,6 @@ req.respond(HTTP_OK, HGTYPE) bases = [] heads = [] - if not web.allowpull: - return if 'bases' in req.form: bases = [bin(x) for x in req.form['bases'][0].split(' ')] @@ -120,28 +116,7 @@ req.write('0\n') req.write(response) - # enforce that you can only unbundle with POST requests - if req.env['REQUEST_METHOD'] != 'POST': - headers = {'status': '405 Method Not Allowed'} - bail('unbundle requires POST request\n', headers) - return - - # require ssl by default, auth info cannot be sniffed and - # replayed - ssl_req = web.configbool('web', 'push_ssl', True) - if ssl_req: - if req.env.get('wsgi.url_scheme') != 'https': - bail('ssl required\n') - return - proto = 'https' - else: - proto = 'http' - - # do not allow push unless explicitly allowed - if not web.check_perm(req, 'push', False): - bail('push not authorized\n', headers={'status': '401 Unauthorized'}) - return - + proto = req.env.get('wsgi.url_scheme') or 'http' their_heads = req.form['heads'][0].split(' ') def check_heads(): @@ -224,7 +199,5 @@ os.unlink(tempname) def stream_out(web, req): - if not web.allowpull: - return req.respond(HTTP_OK, HGTYPE) streamclone.stream_out(web.repo, req, untrusted=True) diff -r 959efdac4a9c -r d3147b4e3e8a tests/test-hgweb-commands.out Binary file tests/test-hgweb-commands.out has changed