comparison mercurial/url.py @ 8847:7951f385fcb7

url: support client certificate files over HTTPS (issue643) This extends the httpshandler with the means to utilise the auth section to provide it with a PEM encoded certificate key file and certificate chain file. This works also with sites that both require client certificate authentication and basic or digest password authentication, although the latter situation may require the user to enter the PEM password multiple times.
author Henrik Stuart <hg@hstuart.dk>
date Sat, 20 Jun 2009 10:58:57 +0200
parents 59acb9c7d90f
children 89b71acdac9a
comparison
equal deleted inserted replaced
8846:b30775386d40 8847:7951f385fcb7
107 if user and passwd: 107 if user and passwd:
108 self._writedebug(user, passwd) 108 self._writedebug(user, passwd)
109 return (user, passwd) 109 return (user, passwd)
110 110
111 if not user: 111 if not user:
112 user, passwd = self._readauthtoken(authuri) 112 auth = self.readauthtoken(authuri)
113 if auth:
114 user, passwd = auth.get('username'), auth.get('password')
113 if not user or not passwd: 115 if not user or not passwd:
114 if not self.ui.interactive(): 116 if not self.ui.interactive():
115 raise util.Abort(_('http authorization required')) 117 raise util.Abort(_('http authorization required'))
116 118
117 self.ui.write(_("http authorization required\n")) 119 self.ui.write(_("http authorization required\n"))
130 132
131 def _writedebug(self, user, passwd): 133 def _writedebug(self, user, passwd):
132 msg = _('http auth: user %s, password %s\n') 134 msg = _('http auth: user %s, password %s\n')
133 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set')) 135 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
134 136
135 def _readauthtoken(self, uri): 137 def readauthtoken(self, uri):
136 # Read configuration 138 # Read configuration
137 config = dict() 139 config = dict()
138 for key, val in self.ui.configitems('auth'): 140 for key, val in self.ui.configitems('auth'):
139 group, setting = key.split('.', 1) 141 group, setting = key.split('.', 1)
140 gdict = config.setdefault(group, dict()) 142 gdict = config.setdefault(group, dict())
141 gdict[setting] = val 143 gdict[setting] = val
142 144
143 # Find the best match 145 # Find the best match
144 scheme, hostpath = uri.split('://', 1) 146 scheme, hostpath = uri.split('://', 1)
145 bestlen = 0 147 bestlen = 0
146 bestauth = None, None 148 bestauth = None
147 for auth in config.itervalues(): 149 for auth in config.itervalues():
148 prefix = auth.get('prefix') 150 prefix = auth.get('prefix')
149 if not prefix: continue 151 if not prefix: continue
150 p = prefix.split('://', 1) 152 p = prefix.split('://', 1)
151 if len(p) > 1: 153 if len(p) > 1:
153 else: 155 else:
154 schemes = (auth.get('schemes') or 'https').split() 156 schemes = (auth.get('schemes') or 'https').split()
155 if (prefix == '*' or hostpath.startswith(prefix)) and \ 157 if (prefix == '*' or hostpath.startswith(prefix)) and \
156 len(prefix) > bestlen and scheme in schemes: 158 len(prefix) > bestlen and scheme in schemes:
157 bestlen = len(prefix) 159 bestlen = len(prefix)
158 bestauth = auth.get('username'), auth.get('password') 160 bestauth = auth
159 return bestauth 161 return bestauth
160 162
161 class proxyhandler(urllib2.ProxyHandler): 163 class proxyhandler(urllib2.ProxyHandler):
162 def __init__(self, ui): 164 def __init__(self, ui):
163 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy') 165 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
409 response_class = keepalive.HTTPResponse 411 response_class = keepalive.HTTPResponse
410 # must be able to send big bundle as stream. 412 # must be able to send big bundle as stream.
411 send = _gen_sendfile(httplib.HTTPSConnection) 413 send = _gen_sendfile(httplib.HTTPSConnection)
412 414
413 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler): 415 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
416 def __init__(self, ui):
417 keepalive.KeepAliveHandler.__init__(self)
418 urllib2.HTTPSHandler.__init__(self)
419 self.ui = ui
420 self.pwmgr = passwordmgr(self.ui)
421
414 def https_open(self, req): 422 def https_open(self, req):
415 return self.do_open(httpsconnection, req) 423 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
424 return self.do_open(self._makeconnection, req)
425
426 def _makeconnection(self, host, port=443, *args, **kwargs):
427 keyfile = None
428 certfile = None
429
430 if args: # key_file
431 keyfile = args.pop(0)
432 if args: # cert_file
433 certfile = args.pop(0)
434
435 # if the user has specified different key/cert files in
436 # hgrc, we prefer these
437 if self.auth and 'key' in self.auth and 'cert' in self.auth:
438 keyfile = self.auth['key']
439 certfile = self.auth['cert']
440
441 return httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
416 442
417 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if 443 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
418 # it doesn't know about the auth type requested. This can happen if 444 # it doesn't know about the auth type requested. This can happen if
419 # somebody is using BasicAuth and types a bad password. 445 # somebody is using BasicAuth and types a bad password.
420 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler): 446 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
458 construct an opener suitable for urllib2 484 construct an opener suitable for urllib2
459 authinfo will be added to the password manager 485 authinfo will be added to the password manager
460 ''' 486 '''
461 handlers = [httphandler()] 487 handlers = [httphandler()]
462 if has_https: 488 if has_https:
463 handlers.append(httpshandler()) 489 handlers.append(httpshandler(ui))
464 490
465 handlers.append(proxyhandler(ui)) 491 handlers.append(proxyhandler(ui))
466 492
467 passmgr = passwordmgr(ui) 493 passmgr = passwordmgr(ui)
468 if authinfo is not None: 494 if authinfo is not None: