# HG changeset patch # User Vadim Gelfer # Date 1150842181 25200 # Node ID e10665147d26894b9c0b1b50c103a42a9a3aaa77 # Parent c91118f425d04738c7b7e7271a400455155c2bdd push over http: server side authorization support. new hgrc entries allow_push, deny_push, push_ssl control push over http. allow_push list controls push. if empty or not set, no user can push. if "*", any user (incl. unauthenticated user) can push. if list of user names, only authenticated users in list can push. deny_push list examined before allow_push. if "*", no user can push. if list of user names, no unauthenticated user can push, and no users in list can push. push_ssl requires https connection for push. default is true, so password sniffing can not be done. diff -r c91118f425d0 -r e10665147d26 doc/hgrc.5.txt --- a/doc/hgrc.5.txt Tue Jun 20 15:17:28 2006 -0700 +++ b/doc/hgrc.5.txt Tue Jun 20 15:23:01 2006 -0700 @@ -381,6 +381,14 @@ Default is false. allowpull;; Whether to allow pulling from the repository. Default is true. + allow_push;; + Whether to allow pushing to the repository. If empty or not set, + push is not allowed. If the special value "*", any remote user + can push, including unauthenticated users. Otherwise, the remote + user must have been authenticated, and the authenticated user name + must be present in this list (separated by whitespace or ","). + The contents of the allow_push list are examined after the + deny_push list. allowzip;; (DEPRECATED) Whether to allow .zip downloading of repo revisions. Default is false. This feature creates temporary files. @@ -391,6 +399,13 @@ contact;; Name or email address of the person in charge of the repository. Default is "unknown". + deny_push;; + Whether to deny pushing to the repository. If empty or not set, + push is not denied. If the special value "*", all remote users + are denied push. Otherwise, unauthenticated users are all denied, + 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. description;; Textual description of the repository's purpose or contents. Default is "unknown". @@ -407,6 +422,9 @@ Maximum number of files to list per changeset. Default is 10. port;; Port to listen on. Default is 8000. + push_ssl;; + Whether to require that inbound pushes be transported over SSL to + prevent password sniffing. Default is true. style;; Which template map style to use. templates;; diff -r c91118f425d0 -r e10665147d26 mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py Tue Jun 20 15:17:28 2006 -0700 +++ b/mercurial/hgweb/hgweb_mod.py Tue Jun 20 15:23:01 2006 -0700 @@ -839,19 +839,59 @@ 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.repo.ui.config('web', 'deny_' + op, '') + deny = deny.replace(',', ' ').split() + + if deny and (not user or deny == ['*'] or user in deny): + return False + + allow = self.repo.ui.config('web', 'allow_' + op, '') + allow = allow.replace(',', ' ').split() + + 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']) + for s in util.filechunkiter(req, limit=length): + # drain incoming bundle, else client will not see + # response when run outside cgi script + pass + req.httphdr("application/mercurial-0.1", headers=headers) + req.write('0\n') + req.write(response) + + # require ssl by default, auth info cannot be sniffed and + # replayed + ssl_req = self.repo.ui.configbool('web', 'push_ssl', True) + if ssl_req and not req.env.get('HTTPS'): + bail(_('ssl required\n')) + return + + # do not allow push unless explicitly allowed + if not self.check_perm(req, 'push', False): + bail(_('push not authorized\n'), + headers={'status': '401 Unauthorized'}) + return + + req.httphdr("application/mercurial-0.1") + their_heads = req.form['heads'][0].split(' ') def check_heads(): heads = map(hex, self.repo.heads()) return their_heads == [hex('force')] or their_heads == heads - req.httphdr("application/mercurial-0.1") - # fail early if possible if not check_heads(): - req.write('0\n') - req.write(_('unsynced changes\n')) + bail(_('unsynced changes\n')) return # do not lock repo until all changegroup data is diff -r c91118f425d0 -r e10665147d26 mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py Tue Jun 20 15:17:28 2006 -0700 +++ b/mercurial/hgweb/request.py Tue Jun 20 15:23:01 2006 -0700 @@ -45,9 +45,9 @@ self.out.write("%s: %s\r\n" % header) self.out.write("\r\n") - def httphdr(self, type, filename=None, length=0): - - headers = [('Content-type', type)] + def httphdr(self, type, filename=None, length=0, headers={}): + headers = headers.items() + headers.append(('Content-type', type)) if filename: headers.append(('Content-disposition', 'attachment; filename=%s' % filename))