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
194
189
# Unless told otherwise, redirections are not followed
195
190
self.follow_redirections = False
196
191
# auth and proxy_auth are dicts containing, at least
197
# (scheme, url, realm, user, password).
192
# (scheme, host, port, realm, user, password, protocol, path).
198
193
# The dict entries are mostly handled by the AuthHandler.
199
194
# Some authentication schemes may add more entries.
241
236
urllib2.Request.set_proxy(self, proxy, type)
244
def extract_credentials(url):
245
"""Extracts credentials information from url.
247
Get user and password from url of the form: http://user:pass@host/path
248
:returns: (clean_url, user, password)
250
scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
253
auth, netloc = netloc.split('@', 1)
255
user, password = auth.split(':', 1)
257
user, password = auth, None
258
user = urllib.unquote(user)
259
if password is not None:
260
password = urllib.unquote(password)
265
# Build the clean url
266
clean_url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
268
return clean_url, user, password
270
def extract_authentication_uri(url):
271
"""Extract the authentication uri from any url.
273
In the context of bzr, we simplify the authentication uri
274
to the host only. For the transport lifetime, we allow only
275
one user by realm on a given host. I.e. handling several
276
users for different paths for the same realm should be done
279
scheme, host, path, query, fragment = urlparse.urlsplit(url)
280
return '%s://%s' % (scheme, host)
283
# The AuthHandler classes handle the authentication of the requests, to do
284
# that, they need a PasswordManager *at build time*. We also need one to reuse
285
# the passwords entered by the user.
286
class PasswordManager(urllib2.HTTPPasswordMgrWithDefaultRealm):
289
urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
292
239
class ConnectionHandler(urllib2.BaseHandler):
293
240
"""Provides connection-sharing by pre-processing requests.
738
685
handler_order = 100
739
686
_debuglevel = DEBUG
741
def __init__(self, password_manager, proxies=None):
688
def __init__(self, proxies=None):
742
689
urllib2.ProxyHandler.__init__(self, proxies)
743
self.password_manager = password_manager
744
690
# First, let's get rid of urllib2 implementation
745
691
for type, proxy in self.proxies.items():
746
692
if self._debuglevel > 0:
784
730
def proxy_bypass(self, host):
785
731
"""Check if host should be proxied or not"""
786
no_proxy = self.get_proxy_env_var('no', None)
732
no_proxy = self.get_proxy_env_var('no', default_to=None)
787
733
if no_proxy is None:
789
735
hhost, hport = urllib.splitport(host)
815
761
# grok user:password@host:port as well as
816
762
# http://user:password@host:port
818
# FIXME: query AuthenticationConfig too
764
(scheme, user, password,
765
host, port, path) = transport.ConnectedTransport._split_url(proxy)
820
# Extract credentials from the url and store them in the
821
# password manager so that the proxy AuthHandler can use
823
proxy, user, password = extract_credentials(proxy)
824
767
if request.proxy_auth == {}:
825
# No proxy auth parameter are available, we are
826
# handling the first proxied request, intialize.
827
# scheme and realm will be set by the AuthHandler
828
authuri = extract_authentication_uri(proxy)
829
request.proxy_auth = {'user': user, 'password': password,
831
if user and password is not None: # '' is a valid password
832
# We default to a realm of None to catch them all.
833
self.password_manager.add_password(None, authuri,
835
orig_type = request.get_type()
836
scheme, r_scheme = urllib.splittype(proxy)
837
if self._debuglevel > 0:
838
print 'scheme: %s, r_scheme: %s' % (scheme, r_scheme)
839
host, XXX = urllib.splithost(r_scheme)
841
raise errors.InvalidURL(proxy,
842
'Invalid syntax in proxy env variable')
843
host = urllib.unquote(host)
844
request.set_proxy(host, type)
845
if self._debuglevel > 0:
846
print 'set_proxy: proxy set to %s://%s' % (type, host)
768
# No proxy auth parameter are available, we are handling the first
769
# proxied request, intialize. scheme (the authentication scheme)
770
# and realm will be set by the AuthHandler
771
request.proxy_auth = {
772
'host': host, 'port': port,
773
'user': user, 'password': password,
775
# We ignore path since we connect to a proxy
780
phost = host + ':%d' % port
781
request.set_proxy(phost, type)
782
if self._debuglevel > 0:
783
print 'set_proxy: proxy set to %s://%s' % (type, phost)
893
830
# - auth_required_header: the header received from the server
894
831
# - auth_header: the header sent in the request
896
def __init__(self, password_manager):
897
self.password_manager = password_manager
898
self.find_user_password = password_manager.find_user_password
899
self.add_password = password_manager.add_password
834
# We want to know when we enter into an try/fail cycle of
835
# authentications so we initialize to None to indicate that we aren't
836
# in such a cycle by default.
900
837
self._retry_count = None
902
839
def update_auth(self, auth, key, value):
916
853
# Don't try to authenticate endlessly
917
854
if self._retry_count is None:
918
# The retry being recusrsive calls, None identify the first try
919
self._retry_count = 0
855
# The retry being recusrsive calls, None identify the first retry
856
self._retry_count = 1
921
858
self._retry_count += 1
922
859
if self._retry_count > self._max_retry:
938
875
if self.auth_match(server_header, auth):
939
876
# auth_match may have modified auth (by adding the
940
877
# password or changing the realm, for example)
941
if request.get_header(self.auth_header, None) is not None \
942
and not auth['modified']:
878
if (request.get_header(self.auth_header, None) is not None
879
and not auth['modified']):
943
880
# We already tried that, give up
1001
938
# It may happen that we need to reconnect later, let's be ready
1002
939
self._retry_count = None
1004
def get_password(self, user, authuri, realm=None):
941
# FIXME: Rename get_credentials or something and query the auth config for
943
def get_password(self, auth):
1005
944
"""Ask user for a password if none is already available."""
1006
user_found, password = self.find_user_password(realm, authuri)
1007
if user_found != user:
1008
# FIXME: write a test for that case
946
realm = auth['realm']
947
password = auth['password']
1011
949
if password is None:
1012
scheme, netloc, path, query, fragment = urlparse.urlsplit(authuri)
1014
host, port = netloc.rsplit(':', 1)
1015
# port is an int here invalid values have been handled by the
1021
auth = config.AuthenticationConfig()
1022
password = auth.get_password(
1023
scheme, host, user, port=port, path=path, realm=realm,
950
auth_conf = config.AuthenticationConfig()
951
password = auth_conf.get_password(
952
auth['protocol'], auth['host'], user, port=auth['port'],
953
path=auth['path'], realm=realm,
1024
954
prompt=self.password_prompt)
1025
if password is not None:
1026
self.add_password(realm, authuri, user, password)
1029
957
def http_request(self, request):
1064
992
self.update_auth(auth, 'scheme', scheme)
1065
993
self.update_auth(auth, 'realm', realm)
1066
994
if auth.get('password',None) is None:
1067
password = self.get_password(auth['user'], auth['authuri'],
995
password = self.get_password(auth)
1069
996
self.update_auth(auth, 'password', password)
1070
997
return match is not None
1127
1054
realm = req_auth.get('realm', None)
1055
# Put useful info into auth
1056
self.update_auth(auth, 'scheme', scheme)
1057
self.update_auth(auth, 'realm', realm)
1128
1058
if auth.get('password',None) is None:
1129
auth['password'] = self.get_password(auth['user'],
1132
# Put useful info into auth
1059
password = self.get_password(auth)
1060
self.update_auth(auth, 'password', password)
1134
self.update_auth(auth, 'scheme', scheme)
1135
1063
if req_auth.get('algorithm', None) is not None:
1136
1064
self.update_auth(auth, 'algorithm', req_auth.get('algorithm'))
1137
self.update_auth(auth, 'realm', realm)
1138
1065
nonce = req_auth['nonce']
1139
1066
if auth.get('nonce', None) != nonce:
1140
1067
# A new nonce, never used
1316
1243
connection=ConnectionHandler,
1317
1244
redirect=HTTPRedirectHandler,
1318
1245
error=HTTPErrorProcessor,):
1319
self.password_manager = PasswordManager()
1320
1246
self._opener = urllib2.build_opener( \
1321
1247
connection, redirect, error,
1322
ProxyHandler(self.password_manager),
1323
HTTPBasicAuthHandler(self.password_manager),
1324
HTTPDigestAuthHandler(self.password_manager),
1325
ProxyBasicAuthHandler(self.password_manager),
1326
ProxyDigestAuthHandler(self.password_manager),
1249
HTTPBasicAuthHandler(),
1250
HTTPDigestAuthHandler(),
1251
ProxyBasicAuthHandler(),
1252
ProxyDigestAuthHandler(),
1329
1255
HTTPDefaultErrorHandler,