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
193
189
# Unless told otherwise, redirections are not followed
194
190
self.follow_redirections = False
195
191
# auth and proxy_auth are dicts containing, at least
196
# (scheme, url, realm, user, password).
192
# (scheme, host, port, realm, user, password, protocol, path).
197
193
# The dict entries are mostly handled by the AuthHandler.
198
194
# Some authentication schemes may add more entries.
240
236
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
239
class ConnectionHandler(urllib2.BaseHandler):
292
240
"""Provides connection-sharing by pre-processing requests.
737
685
handler_order = 100
738
686
_debuglevel = DEBUG
740
def __init__(self, password_manager, proxies=None):
688
def __init__(self, proxies=None):
741
689
urllib2.ProxyHandler.__init__(self, proxies)
742
self.password_manager = password_manager
743
690
# First, let's get rid of urllib2 implementation
744
691
for type, proxy in self.proxies.items():
745
692
if self._debuglevel > 0:
783
730
def proxy_bypass(self, host):
784
731
"""Check if host should be proxied or not"""
785
no_proxy = self.get_proxy_env_var('no', None)
732
no_proxy = self.get_proxy_env_var('no', default_to=None)
786
733
if no_proxy is None:
788
735
hhost, hport = urllib.splitport(host)
814
761
# grok user:password@host:port as well as
815
762
# 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)
764
(scheme, user, password,
765
host, port, path) = transport.ConnectedTransport._split_url(proxy)
821
767
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)
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)
890
830
# - auth_required_header: the header received from the server
891
831
# - 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
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.
897
837
self._retry_count = None
899
839
def update_auth(self, auth, key, value):
913
853
# Don't try to authenticate endlessly
914
854
if self._retry_count is None:
915
# The retry being recusrsive calls, None identify the first try
916
self._retry_count = 0
855
# The retry being recusrsive calls, None identify the first retry
856
self._retry_count = 1
918
858
self._retry_count += 1
919
859
if self._retry_count > self._max_retry:
935
875
if self.auth_match(server_header, auth):
936
876
# auth_match may have modified auth (by adding the
937
877
# password or changing the realm, for example)
938
if request.get_header(self.auth_header, None) is not None \
939
and not auth['modified']:
878
if (request.get_header(self.auth_header, None) is not None
879
and not auth['modified']):
940
880
# We already tried that, give up
998
938
# It may happen that we need to reconnect later, let's be ready
999
939
self._retry_count = None
1001
def get_password(self, user, authuri, realm=None):
941
def get_user_password(self, auth):
1002
942
"""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
943
auth_conf = config.AuthenticationConfig()
945
password = auth['password']
946
realm = auth['realm']
949
user = auth.get_user(auth['protocol'], auth['host'],
950
port=auth['port'], path=auth['path'],
953
# Default to local user
954
user = getpass.getuser()
1008
956
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)
957
password = auth_conf.get_password(
958
auth['protocol'], auth['host'], user, port=auth['port'],
959
path=auth['path'], realm=realm,
960
prompt=self.build_password_prompt(auth))
962
return user, password
964
def _build_password_prompt(self, auth):
965
"""Build a prompt taking the protocol used into account.
967
The AuthHandler is used by http and https, we want that information in
968
the prompt, so we build the prompt from the authentication dict which
969
contains all the needed parts.
971
Also, hhtp and proxy AuthHandlers present different prompts to the
972
user. The daughter classes hosuld implements a public
973
build_password_prompt using this method.
975
prompt = '%s' % auth['protocol'].upper() + ' %(user)s@%(host)s'
976
realm = auth['realm']
977
if realm is not None:
978
prompt += ", Realm: '%s'" % realm
979
prompt += ' password'
1022
982
def http_request(self, request):
1023
983
"""Insert an authentication header if information is available"""
1056
1016
# Put useful info into auth
1057
1017
self.update_auth(auth, 'scheme', scheme)
1058
1018
self.update_auth(auth, 'realm', realm)
1059
if auth.get('password',None) is None:
1060
password = self.get_password(auth['user'], auth['authuri'],
1019
if auth['user'] is None or auth['password'] is None:
1020
user, password = self.get_user_password(auth)
1021
self.update_auth(auth, 'user', user)
1062
1022
self.update_auth(auth, 'password', password)
1063
1023
return match is not None
1120
1080
realm = req_auth.get('realm', None)
1121
if auth.get('password',None) is None:
1122
auth['password'] = self.get_password(auth['user'],
1125
1081
# Put useful info into auth
1082
self.update_auth(auth, 'scheme', scheme)
1083
self.update_auth(auth, 'realm', realm)
1084
if auth['user'] is None or auth['password'] is None:
1085
user, password = self.get_user_password(auth)
1086
self.update_auth(auth, 'user', user)
1087
self.update_auth(auth, 'password', password)
1127
self.update_auth(auth, 'scheme', scheme)
1128
1090
if req_auth.get('algorithm', None) is not None:
1129
1091
self.update_auth(auth, 'algorithm', req_auth.get('algorithm'))
1130
self.update_auth(auth, 'realm', realm)
1131
1092
nonce = req_auth['nonce']
1132
1093
if auth.get('nonce', None) != nonce:
1133
1094
# A new nonce, never used
1200
1160
"""Set the auth params for the request"""
1201
1161
request.auth = auth
1163
def build_password_prompt(self, auth):
1164
return self._build_password_prompt(auth)
1203
1166
def http_error_401(self, req, fp, code, msg, headers):
1204
1167
return self.auth_required(req, headers)
1227
1189
"""Set the auth params for the request"""
1228
1190
request.proxy_auth = auth
1192
def build_password_prompt(self, auth):
1193
prompt = self._build_password_prompt(auth)
1194
prompt = 'Proxy ' + prompt
1230
1197
def http_error_407(self, req, fp, code, msg, headers):
1231
1198
return self.auth_required(req, headers)
1307
1274
connection=ConnectionHandler,
1308
1275
redirect=HTTPRedirectHandler,
1309
1276
error=HTTPErrorProcessor,):
1310
self.password_manager = PasswordManager()
1311
1277
self._opener = urllib2.build_opener( \
1312
1278
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),
1280
HTTPBasicAuthHandler(),
1281
HTTPDigestAuthHandler(),
1282
ProxyBasicAuthHandler(),
1283
ProxyDigestAuthHandler(),
1320
1286
HTTPDefaultErrorHandler,