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.
--- 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;;
--- 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
--- 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))