Mercurial > hg-stable
changeset 6779:d3147b4e3e8a
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.
author | Dirkjan Ochtman <dirkjan@ochtman.nl> |
---|---|
date | Sun, 29 Jun 2008 11:35:06 +0200 |
parents | 959efdac4a9c |
children | 4c1d67e0fa8c |
files | mercurial/hgweb/hgweb_mod.py mercurial/hgweb/protocol.py tests/test-hgweb-commands.out |
diffstat | 3 files changed, 40 insertions(+), 35 deletions(-) [+] |
line wrap: on
line diff
--- 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
--- 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)