Rev 2423: Implement http proxy basic authentication. in http://bazaar.launchpad.net/~bzr/bzr/bzr.http.auth
Vincent Ladeuil
v.ladeuil+lp at free.fr
Tue Apr 17 23:07:20 BST 2007
At http://bazaar.launchpad.net/~bzr/bzr/bzr.http.auth
------------------------------------------------------------
revno: 2423
revision-id: v.ladeuil+lp at free.fr-20070417220718-kce3mj0wn8hi8m02
parent: v.ladeuil+lp at free.fr-20070417103339-3kywr38d0p50czrw
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: bzr.http.auth
timestamp: Wed 2007-04-18 00:07:18 +0200
message:
Implement http proxy basic authentication.
* bzrlib/transport/http/_urllib2_wrappers.py:
(Request.set_proxy_auth): New method.
(extract_credentials): Moved from
HttpTransport_urllib._extract_auth.
(ProxyHandler.__init__): We need a password_manager for
authentication.
(ProxyHandler.set_proxy): Don't add the auth header, the
ProxyHandlers will do it later.
(ProxyBasicAuthHandler): New class. Handle the http basic
authentication for proxy.
(Opener.__init__): Enable ProxyBasicAuthHandler.
* bzrlib/transport/http/_urllib.py:
(HttpTransport_urllib._extract_auth): Moved to
_urllib2_wrappers.extract_credentials.
* bzrlib/tests/test_http.py:
(TestHttpProxyWhiteBox.test_empty_pass,
TestHttpProxyWhiteBox.test_user_pass): Deleted. Euivalent tests
exists in TestHTTPProxyBasicAuth now.
* bzrlib/tests/HTTPTestUtil.py:
(BasicAuthRequestHandler): Fix inheritance.
(ProxyBasicAuthRequestHandler): Force the use of
FakeProxyRequestHandler.translate_path. This is a bit ugly :-/
modified:
bzrlib/tests/HTTPTestUtil.py HTTPTestUtil.py-20050914180604-247d3aafb7a43343
bzrlib/tests/test_http.py testhttp.py-20051018020158-b2eef6e867c514d9
bzrlib/transport/http/_urllib.py _urlgrabber.py-20060113083826-0bbf7d992fbf090c
bzrlib/transport/http/_urllib2_wrappers.py _urllib2_wrappers.py-20060913231729-ha9ugi48ktx481ao-1
-------------- next part --------------
=== modified file 'bzrlib/tests/HTTPTestUtil.py'
--- a/bzrlib/tests/HTTPTestUtil.py 2007-04-17 10:33:39 +0000
+++ b/bzrlib/tests/HTTPTestUtil.py 2007-04-17 22:07:18 +0000
@@ -367,14 +367,8 @@
return expected_password is not None and password == expected_password
-class BasicAuthRequestHandler(AbstractBasicAuthRequestHandler,
- FakeProxyRequestHandler):
- """Requires a basic authentication to process requests.
-
- Note: Each of the inherited request handler overrides
- different parts of processing in a compatible way, so it is
- okay to inherit from both.
- """
+class BasicAuthRequestHandler(AbstractBasicAuthRequestHandler):
+ """Requires a basic authentication to process requests"""
_auth_header_sent = 'WWW-Authenticate'
_auth_header_recv = 'Authorization'
@@ -392,15 +386,17 @@
FakeProxyRequestHandler):
"""Requires a basic authentication to proxy requests.
- Note: Each of the inherited request handler overrides
- different parts of processing in a compatible way, so it is
- okay to inherit from both.
+ Note: We have to override translate_path because otherwise
+ AbstractBasicAuthRequestHandler.translate_path take
+ precedence.
"""
_auth_header_sent = 'Proxy-Authenticate'
_auth_header_recv = 'Proxy-Authorization'
_auth_error_code = 407
+ translate_path = FakeProxyRequestHandler.translate_path
+
class ProxyBasicAuthHTTPServer(AuthHTTPServer):
"""An HTTP server requiring basic authentication"""
=== modified file 'bzrlib/tests/test_http.py'
--- a/bzrlib/tests/test_http.py 2007-04-17 10:33:39 +0000
+++ b/bzrlib/tests/test_http.py 2007-04-17 22:07:18 +0000
@@ -71,6 +71,11 @@
_urllib2_wrappers,
)
from bzrlib.transport.http._urllib import HttpTransport_urllib
+from bzrlib.transport.http._urllib2_wrappers import (
+ PasswordManager,
+ ProxyHandler,
+ Request,
+ )
class FakeManager(object):
@@ -708,7 +713,7 @@
class TestHttpProxyWhiteBox(TestCase):
"""Whitebox test proxy http authorization.
- These tests concern urllib implementation only.
+ Only the urllib implementation is tested here.
"""
def setUp(self):
@@ -727,8 +732,8 @@
osutils.set_or_unset_env(name, value)
def _proxied_request(self):
- handler = _urllib2_wrappers.ProxyHandler()
- request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
+ handler = ProxyHandler(PasswordManager())
+ request = Request('GET','http://baz/buzzle')
handler.set_proxy(request, 'http')
return request
@@ -737,17 +742,6 @@
request = self._proxied_request()
self.assertFalse(request.headers.has_key('Proxy-authorization'))
- def test_empty_pass(self):
- self._install_env({'http_proxy': 'http://joe@bar.com'})
- request = self._proxied_request()
- self.assertEqual('Basic ' + 'joe:'.encode('base64').strip(),
- request.headers['Proxy-authorization'])
- def test_user_pass(self):
- self._install_env({'http_proxy': 'http://joe:foo@bar.com'})
- request = self._proxied_request()
- self.assertEqual('Basic ' + 'joe:foo'.encode('base64').strip(),
- request.headers['Proxy-authorization'])
-
def test_invalid_proxy(self):
"""A proxy env variable without scheme"""
self._install_env({'http_proxy': 'host:1234'})
@@ -1002,10 +996,10 @@
"""Tests redirections for pycurl implementation"""
-class RedirectedRequest(_urllib2_wrappers.Request):
+class RedirectedRequest(Request):
"""Request following redirections"""
- init_orig = _urllib2_wrappers.Request.__init__
+ init_orig = Request.__init__
def __init__(self, method, url, *args, **kwargs):
RedirectedRequest.init_orig(self, method, url, args, kwargs)
=== modified file 'bzrlib/transport/http/_urllib.py'
--- a/bzrlib/transport/http/_urllib.py 2007-04-15 15:57:08 +0000
+++ b/bzrlib/transport/http/_urllib.py 2007-04-17 22:07:18 +0000
@@ -30,6 +30,7 @@
from bzrlib.transport.http._urllib2_wrappers import (
Opener,
Request,
+ extract_credentials,
)
@@ -59,7 +60,7 @@
# info in the urls. So we handle them separatly.
# Note: we don't need to when cloning because it was
# already done.
- clean_base, user, password = self._extract_auth(base)
+ clean_base, user, password = extract_credentials(base)
super(HttpTransport_urllib, self).__init__(clean_base,
from_transport)
self._connection = None
@@ -94,31 +95,6 @@
host=self._host)
pm.add_password(None, authuri, self._user, self._password)
- def _extract_auth(self, url):
- """Extracts authentication information from url.
-
- Get user and password from url of the form: http://user:pass@host/path
- :returns: (clean_url, user, password)
- """
- scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
-
- if '@' in netloc:
- auth, netloc = netloc.split('@', 1)
- if ':' in auth:
- user, password = auth.split(':', 1)
- else:
- user, password = auth, None
- user = urllib.unquote(user)
- if password is not None:
- password = urllib.unquote(password)
- else:
- user = None
- password = None
-
- url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
-
- return url, user, password
-
def _perform(self, request):
"""Send the request to the server and handles common errors.
=== modified file 'bzrlib/transport/http/_urllib2_wrappers.py'
--- a/bzrlib/transport/http/_urllib2_wrappers.py 2007-04-15 15:57:08 +0000
+++ b/bzrlib/transport/http/_urllib2_wrappers.py 2007-04-17 22:07:18 +0000
@@ -157,16 +157,51 @@
# Unless told otherwise, redirections are not followed
self.follow_redirections = False
self.set_auth(None, None, None) # Until the first 401
+ self.set_proxy_auth(None, None, None) # Until the first 407
def set_auth(self, auth_scheme, user, password=None):
self.auth_scheme = auth_scheme
self.user = user
self.password = password
+ # FIXME: this is not called, we incur a rountrip for each
+ # request.
+ def set_proxy_auth(self, auth_scheme, user, password=None):
+ self.proxy_auth_scheme = auth_scheme
+ self.proxy_user = user
+ self.proxy_password = password
+
def get_method(self):
return self.method
+def extract_credentials(url):
+ """Extracts credentials information from url.
+
+ Get user and password from url of the form: http://user:pass@host/path
+ :returns: (clean_url, user, password)
+ """
+ scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
+
+ if '@' in netloc:
+ auth, netloc = netloc.split('@', 1)
+ if ':' in auth:
+ user, password = auth.split(':', 1)
+ else:
+ user, password = auth, None
+ user = urllib.unquote(user)
+ if password is not None:
+ password = urllib.unquote(password)
+ else:
+ user = None
+ password = None
+
+ # Build the clean url
+ clean_url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
+
+ return clean_url, user, password
+
+
# The urlib2.xxxAuthHandler handle the authentication of the
# requests, to do that, they need an urllib2 PasswordManager *at
# build time*. We also need one to reuse the passwords already
@@ -594,8 +629,9 @@
handler_order = 100
_debuglevel = DEBUG
- def __init__(self, proxies=None):
+ def __init__(self, password_manager, proxies=None):
urllib2.ProxyHandler.__init__(self, proxies)
+ self.password_manager = password_manager
# First, let's get rid of urllib2 implementation
for type, proxy in self.proxies.items():
if self._debuglevel > 0:
@@ -675,16 +711,13 @@
raise errors.InvalidURL(proxy,
'Invalid syntax in proxy env variable')
elif '@' in host:
- user_pass, host = host.split('@', 1)
- if ':' in user_pass:
- user, password = user_pass.split(':', 1)
- else:
- user = user_pass
- password = ''
- user_pass = '%s:%s' % (urllib.unquote(user),
- urllib.unquote(password))
- user_pass = user_pass.encode('base64').strip()
- request.add_header('Proxy-authorization', 'Basic ' + user_pass)
+ # Extract credentials from the url and store them in
+ # the password manager so that the
+ # ProxyxxxAuthHandler can use them later.
+ clean_url, user, password = extract_credentials(proxy)
+ if user and password is not None: # '' is a valid password
+ pm = self.password_manager
+ pm.add_password(None, self.base, self._user, self._password)
host = urllib.unquote(host)
request.set_proxy(host, type)
if self._debuglevel > 0:
@@ -739,6 +772,55 @@
return response
+class ProxyBasicAuthHandler(urllib2.ProxyBasicAuthHandler):
+ """Custom proxy basic authentication handler.
+
+ Send the authentication preventively to avoid the roundtrip
+ associated with the 407 error.
+ """
+
+ def get_auth(self, user, password):
+ raw = '%s:%s' % (user, password)
+ auth = 'Basic ' + raw.encode('base64').strip()
+ return auth
+
+ def set_auth(self, request):
+ """Add the authentication header if needed.
+
+ All required informations should be part of the request.
+ """
+ if request.proxy_password is not None:
+ request.add_header(self.auth_header,
+ self.get_auth(request.proxy_user,
+ request.proxy_password))
+
+ def http_request(self, request):
+ """Insert an authentication header if information is available"""
+ if request.proxy_auth_scheme == 'basic' \
+ and request.proxy_password is not None:
+ self.set_auth(request)
+ return request
+
+ https_request = http_request # FIXME: Need test
+
+ def http_error_407(self, req, fp, code, msg, headers):
+ """Trap the 401 to gather the auth type."""
+ response = urllib2.ProxyBasicAuthHandler.http_error_407(self, req, fp,
+ code, msg,
+ headers)
+ if response is not None:
+ # We capture the auth_scheme to be able to send the
+ # authentication header with the next requests
+ # without waiting for a 407 error.
+ # The urllib2.ProxyBasicAuthHandler will return a
+ # response *only* if the basic authentication
+ # succeeds. If another scheme is used or the
+ # authentication fails, the response will be None.
+ req.proxy_auth_scheme = 'basic'
+
+ return response
+
+
class HTTPErrorProcessor(urllib2.HTTPErrorProcessor):
"""Process HTTP error responses.
@@ -797,10 +879,10 @@
# commented out below
self._opener = urllib2.build_opener( \
connection, redirect, error,
- ProxyHandler,
+ ProxyHandler(self.password_manager),
HTTPBasicAuthHandler(self.password_manager),
#urllib2.HTTPDigestAuthHandler(self.password_manager),
- #urllib2.ProxyBasicAuthHandler,
+ ProxyBasicAuthHandler(self.password_manager),
#urllib2.ProxyDigestAuthHandler,
HTTPHandler,
HTTPSHandler,
More information about the bazaar-commits
mailing list