37
# TODO: It may be possible to share the password_manager across
38
# all transports by prefixing the realm by the protocol used
39
# (especially if other protocols do not use realms). See
40
# PasswordManager below.
42
37
# FIXME: Oversimplifying, two kind of exceptions should be
43
38
# raised, once a request is issued: URLError before we have been
44
39
# able to process the response, HTTPError after that. Process the
113
109
# close the connection. Some cases are not correctly detected by
114
110
# httplib.HTTPConnection.getresponse (called by
115
111
# httplib.HTTPResponse.begin). The CONNECT response for the https
116
# through proxy case is one.
112
# through proxy case is one. Note: the 'will_close' below refers
113
# to the "true" socket between us and the server, whereas the
114
# 'close()' above refers to the copy of that socket created by
115
# httplib for the response itself. So, in the if above we close the
116
# socket to indicate that we are done with the response whereas
117
# below we keep the socket with the server opened.
117
118
self.will_close = False
193
194
# Unless told otherwise, redirections are not followed
194
195
self.follow_redirections = False
195
196
# auth and proxy_auth are dicts containing, at least
196
# (scheme, url, realm, user, password).
197
# (scheme, host, port, realm, user, password, protocol, path).
197
198
# The dict entries are mostly handled by the AuthHandler.
198
199
# Some authentication schemes may add more entries.
240
241
urllib2.Request.set_proxy(self, proxy, type)
243
def extract_credentials(url):
244
"""Extracts credentials information from url.
246
Get user and password from url of the form: http://user:pass@host/path
247
:returns: (clean_url, user, password)
249
scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
252
auth, netloc = netloc.split('@', 1)
254
user, password = auth.split(':', 1)
256
user, password = auth, None
257
user = urllib.unquote(user)
258
if password is not None:
259
password = urllib.unquote(password)
264
# Build the clean url
265
clean_url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
267
return clean_url, user, password
269
def extract_authentication_uri(url):
270
"""Extract the authentication uri from any url.
272
In the context of bzr, we simplified the authentication uri
273
to the host only. For the transport lifetime, we allow only
274
one user by realm on a given host. I.e. handling several
275
users for different paths for the same realm should be done
278
scheme, host, path, query, fragment = urlparse.urlsplit(url)
279
return '%s://%s' % (scheme, host)
282
# The AuthHandler classes handle the authentication of the requests, to do
283
# that, they need a PasswordManager *at build time*. We also need one to reuse
284
# the passwords entered by the user.
285
class PasswordManager(urllib2.HTTPPasswordMgrWithDefaultRealm):
288
urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
291
244
class ConnectionHandler(urllib2.BaseHandler):
292
245
"""Provides connection-sharing by pre-processing requests.
405
358
raise errors.ConnectionError("Couldn't resolve host '%s'"
406
359
% request.get_origin_req_host(),
407
360
orig_error=exc_val)
361
elif isinstance(exc_val, httplib.ImproperConnectionState):
362
# The httplib pipeline is in incorrect state, it's a bug in our
364
raise exc_type, exc_val, exc_tb
410
367
if self._debuglevel > 0:
435
392
# All other exception are considered connection related.
437
# httplib.HTTPException should indicate a bug in our
438
# urllib-based implementation, somewhow the httplib
439
# pipeline is in an incorrect state, we retry in hope that
440
# this will correct the problem but that may need
441
# investigation (note that no such bug is known as of
444
394
# socket errors generally occurs for reasons
445
395
# far outside our scope, so closing the
446
396
# connection and retrying is the best we can
449
# FIXME: and then there is HTTPError raised by:
450
# - HTTPDefaultErrorHandler (we define our own)
451
# - HTTPRedirectHandler.redirect_request
453
399
my_exception = errors.ConnectionError(
454
400
msg= 'while sending %s %s:' % (request.get_method(),
455
401
request.get_selector()),
737
683
handler_order = 100
738
684
_debuglevel = DEBUG
740
def __init__(self, password_manager, proxies=None):
686
def __init__(self, proxies=None):
741
687
urllib2.ProxyHandler.__init__(self, proxies)
742
self.password_manager = password_manager
743
688
# First, let's get rid of urllib2 implementation
744
689
for type, proxy in self.proxies.items():
745
690
if self._debuglevel > 0:
783
728
def proxy_bypass(self, host):
784
729
"""Check if host should be proxied or not"""
785
no_proxy = self.get_proxy_env_var('no', None)
730
no_proxy = self.get_proxy_env_var('no', default_to=None)
786
731
if no_proxy is None:
788
733
hhost, hport = urllib.splitport(host)
814
759
# grok user:password@host:port as well as
815
760
# http://user:password@host:port
817
# Extract credentials from the url and store them in the
818
# password manager so that the proxy AuthHandler can use
820
proxy, user, password = extract_credentials(proxy)
762
(scheme, user, password,
763
host, port, path) = transport.ConnectedTransport._split_url(proxy)
821
765
if request.proxy_auth == {}:
822
# No proxy auth parameter are available, we are
823
# handling the first proxied request, intialize.
824
# scheme and realm will be set by the AuthHandler
825
authuri = extract_authentication_uri(proxy)
826
request.proxy_auth = {'user': user, 'password': password,
828
if user and password is not None: # '' is a valid password
829
# We default to a realm of None to catch them all.
830
self.password_manager.add_password(None, authuri,
832
orig_type = request.get_type()
833
scheme, r_scheme = urllib.splittype(proxy)
834
if self._debuglevel > 0:
835
print 'scheme: %s, r_scheme: %s' % (scheme, r_scheme)
836
host, XXX = urllib.splithost(r_scheme)
838
raise errors.InvalidURL(proxy,
839
'Invalid syntax in proxy env variable')
840
host = urllib.unquote(host)
841
request.set_proxy(host, type)
842
if self._debuglevel > 0:
843
print 'set_proxy: proxy set to %s://%s' % (type, host)
766
# No proxy auth parameter are available, we are handling the first
767
# proxied request, intialize. scheme (the authentication scheme)
768
# and realm will be set by the AuthHandler
769
request.proxy_auth = {
770
'host': host, 'port': port,
771
'user': user, 'password': password,
773
# We ignore path since we connect to a proxy
778
phost = host + ':%d' % port
779
request.set_proxy(phost, type)
780
if self._debuglevel > 0:
781
print 'set_proxy: proxy set to %s://%s' % (type, phost)
890
828
# - auth_required_header: the header received from the server
891
829
# - auth_header: the header sent in the request
893
def __init__(self, password_manager):
894
self.password_manager = password_manager
895
self.find_user_password = password_manager.find_user_password
896
self.add_password = password_manager.add_password
832
# We want to know when we enter into an try/fail cycle of
833
# authentications so we initialize to None to indicate that we aren't
834
# in such a cycle by default.
897
835
self._retry_count = None
899
837
def update_auth(self, auth, key, value):
913
851
# Don't try to authenticate endlessly
914
852
if self._retry_count is None:
915
# The retry being recusrsive calls, None identify the first try
916
self._retry_count = 0
853
# The retry being recusrsive calls, None identify the first retry
854
self._retry_count = 1
918
856
self._retry_count += 1
919
857
if self._retry_count > self._max_retry:
935
873
if self.auth_match(server_header, auth):
936
874
# auth_match may have modified auth (by adding the
937
875
# password or changing the realm, for example)
938
if request.get_header(self.auth_header, None) is not None \
939
and not auth['modified']:
876
if (request.get_header(self.auth_header, None) is not None
877
and not auth['modified']):
940
878
# We already tried that, give up
998
936
# It may happen that we need to reconnect later, let's be ready
999
937
self._retry_count = None
1001
def get_password(self, user, authuri, realm=None):
939
def get_user_password(self, auth):
1002
940
"""Ask user for a password if none is already available."""
1003
user_found, password = self.find_user_password(realm, authuri)
1004
if user_found != user:
1005
# FIXME: write a test for that case
941
auth_conf = config.AuthenticationConfig()
943
password = auth['password']
944
realm = auth['realm']
947
user = auth.get_user(auth['protocol'], auth['host'],
948
port=auth['port'], path=auth['path'],
951
# Default to local user
952
user = getpass.getuser()
1008
954
if password is None:
1009
# Prompt user only if we can't find a password
1011
realm_prompt = " Realm: '%s'" % realm
1014
scheme, host, path, query, fragment = urlparse.urlsplit(authuri)
1015
password = ui.ui_factory.get_password(prompt=self.password_prompt,
1016
user=user, host=host,
1018
if password is not None:
1019
self.add_password(realm, authuri, user, password)
955
password = auth_conf.get_password(
956
auth['protocol'], auth['host'], user, port=auth['port'],
957
path=auth['path'], realm=realm,
958
prompt=self.build_password_prompt(auth))
960
return user, password
962
def _build_password_prompt(self, auth):
963
"""Build a prompt taking the protocol used into account.
965
The AuthHandler is used by http and https, we want that information in
966
the prompt, so we build the prompt from the authentication dict which
967
contains all the needed parts.
969
Also, hhtp and proxy AuthHandlers present different prompts to the
970
user. The daughter classes hosuld implements a public
971
build_password_prompt using this method.
973
prompt = '%s' % auth['protocol'].upper() + ' %(user)s@%(host)s'
974
realm = auth['realm']
975
if realm is not None:
976
prompt += ", Realm: '%s'" % realm
977
prompt += ' password'
1022
980
def http_request(self, request):
1023
981
"""Insert an authentication header if information is available"""
1056
1014
# Put useful info into auth
1057
1015
self.update_auth(auth, 'scheme', scheme)
1058
1016
self.update_auth(auth, 'realm', realm)
1059
if auth.get('password',None) is None:
1060
password = self.get_password(auth['user'], auth['authuri'],
1017
if auth['user'] is None or auth['password'] is None:
1018
user, password = self.get_user_password(auth)
1019
self.update_auth(auth, 'user', user)
1062
1020
self.update_auth(auth, 'password', password)
1063
1021
return match is not None
1120
1078
realm = req_auth.get('realm', None)
1121
if auth.get('password',None) is None:
1122
auth['password'] = self.get_password(auth['user'],
1125
1079
# Put useful info into auth
1080
self.update_auth(auth, 'scheme', scheme)
1081
self.update_auth(auth, 'realm', realm)
1082
if auth['user'] is None or auth['password'] is None:
1083
user, password = self.get_user_password(auth)
1084
self.update_auth(auth, 'user', user)
1085
self.update_auth(auth, 'password', password)
1127
self.update_auth(auth, 'scheme', scheme)
1128
1088
if req_auth.get('algorithm', None) is not None:
1129
1089
self.update_auth(auth, 'algorithm', req_auth.get('algorithm'))
1130
self.update_auth(auth, 'realm', realm)
1131
1090
nonce = req_auth['nonce']
1132
1091
if auth.get('nonce', None) != nonce:
1133
1092
# A new nonce, never used
1200
1158
"""Set the auth params for the request"""
1201
1159
request.auth = auth
1161
def build_password_prompt(self, auth):
1162
return self._build_password_prompt(auth)
1203
1164
def http_error_401(self, req, fp, code, msg, headers):
1204
1165
return self.auth_required(req, headers)
1227
1187
"""Set the auth params for the request"""
1228
1188
request.proxy_auth = auth
1190
def build_password_prompt(self, auth):
1191
prompt = self._build_password_prompt(auth)
1192
prompt = 'Proxy ' + prompt
1230
1195
def http_error_407(self, req, fp, code, msg, headers):
1231
1196
return self.auth_required(req, headers)
1307
1272
connection=ConnectionHandler,
1308
1273
redirect=HTTPRedirectHandler,
1309
1274
error=HTTPErrorProcessor,):
1310
self.password_manager = PasswordManager()
1311
1275
self._opener = urllib2.build_opener( \
1312
1276
connection, redirect, error,
1313
ProxyHandler(self.password_manager),
1314
HTTPBasicAuthHandler(self.password_manager),
1315
HTTPDigestAuthHandler(self.password_manager),
1316
ProxyBasicAuthHandler(self.password_manager),
1317
ProxyDigestAuthHandler(self.password_manager),
1278
HTTPBasicAuthHandler(),
1279
HTTPDigestAuthHandler(),
1280
ProxyBasicAuthHandler(),
1281
ProxyDigestAuthHandler(),
1320
1284
HTTPDefaultErrorHandler,