comparison mercurial/hgweb/hgweb_mod.py @ 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 44c5157474e7
children 4c1d67e0fa8c
comparison
equal deleted inserted replaced
6778:959efdac4a9c 6779:d3147b4e3e8a
13 from mercurial import revlog, templater, templatefilters, changegroup 13 from mercurial import revlog, templater, templatefilters, changegroup
14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse 14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR 15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from request import wsgirequest 16 from request import wsgirequest
17 import webcommands, protocol, webutil 17 import webcommands, protocol, webutil
18
19 perms = {
20 'changegroup': 'pull',
21 'changegroupsubset': 'pull',
22 'unbundle': 'push',
23 'stream_out': 'pull',
24 }
18 25
19 class hgweb(object): 26 class hgweb(object):
20 def __init__(self, repo, name=None): 27 def __init__(self, repo, name=None):
21 if isinstance(repo, str): 28 if isinstance(repo, str):
22 parentui = ui.ui(report_untrusted=False, interactive=False) 29 parentui = ui.ui(report_untrusted=False, interactive=False)
93 # protocol bits don't need to create any URLs 100 # protocol bits don't need to create any URLs
94 # and the clients always use the old URL structure 101 # and the clients always use the old URL structure
95 102
96 cmd = req.form.get('cmd', [''])[0] 103 cmd = req.form.get('cmd', [''])[0]
97 if cmd and cmd in protocol.__all__: 104 if cmd and cmd in protocol.__all__:
105 if cmd in perms and not self.check_perm(req, perms[cmd]):
106 return
98 method = getattr(protocol, cmd) 107 method = getattr(protocol, cmd)
99 method(self, req) 108 method(self, req)
100 return 109 return
101 110
102 # work with CGI variables to create coherent structure 111 # work with CGI variables to create coherent structure
341 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None), 350 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
342 'gz': ('application/x-tar', 'tgz', '.tar.gz', None), 351 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
343 'zip': ('application/zip', 'zip', '.zip', None), 352 'zip': ('application/zip', 'zip', '.zip', None),
344 } 353 }
345 354
346 def check_perm(self, req, op, default): 355 def check_perm(self, req, op):
347 '''check permission for operation based on user auth. 356 '''Check permission for operation based on request data (including
348 return true if op allowed, else false. 357 authentication info. Return true if op allowed, else false.'''
349 default is policy to use if no config given.''' 358
359 def error(status, message):
360 req.respond(status, protocol.HGTYPE)
361 req.write('0\n%s\n' % message)
362
363 if op == 'pull':
364 return self.allowpull
365
366 # enforce that you can only push using POST requests
367 if req.env['REQUEST_METHOD'] != 'POST':
368 error('405 Method Not Allowed', 'push requires POST request')
369 return False
370
371 # require ssl by default for pushing, auth info cannot be sniffed
372 # and replayed
373 scheme = req.env.get('wsgi.url_scheme')
374 if self.configbool('web', 'push_ssl', True) and scheme != 'https':
375 error(HTTP_OK, 'ssl required')
376 return False
350 377
351 user = req.env.get('REMOTE_USER') 378 user = req.env.get('REMOTE_USER')
352 379
353 deny = self.configlist('web', 'deny_' + op) 380 deny = self.configlist('web', 'deny_push')
354 if deny and (not user or deny == ['*'] or user in deny): 381 if deny and (not user or deny == ['*'] or user in deny):
382 error('401 Unauthorized', 'push not authorized')
355 return False 383 return False
356 384
357 allow = self.configlist('web', 'allow_' + op) 385 allow = self.configlist('web', 'allow_push')
358 return (allow and (allow == ['*'] or user in allow)) or default 386 result = allow and (allow == ['*'] or user in allow)
387 if not result:
388 error('401 Unauthorized', 'push not authorized')
389
390 return result