Rev 2431: Implement digest authentication. Test suite passes. Tested against apache-2.x. in http://bazaar.launchpad.net/~bzr/bzr/bzr.http.auth

Vincent Ladeuil v.ladeuil+lp at free.fr
Sat Apr 21 21:39:09 BST 2007


At http://bazaar.launchpad.net/~bzr/bzr/bzr.http.auth

------------------------------------------------------------
revno: 2431
revision-id: v.ladeuil+lp at free.fr-20070421203906-hta5jt0nmauyl9qy
parent: v.ladeuil+lp at free.fr-20070421113113-b0br4norqbtlx1o2
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: bzr.http.auth
timestamp: Sat 2007-04-21 22:39:06 +0200
message:
  Implement digest authentication. Test suite passes. Tested against apache-2.x.
  
  * bzrlib/transport/http/_urllib2_wrappers.py:
  (AbstractAuthHandler.auth_required): Do not attempt to
  authenticate if we don't have a user. Rework the detection of
  already tried authentications. Avoid building the auth header two
  times, save the auth info at the right places.
  (AbstractAuthHandler.build_auth_header): Add a request parameter
  for digest needs.
  (BasicAuthHandler.auth_match): Simplify.
  (get_digest_algorithm_impls, DigestAuthHandler): Implements client
  digest authentication. MD5 and SHA algorithms are supported. Only
  'auth' qop is suppoted.
  (HTTPBasicAuthHandler, ProxyBasicAuthHandler): Renamed HTTPHandler
  and ProxyAuthHandler respectively.
  (HTTPBasicAuthHandler, ProxyBasicAuthHandler,
  HTTPDigestAuthHandler, ProxyDigestAuthHandler): New classes
  implementing the combinations between (http, proxy) and (basic,
  digest).
  (Opener.__init__): No more handlers in comment ! One TODO less !
  
  * bzrlib/transport/http/_urllib.py:
  (HttpTransport_urllib.__init__): self.base is not suitable for an
  auth uri, it can contain decorators.
  
  * bzrlib/tests/test_http.py:
  (TestAuth.test_no_user): New test to check the behavior with no
  user when authentication is required.
  
  * bzrlib/tests/HTTPTestUtil.py:
  (DigestAuthRequestHandler.authorized): Delegate most of the work
  to the server that control the needed persistent infos.
  (AuthServer): Define an auth_relam attribute.
  (DigestAuthServer): Implement a first version of digest
  authentication. Only the MD5 algorithm and the 'auth' qop are
  supported so far.
  (HTTPAuthServer.init_http_auth): New method to simplify
  the [http|proxy], [basic|digest] server combinations writing.
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  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 'NEWS'
--- a/NEWS	2007-04-21 11:31:13 +0000
+++ b/NEWS	2007-04-21 20:39:06 +0000
@@ -5,6 +5,9 @@
     * Merge directives can now be supplied as input to `merge` and `pull`,
       like bundles can.  (Aaron Bentley)
 
+    * digest authentication is now supported for proxy and http.
+      (Vincent Ladeuil).
+
   INTERNALS:
 
     * bzrlib API compatability with 0.8 has been dropped, cleaning up some
@@ -92,6 +95,9 @@
       basic authentication should be used, but plug the security
       hole. (Vincent Ladeuil)
 
+    * Handle http and proxy digest authentication.
+      (Vincent Ladeuil, #94034).
+
   TESTING:
 
     * Added ``bzrlib.strace.strace`` which will strace a single callable and

=== modified file 'bzrlib/tests/HTTPTestUtil.py'
--- a/bzrlib/tests/HTTPTestUtil.py	2007-04-21 11:31:13 +0000
+++ b/bzrlib/tests/HTTPTestUtil.py	2007-04-21 20:39:06 +0000
@@ -16,9 +16,12 @@
 
 from cStringIO import StringIO
 import errno
+import md5
 from SimpleHTTPServer import SimpleHTTPRequestHandler
 import re
+import sha
 import socket
+import time
 import urllib2
 import urlparse
 
@@ -332,38 +335,54 @@
         if tcs.auth_scheme != 'basic':
             return False
 
-        auth_header = self.headers.get(tcs.auth_header_recv)
-        if auth_header and auth_header.lower().startswith('basic '):
-            raw_auth = auth_header[len('Basic '):]
-            user, password = raw_auth.decode('base64').split(':')
-            return tcs.authorized(user, password)
+        auth_header = self.headers.get(tcs.auth_header_recv, None)
+        if auth_header:
+            scheme, raw_auth = auth_header.split(' ', 1)
+            if scheme.lower() == tcs.auth_scheme:
+                user, password = raw_auth.decode('base64').split(':')
+                return tcs.authorized(user, password)
 
         return False
 
     def send_header_auth_reqed(self):
-        self.send_header(self.server.test_case_server.auth_header_sent,
-                         'Basic realm="Thou should not pass"')
-
+        tcs = self.server.test_case_server
+        self.send_header(tcs.auth_header_sent,
+                         'Basic realm="%s"' % tcs.auth_realm)
+
+
+# FIXME: We should send an Authentication-Info header too when
+# the autheticaion is succesful
 
 class DigestAuthRequestHandler(AuthRequestHandler):
-    """Implements the digest authentication of a request"""
+    """Implements the digest authentication of a request.
+
+    We need persistence for some attributes and that can't be
+    achieved here since we get instantiated for each request. We
+    rely on the DigestAuthServer to take care of them.
+    """
 
     def authorized(self):
         tcs = self.server.test_case_server
         if tcs.auth_scheme != 'digest':
             return False
 
-        auth_header = self.headers.get(tcs.auth_header_recv)
-        if auth_header and auth_header.lower().startswith('digest '):
-            raw_auth = auth_header[len('Basic '):]
-            user, password = raw_auth.decode('base64').split(':')
-            return tcs.authorized(user, password)
+        auth_header = self.headers.get(tcs.auth_header_recv, None)
+        if auth_header is None:
+            return False
+        scheme, auth = auth_header.split(None, 1)
+        if scheme.lower() == tcs.auth_scheme:
+            auth_dict = urllib2.parse_keqv_list(urllib2.parse_http_list(auth))
+
+            return tcs.digest_authorized(auth_dict, self.command)
 
         return False
 
     def send_header_auth_reqed(self):
-        self.send_header(self.server.test_case_server.auth_header_sent,
-                         'Basic realm="Thou should not pass"')
+        tcs = self.server.test_case_server
+        header = 'Digest realm="%s", ' % tcs.auth_realm
+        header += 'nonce="%s", algorithm=%s, qop=auth' % (tcs.auth_nonce, 'MD5')
+        self.send_header(tcs.auth_header_sent,header)
+
 
 class AuthServer(HttpServer):
     """Extends HttpServer with a dictionary of passwords.
@@ -373,13 +392,14 @@
 
     Note that no users are defined by default, so add_user should
     be called before issuing the first request.
+    """
 
-    """
     # The following attributes should be set dy daughter classes
     # and are used by AuthRequestHandler.
     auth_header_sent = None
     auth_header_recv = None
     auth_error_code = None
+    auth_realm = "Thou should not pass"
 
     def __init__(self, request_handler, auth_scheme):
         HttpServer.__init__(self, request_handler)
@@ -396,25 +416,71 @@
         self.password_of[user] = password
 
     def authorized(self, user, password):
+        """Check that the given user provided the right password"""
         expected_password = self.password_of.get(user, None)
         return expected_password is not None and password == expected_password
 
 
+class DigestAuthServer(AuthServer):
+    """A digest authentication server"""
+
+    auth_nonce = 'rRQ+Lp4uBAA=301b77beb156b6158b73dee026b8be23302292b4'
+
+    def __init__(self, request_handler, auth_scheme):
+        AuthServer.__init__(self, request_handler, auth_scheme)
+
+    def digest_authorized(self, auth, command):
+        realm = auth['realm']
+        if realm != self.auth_realm:
+            return False
+        user = auth['username']
+        if not self.password_of.has_key(user):
+            return False
+        algorithm= auth['algorithm']
+        if algorithm != 'MD5':
+            return False
+        qop = auth['qop']
+        if qop != 'auth':
+            return False
+
+        password = self.password_of[user]
+
+        # Recalculate the response_digest to compare with the one
+        # sent by the client
+        A1 = '%s:%s:%s' % (user, realm, password)
+        A2 = '%s:%s' % (command, auth['uri'])
+
+        H = lambda x: md5.new(x).hexdigest()
+        KD = lambda secret, data: H("%s:%s" % (secret, data))
+
+        nonce = auth['nonce']
+        nonce_count = int(auth['nc'], 16)
+
+        ncvalue = '%08x' % nonce_count
+
+        cnonce = auth['cnonce']
+        noncebit = '%s:%s:%s:%s:%s' % (nonce, ncvalue, cnonce, qop, H(A2))
+        response_digest = KD(H(A1), noncebit)
+
+        return response_digest == auth['response']
+
 class HTTPAuthServer(AuthServer):
     """An HTTP server requiring authentication"""
 
-    auth_header_sent = 'WWW-Authenticate'
-    auth_header_recv = 'Authorization'
-    auth_error_code = 401
+    def init_http_auth(self):
+        self.auth_header_sent = 'WWW-Authenticate'
+        self.auth_header_recv = 'Authorization'
+        self.auth_error_code = 401
 
 
 class ProxyAuthServer(AuthServer):
     """A proxy server requiring authentication"""
 
-    proxy_requests = True
-    auth_header_sent = 'Proxy-Authenticate'
-    auth_header_recv = 'Proxy-Authorization'
-    auth_error_code = 407
+    def init_proxy_auth(self):
+        self.proxy_requests = True
+        self.auth_header_sent = 'Proxy-Authenticate'
+        self.auth_header_recv = 'Proxy-Authorization'
+        self.auth_error_code = 407
 
 
 class HTTPBasicAuthServer(HTTPAuthServer):
@@ -422,26 +488,30 @@
 
     def __init__(self):
         HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
-
-
-class HTTPDigestAuthServer(HTTPAuthServer):
+        self.init_http_auth()
+
+
+class HTTPDigestAuthServer(DigestAuthServer, HTTPAuthServer):
     """An HTTP server requiring digest authentication"""
 
     def __init__(self):
-        HTTPAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
+        DigestAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
+        self.init_http_auth()
 
 
 class ProxyBasicAuthServer(ProxyAuthServer):
-    """An proxy server requiring basic authentication"""
+    """A proxy server requiring basic authentication"""
 
     def __init__(self):
         ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
-
-
-class ProxyDigestAuthServer(ProxyAuthServer):
-    """An proxy server requiring basic authentication"""
+        self.init_proxy_auth()
+
+
+class ProxyDigestAuthServer(DigestAuthServer, ProxyAuthServer):
+    """A proxy server requiring basic authentication"""
 
     def __init__(self):
         ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
+        self.init_proxy_auth()
 
 

=== modified file 'bzrlib/tests/test_http.py'
--- a/bzrlib/tests/test_http.py	2007-04-21 09:26:30 +0000
+++ b/bzrlib/tests/test_http.py	2007-04-21 20:39:06 +0000
@@ -1170,6 +1170,13 @@
         url += '%s:%s/' % (self.server.host, self.server.port)
         return url
 
+    def test_no_user(self):
+        self.server.add_user('joe', 'foo')
+        t = self.get_user_transport()
+        self.assertRaises(errors.InvalidHttpResponse, t.get, 'a')
+        # Only one 'Authentication Required' error should occur
+        self.assertEqual(1, self.server.auth_required_errors)
+
     def test_empty_pass(self):
         self.server.add_user('joe', '')
         t = self.get_user_transport('joe', '')
@@ -1231,7 +1238,7 @@
         self.server = self.get_readonly_server()
         TestAuth.setUp(self)
 
-    def get_user_transport(self, user, password=None):
+    def get_user_transport(self, user=None, password=None):
         return self._transport(self.get_user_url(user, password))
 
 
@@ -1255,7 +1262,7 @@
                                   ('b-proxied', 'contents of b\n'),
                                   ])
 
-    def get_user_transport(self, user, password=None):
+    def get_user_transport(self, user=None, password=None):
         self._install_env({'all_proxy': self.get_user_url(user, password)})
         return self._transport(self.server.get_url())
 

=== modified file 'bzrlib/transport/http/_urllib.py'
--- a/bzrlib/transport/http/_urllib.py	2007-04-20 06:50:59 +0000
+++ b/bzrlib/transport/http/_urllib.py	2007-04-21 20:39:06 +0000
@@ -64,7 +64,7 @@
             self._connection = None
             self._opener = self._opener_class()
 
-            authuri = extract_authentication_uri(self.base)
+            authuri = extract_authentication_uri(self._real_abspath(self._path))
             self._auth = {'user': user, 'password': password,
                           'authuri': authuri}
             if user and password is not None: # '' is a valid password

=== modified file 'bzrlib/transport/http/_urllib2_wrappers.py'
--- a/bzrlib/transport/http/_urllib2_wrappers.py	2007-04-21 09:26:30 +0000
+++ b/bzrlib/transport/http/_urllib2_wrappers.py	2007-04-21 20:39:06 +0000
@@ -51,12 +51,15 @@
 # ensure that.
 
 import httplib
+import md5
+import sha
 import socket
 import urllib
 import urllib2
 import urlparse
 import re
 import sys
+import time
 
 from bzrlib import __version__ as bzrlib_version
 from bzrlib import (
@@ -760,7 +763,7 @@
 
     # The following attributes should be defined by daughter
     # classes:
-    # - auth_reqed_header:  the header received from the server
+    # - auth_required_header:  the header received from the server
     # - auth_header: the header sent in the request
 
     def __init__(self, password_manager):
@@ -775,21 +778,32 @@
         :param headers: The headers for the authentication error response.
         :return: None or the response for the authenticated request.
         """
-        server_header = headers.get(self.auth_reqed_header, None)
+        server_header = headers.get(self.auth_required_header, None)
         if server_header is None:
             # The http error MUST have the associated
             # header. This must never happen in production code.
-            raise KeyError('%s not found' % self.auth_reqed_header)
-
-        auth = self.get_auth(request)
+            raise KeyError('%s not found' % self.auth_required_header)
+
+        auth = self.get_auth(request).copy()
+        if auth.get('user', None) is None:
+            # Without a known user, we can't authenticate
+            return None
+
         if self.auth_match(server_header, auth):
-            client_header = self.build_auth_header(auth)
-            if client_header == request.get_header(self.auth_header, None):
+            # auth_match may have modified auth (by adding the
+            # password or changing the realm, for example)
+            old = self.get_auth(request)
+            if request.get_header(self.auth_header, None) is not None \
+                    and old.get('user') == auth.get('user') \
+                    and old.get('realm') == auth.get('realm') \
+                    and old.get('password') == auth.get('password'):
                 # We already tried that, give up
                 return None
 
-            self.add_auth_header(request, client_header)
-            request.add_unredirected_header(self.auth_header, client_header)
+            # We will try to authenticate, save the auth so that
+            # the build_auth_header that will be called during
+            # parent.open use the right values
+            self.set_auth(request, auth)
             response = self.parent.open(request)
             if response:
                 self.auth_successful(request, response, auth)
@@ -803,15 +817,8 @@
         # 'Proxy Authentication Required' error.
         return None
 
-    def get_auth(self, request):
-        """Get the auth params from the request"""
-        raise NotImplementedError(self.get_auth)
-
-    def set_auth(self, request, auth):
-        """Set the auth params for the request"""
-        raise NotImplementedError(self.set_auth)
-
     def add_auth_header(self, request, header):
+        """Add the authentication header to the request"""
         request.add_unredirected_header(self.auth_header, header)
 
     def auth_match(self, header, auth):
@@ -827,10 +834,11 @@
         """
         raise NotImplementedError(self.auth_match)
 
-    def build_auth_header(self, auth):
+    def build_auth_header(self, auth, request):
         """Build the value of the header used to authenticate.
 
         :param auth: The auth parameters needed to build the header.
+        :param request: The request needing authentication.
 
         :return: None or header.
         """
@@ -875,89 +883,213 @@
         """Insert an authentication header if information is available"""
         auth = self.get_auth(request)
         if self.auth_params_reusable(auth):
-            self.add_auth_header(request, self.build_auth_header(auth))
+            self.add_auth_header(request, self.build_auth_header(auth, request))
         return request
 
     https_request = http_request # FIXME: Need test
 
 
-class AbstractBasicAuthHandler(AbstractAuthHandler):
-    """A custom basic auth handler."""
-
-    auth_regexp = re.compile('[ \t]*([^ \t]+)[ \t]+realm="([^"]*)"', re.I)
-
-    def build_auth_header(self, auth):
+class BasicAuthHandler(AbstractAuthHandler):
+    """A custom basic authentication handler."""
+
+    auth_regexp = re.compile('realm="([^"]*)"', re.I)
+
+    def build_auth_header(self, auth, request):
         raw = '%s:%s' % (auth['user'], auth['password'])
         auth_header = 'Basic ' + raw.encode('base64').strip()
         return auth_header
 
     def auth_match(self, header, auth):
-        match = self.auth_regexp.search(header)
+        scheme, raw_auth = header.split(None, 1)
+        scheme = scheme.lower()
+        if scheme != 'basic':
+            return False
+
+        match = self.auth_regexp.search(raw_auth)
         if match:
-            scheme, auth['realm'] = match.groups()
-            auth['scheme'] = scheme.lower()
-            if auth['scheme'] != 'basic':
-                match = None
-            else:
-                if auth.get('password',None) is None:
-                    auth['password'] = self.get_password(auth['user'],
-                                                         auth['authuri'],
-                                                         auth['realm'])
+            realm = match.groups()
+            if scheme != 'basic':
+                return False
 
+            # Put useful info into auth
+            auth['scheme'] = scheme
+            auth['realm'] = realm
+            if auth.get('password',None) is None:
+                auth['password'] = self.get_password(auth['user'],
+                                                     auth['authuri'],
+                                                     auth['realm'])
         return match is not None
 
     def auth_params_reusable(self, auth):
         # If the auth scheme is known, it means a previous
         # authentication was succesful, all information is
         # available, no further checks are needed.
-        return auth.get('scheme',None) == 'basic'
-
-
-class HTTPBasicAuthHandler(AbstractBasicAuthHandler):
-    """Custom basic authentication handler.
+        return auth.get('scheme', None) == 'basic'
+
+
+def get_digest_algorithm_impls(algorithm):
+    H = None
+    if algorithm == 'MD5':
+        H = lambda x: md5.new(x).hexdigest()
+    elif algorithm == 'SHA':
+        H = lambda x: sha.new(x).hexdigest()
+    if H is not None:
+        KD = lambda secret, data: H("%s:%s" % (secret, data))
+    return H, KD
+
+
+class DigestAuthHandler(AbstractAuthHandler):
+    """A custom digest authentication handler."""
+
+    def auth_params_reusable(self, auth):
+        # If the auth scheme is known, it means a previous
+        # authentication was succesful, all information is
+        # available, no further checks are needed.
+        return auth.get('scheme', None) == 'digest'
+
+    def auth_match(self, header, auth):
+        scheme, raw_auth = header.split(None, 1)
+        scheme = scheme.lower()
+        if scheme != 'digest':
+            return False
+
+        # Put the requested authentication info into a dict
+        req_auth = urllib2.parse_keqv_list(urllib2.parse_http_list(raw_auth))
+
+        # Check that we can handle that authentication
+        qop = req_auth.get('qop', None)
+        if qop != 'auth': # No auth-int so far
+            return False
+
+        nonce = req_auth.get('nonce', None)
+        old_nonce = auth.get('nonce', None)
+        if nonce and old_nonce and nonce == old_nonce:
+            # We already tried that
+            return False
+
+        algorithm = req_auth.get('algorithm', 'MD5')
+        H, KD = get_digest_algorithm_impls(algorithm)
+        if H is None:
+            return False
+
+        realm = req_auth.get('realm', None)
+        if auth.get('password',None) is None:
+            auth['password'] = self.get_password(auth['user'],
+                                                 auth['authuri'],
+                                                 realm)
+        # Put useful info into auth
+        try:
+            auth['scheme'] = scheme
+            auth['algorithm'] = algorithm
+            auth['realm'] = req_auth['realm']
+            auth['nonce'] = req_auth['nonce']
+            auth['qop'] = qop
+            auth['opaque'] = req_auth.get('opaque', None)
+        except KeyError:
+            return False
+
+        return True
+
+    def build_auth_header(self, auth, request):
+        uri = request.get_selector()
+        A1 = '%s:%s:%s' % (auth['user'], auth['realm'], auth['password'])
+        A2 = '%s:%s' % (request.get_method(), uri)
+        nonce = auth['nonce']
+        qop = auth['qop']
+
+        H, KD = get_digest_algorithm_impls(auth['algorithm'])
+        nonce_count = auth.get('nonce_count',0)
+        nonce_count += 1
+        ncvalue = '%08x' % nonce_count
+        cnonce = sha.new("%s:%s:%s:%s" % (nonce_count, nonce,
+                                          time.ctime(), urllib2.randombytes(8))
+                         ).hexdigest()[:16]
+        noncebit = '%s:%s:%s:%s:%s' % (nonce, ncvalue, cnonce, qop, H(A2))
+        response_digest = KD(H(A1), noncebit)
+
+        header = 'Digest '
+        header += 'username="%s", realm="%s", nonce="%s",' % (auth['user'],
+                                                             auth['realm'],
+                                                             nonce)
+        header += ' uri="%s", response="%s"' % (uri, response_digest)
+        opaque = auth.get('opaque', None)
+        if opaque:
+            header += ', opaque="%s"' % opaque
+        header += ', algorithm="%s"' % auth['algorithm']
+        header += ', qop="%s", nc="%s", cnonce="%s"' % (qop, ncvalue, cnonce)
+
+        # We have used the nonce once more, update the count
+        auth['nonce_count'] = nonce_count
+
+        return header
+
+
+class HTTPAuthHandler(AbstractAuthHandler):
+    """Custom http authentication handler.
 
     Send the authentication preventively to avoid the roundtrip
-    associated with the 401 error.
+    associated with the 401 error and keep the revelant info in
+    the auth request attribute.
     """
 
     password_prompt = 'HTTP %(user)s@%(host)s%(realm)s password'
-    auth_reqed_header = 'www-authenticate'
+    auth_required_header = 'www-authenticate'
     auth_header = 'Authorization'
 
     def get_auth(self, request):
+        """Get the auth params from the request"""
         return request.auth
 
     def set_auth(self, request, auth):
+        """Set the auth params for the request"""
         request.auth = auth
 
     def http_error_401(self, req, fp, code, msg, headers):
         return self.auth_required(req, headers)
 
 
-class ProxyBasicAuthHandler(AbstractBasicAuthHandler):
-    """Custom proxy basic authentication handler.
+class ProxyAuthHandler(AbstractAuthHandler):
+    """Custom proxy authentication handler.
 
     Send the authentication preventively to avoid the roundtrip
-    associated with the 407 error.
+    associated with the 407 error and keep the revelant info in
+    the proxy_auth request attribute..
     """
 
     password_prompt = 'Proxy %(user)s@%(host)s%(realm)s password'
-    auth_reqed_header = 'proxy-authenticate'
+    auth_required_header = 'proxy-authenticate'
     # FIXME: the correct capitalization is Proxy-Authorization,
     # but python-2.4 urllib2.Request insist on using capitalize()
     # instead of title().
     auth_header = 'Proxy-authorization'
 
     def get_auth(self, request):
+        """Get the auth params from the request"""
         return request.proxy_auth
 
     def set_auth(self, request, auth):
+        """Set the auth params for the request"""
         request.proxy_auth = auth
 
     def http_error_407(self, req, fp, code, msg, headers):
         return self.auth_required(req, headers)
 
 
+class HTTPBasicAuthHandler(BasicAuthHandler, HTTPAuthHandler):
+    """Custom http basic authentication handler"""
+
+
+class ProxyBasicAuthHandler(BasicAuthHandler, ProxyAuthHandler):
+    """Custom proxy basic authentication handler"""
+
+
+class HTTPDigestAuthHandler(DigestAuthHandler, HTTPAuthHandler):
+    """Custom http basic authentication handler"""
+
+
+class ProxyDigestAuthHandler(DigestAuthHandler, ProxyAuthHandler):
+    """Custom proxy basic authentication handler"""
+
 
 class HTTPErrorProcessor(urllib2.HTTPErrorProcessor):
     """Process HTTP error responses.
@@ -1013,15 +1145,13 @@
                  redirect=HTTPRedirectHandler,
                  error=HTTPErrorProcessor,):
         self.password_manager = PasswordManager()
-        # TODO: Implements the necessary wrappers for the handlers
-        # commented out below
         self._opener = urllib2.build_opener( \
             connection, redirect, error,
             ProxyHandler(self.password_manager),
             HTTPBasicAuthHandler(self.password_manager),
-            #urllib2.HTTPDigestAuthHandler(self.password_manager),
+            HTTPDigestAuthHandler(self.password_manager),
             ProxyBasicAuthHandler(self.password_manager),
-            #urllib2.ProxyDigestAuthHandler,
+            ProxyDigestAuthHandler(self.password_manager),
             HTTPHandler,
             HTTPSHandler,
             HTTPDefaultErrorHandler,



More information about the bazaar-commits mailing list