Rev 2429: Refactor proxy and auth test classes. Tests failing for digest auth. in http://bazaar.launchpad.net/~bzr/bzr/bzr.http.auth

Vincent Ladeuil v.ladeuil+lp at free.fr
Sat Apr 21 10:26:32 BST 2007


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

------------------------------------------------------------
revno: 2429
revision-id: v.ladeuil+lp at free.fr-20070421092630-ty7a90wrx4v2x5k8
parent: v.ladeuil+lp at free.fr-20070420113620-lawri7ftno7lbycw
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: bzr.http.auth
timestamp: Sat 2007-04-21 11:26:30 +0200
message:
  Refactor proxy and auth test classes. Tests failing for digest auth.
  
  * bzrlib/transport/http/_urllib2_wrappers.py:
  (AbstractAuthHandler.auth_required,
  AbstractAuthHandler.auth_successful): Add response parameter as an
  authentication scheme may need additional info provided only
  there.
  
  * bzrlib/tests/test_http.py:
  (TestAuth, TestHTTPAuth, TestProxyAuth, TestHTTPBasicAuth,
  TestHTTPProxyBasicAuth): Refactoring.
  (TestHTTPDigestAuth, TestHTTPProxyDigestAuth): New classes for
  digest authentication tests.
  
  * bzrlib/tests/HttpServer.py:
  (TestingHTTPRequestHandler.translate_path): Handles request
  proxying based on a server attribute.
  (HttpServer): Add proxy_requests attribute.
  
  * bzrlib/tests/HTTPTestUtil.py:
  (ProxyServer): Replaces FakeProxyRequestHandler now that
  HttpServer handles proxying based on a server attribute.
  (AuthRequestHandler, BasicAuthRequestHandler, AuthServer,
  HTTPAuthServer, ProxyAuthServer, HTTPBasicAuthServer,
  ProxyBasicAuthServer): Refactoring Auth servers and request
  handlers.
  (HTTPDigestAuthServer, ProxyDigestAuthServer): New classes for
  digest authentication.
modified:
  bzrlib/tests/HTTPTestUtil.py   HTTPTestUtil.py-20050914180604-247d3aafb7a43343
  bzrlib/tests/HttpServer.py     httpserver.py-20061012142527-m1yxdj1xazsf8d7s-1
  bzrlib/tests/test_http.py      testhttp.py-20051018020158-b2eef6e867c514d9
  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-18 08:08:16 +0000
+++ b/bzrlib/tests/HTTPTestUtil.py	2007-04-21 09:26:30 +0000
@@ -19,6 +19,7 @@
 from SimpleHTTPServer import SimpleHTTPRequestHandler
 import re
 import socket
+import urllib2
 import urlparse
 
 from bzrlib.tests import TestCaseWithTransport
@@ -204,26 +205,10 @@
         return self.__secondary_server
 
 
-class FakeProxyRequestHandler(TestingHTTPRequestHandler):
-    """Append a '-proxied' suffix to file served"""
-
-    def translate_path(self, path):
-        # We need to act as a proxy and accept absolute urls,
-        # which SimpleHTTPRequestHandler (grand parent) is not
-        # ready for. So we just drop the protocol://host:port
-        # part in front of the request-url (because we know we
-        # would not forward the request to *another* proxy).
-
-        # So we do what SimpleHTTPRequestHandler.translate_path
-        # do beginning with python 2.4.3: abandon query
-        # parameters, scheme, host port, etc (which ensure we
-        # provide the right behaviour on all python versions).
-        path = urlparse.urlparse(path)[2]
-        # And now, we can apply *our* trick to proxy files
-        self.path += '-proxied'
-        # An finally we leave our mother class do whatever it
-        # wants with the path
-        return TestingHTTPRequestHandler.translate_path(self, path)
+class ProxyServer(HttpServer):
+    """A proxy test server for http transports."""
+
+    proxy_requests = True
 
 
 class RedirectRequestHandler(TestingHTTPRequestHandler):
@@ -309,49 +294,92 @@
        self.old_server = self.get_secondary_server()
 
 
-class AbstractBasicAuthRequestHandler(TestingHTTPRequestHandler):
-    """Requires a basic authentication to process requests.
+class AuthRequestHandler(TestingHTTPRequestHandler):
+    """Requires an authentication to process requests.
 
     This is intended to be used with a server that always and
-    only use basic authentication.
+    only use one authentication scheme (implemented by daughter
+    classes).
     """
 
-    # The following attribute should be set dy daughter classes
-    _auth_header_sent = None
-    _auth_header_recv = None
-    _auth_error_code = None
+    # The following attributes should be defined in the server
+    # - _auth_header_sent: the header name sent to require auth
+    # - _auth_header_recv: the header received containing auth
+    # - _auth_error_code: the error code to indicate auth required
 
     def do_GET(self):
-        tcs = self.server.test_case_server
-        if tcs.auth_scheme == 'basic':
-            auth_header = self.headers.get(self._auth_header_recv)
-            authorized = False
-            if auth_header and auth_header.lower().startswith('basic '):
-                coded_auth = auth_header[len('Basic '):]
-                user, password = coded_auth.decode('base64').split(':')
-                authorized = tcs.authorized(user, password)
-            if not authorized:
-                # Note that we must update tcs *before* sending
-                # the error or the client may try to read it
-                # before we have sent the whole error back.
-                tcs.auth_required_errors += 1
-                self.send_response(self._auth_error_code)
-                self.send_header(self._auth_header_sent,
-                                 'Basic realm="Thou should not pass"')
-                self.end_headers()
-                return
+        if self.authorized():
+            return TestingHTTPRequestHandler.do_GET(self)
+        else:
+            # Note that we must update test_case_server *before*
+            # sending the error or the client may try to read it
+            # before we have sent the whole error back.
+            tcs = self.server.test_case_server
+            tcs.auth_required_errors += 1
+            self.send_response(tcs.auth_error_code)
+            self.send_header_auth_reqed()
+            self.end_headers()
+            return
 
         TestingHTTPRequestHandler.do_GET(self)
 
 
-class AuthHTTPServer(HttpServer):
-    """AuthHTTPServer extends HttpServer with a dictionary of passwords.
-
-    This is used as a base class for various schemes.
+class BasicAuthRequestHandler(AuthRequestHandler):
+    """Implements the basic authentication of a request"""
+
+    def authorized(self):
+        tcs = self.server.test_case_server
+        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)
+
+        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"')
+
+
+class DigestAuthRequestHandler(AuthRequestHandler):
+    """Implements the digest authentication of a request"""
+
+    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)
+
+        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"')
+
+class AuthServer(HttpServer):
+    """Extends HttpServer with a dictionary of passwords.
+
+    This is used as a base class for various schemes which should
+    all use or redefined the associated AuthRequestHandler.
 
     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
 
     def __init__(self, request_handler, auth_scheme):
         HttpServer.__init__(self, request_handler)
@@ -372,39 +400,48 @@
         return expected_password is not None and password == expected_password
 
 
-class BasicAuthRequestHandler(AbstractBasicAuthRequestHandler):
-    """Requires a basic authentication to process requests"""
-
-    _auth_header_sent = 'WWW-Authenticate'
-    _auth_header_recv = 'Authorization'
-    _auth_error_code = 401
-
-
-class BasicAuthHTTPServer(AuthHTTPServer):
-    """An HTTP server requiring basic authentication"""
-
-    def __init__(self):
-        AuthHTTPServer.__init__(self, BasicAuthRequestHandler, 'basic')
-
-
-class ProxyBasicAuthRequestHandler(AbstractBasicAuthRequestHandler,
-                                   FakeProxyRequestHandler):
-    """Requires a basic authentication to proxy requests.
-
-    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"""
-
-    def __init__(self):
-        AuthHTTPServer.__init__(self, ProxyBasicAuthRequestHandler, 'basic')
+class HTTPAuthServer(AuthServer):
+    """An HTTP server requiring authentication"""
+
+    auth_header_sent = 'WWW-Authenticate'
+    auth_header_recv = 'Authorization'
+    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
+
+
+class HTTPBasicAuthServer(HTTPAuthServer):
+    """An HTTP server requiring basic authentication"""
+
+    def __init__(self):
+        HTTPAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
+
+
+class HTTPDigestAuthServer(HTTPAuthServer):
+    """An HTTP server requiring digest authentication"""
+
+    def __init__(self):
+        HTTPAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
+
+
+class ProxyBasicAuthServer(ProxyAuthServer):
+    """An proxy server requiring basic authentication"""
+
+    def __init__(self):
+        ProxyAuthServer.__init__(self, BasicAuthRequestHandler, 'basic')
+
+
+class ProxyDigestAuthServer(ProxyAuthServer):
+    """An proxy server requiring basic authentication"""
+
+    def __init__(self):
+        ProxyAuthServer.__init__(self, DigestAuthRequestHandler, 'digest')
+
+

=== modified file 'bzrlib/tests/HttpServer.py'
--- a/bzrlib/tests/HttpServer.py	2007-04-01 06:19:16 +0000
+++ b/bzrlib/tests/HttpServer.py	2007-04-21 09:26:30 +0000
@@ -219,13 +219,39 @@
             self.get_multiple_ranges(file, file_size, ranges)
         file.close()
 
+    def translate_path(self, path):
+        """Translate a /-separated PATH to the local filename syntax.
+
+        If the server requires it, proxy the path before the usual translation
+        """
+        if self.server.test_case_server.proxy_requests:
+            # We need to act as a proxy and accept absolute urls,
+            # which SimpleHTTPRequestHandler (parent) is not
+            # ready for. So we just drop the protocol://host:port
+            # part in front of the request-url (because we know
+            # we would not forward the request to *another*
+            # proxy).
+
+            # So we do what SimpleHTTPRequestHandler.translate_path
+            # do beginning with python 2.4.3: abandon query
+            # parameters, scheme, host port, etc (which ensure we
+            # provide the right behaviour on all python versions).
+            path = urlparse.urlparse(path)[2]
+            # And now, we can apply *our* trick to proxy files
+            path += '-proxied'
+
+        return self._translate_path(path)
+
+    def _translate_path(self, path):
+        return SimpleHTTPRequestHandler.translate_path(self, path)
+
     if sys.platform == 'win32':
         # On win32 you cannot access non-ascii filenames without
         # decoding them into unicode first.
         # However, under Linux, you can access bytestream paths
         # without any problems. If this function was always active
         # it would probably break tests when LANG=C was set
-        def translate_path(self, path):
+        def _translate_path(self, path):
             """Translate a /-separated PATH to the local filename syntax.
 
             For bzr, all url paths are considered to be utf8 paths.
@@ -267,6 +293,10 @@
     Subclasses can provide a specific request handler.
     """
 
+    # Whether or not we proxy the requests (see
+    # TestingHTTPRequestHandler.translate_path).
+    proxy_requests = False
+
     # used to form the url that connects to this server
     _url_protocol = 'http'
 

=== modified file 'bzrlib/tests/test_http.py'
--- a/bzrlib/tests/test_http.py	2007-04-19 17:28:04 +0000
+++ b/bzrlib/tests/test_http.py	2007-04-21 09:26:30 +0000
@@ -47,13 +47,15 @@
 from bzrlib.tests.HTTPTestUtil import (
     BadProtocolRequestHandler,
     BadStatusRequestHandler,
-    BasicAuthHTTPServer,
-    FakeProxyRequestHandler,
     ForbiddenRequestHandler,
+    HTTPBasicAuthServer,
+    HTTPDigestAuthServer,
     HTTPServerRedirecting,
     InvalidStatusRequestHandler,
     NoRangeRequestHandler,
-    ProxyBasicAuthHTTPServer,
+    ProxyBasicAuthServer,
+    ProxyDigestAuthServer,
+    ProxyServer,
     SingleRangeRequestHandler,
     TestCaseWithRedirectedWebserver,
     TestCaseWithTwoWebservers,
@@ -788,7 +790,7 @@
         """Creates an http server that will serve files with
         '-proxied' appended to their names.
         """
-        return HttpServer(FakeProxyRequestHandler)
+        return ProxyServer()
 
     def _install_env(self, env):
         for name, value in env.iteritems():
@@ -1133,7 +1135,7 @@
                           self.get_a, self.old_transport, redirected)
 
 
-class TestHTTPAuth(object):
+class TestAuth(object):
     """Test some authentication scheme specified by daughter class.
 
     This MUST be used by daughter classes that also inherit from
@@ -1216,36 +1218,36 @@
         self.assertEqual(1, self.server.auth_required_errors)
 
 
-class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
-    """Test basic http authentication scheme"""
-
-    _transport = HttpTransport_urllib
+class TestHTTPAuth(TestAuth):
+    """Test HTTP authentication schemes.
+
+    Daughter classes MUST inherit from TestCaseWithWebserver too.
+    """
+
     _auth_header = 'Authorization'
 
     def setUp(self):
         TestCaseWithWebserver.setUp(self)
         self.server = self.get_readonly_server()
-        TestHTTPAuth.setUp(self)
-
-    def create_transport_readonly_server(self):
-        return BasicAuthHTTPServer()
+        TestAuth.setUp(self)
 
     def get_user_transport(self, user, password=None):
         return self._transport(self.get_user_url(user, password))
 
 
-class TestHTTPProxyBasicAuth(TestHTTPAuth, TestCaseWithTwoWebservers):
-    """Test basic http authentication scheme"""
+class TestProxyAuth(TestAuth):
+    """Test proxy authentication schemes.
 
-    _transport = HttpTransport_urllib
+    Daughter classes MUST also inherit from TestCaseWithWebserver.
+    """
     _auth_header = 'Proxy-authorization'
 
     def setUp(self):
-        TestCaseWithTwoWebservers.setUp(self)
+        TestCaseWithWebserver.setUp(self)
         self.server = self.get_readonly_server()
         self._old_env = {}
         self.addCleanup(self._restore_env)
-        TestHTTPAuth.setUp(self)
+        TestAuth.setUp(self)
         # Override the contents to avoid false positives
         self.build_tree_contents([('a', 'not proxied contents of a\n'),
                                   ('b', 'not proxied contents of b\n'),
@@ -1253,17 +1255,51 @@
                                   ('b-proxied', 'contents of b\n'),
                                   ])
 
-    def create_transport_readonly_server(self):
-        return ProxyBasicAuthHTTPServer()
-
-    def _install_env(self, env):
-        for name, value in env.iteritems():
-            self._old_env[name] = osutils.set_or_unset_env(name, value)
-
-    def _restore_env(self):
-        for name, value in self._old_env.iteritems():
-            osutils.set_or_unset_env(name, value)
-
     def get_user_transport(self, user, password=None):
         self._install_env({'all_proxy': self.get_user_url(user, password)})
         return self._transport(self.server.get_url())
+
+    def _install_env(self, env):
+        for name, value in env.iteritems():
+            self._old_env[name] = osutils.set_or_unset_env(name, value)
+
+    def _restore_env(self):
+        for name, value in self._old_env.iteritems():
+            osutils.set_or_unset_env(name, value)
+
+
+class TestHTTPBasicAuth(TestHTTPAuth, TestCaseWithWebserver):
+    """Test http basic authentication scheme"""
+
+    _transport = HttpTransport_urllib
+
+    def create_transport_readonly_server(self):
+        return HTTPBasicAuthServer()
+
+
+class TestHTTPProxyBasicAuth(TestProxyAuth, TestCaseWithWebserver):
+    """Test proxy basic authentication scheme"""
+
+    _transport = HttpTransport_urllib
+
+    def create_transport_readonly_server(self):
+        return ProxyBasicAuthServer()
+
+
+class TestHTTPDigestAuth(TestHTTPAuth, TestCaseWithWebserver):
+    """Test http digest authentication scheme"""
+
+    _transport = HttpTransport_urllib
+
+    def create_transport_readonly_server(self):
+        return HTTPDigestAuthServer()
+
+
+class TestHTTPProxyDigestAuth(TestProxyAuth, TestCaseWithWebserver):
+    """Test proxy digest authentication scheme"""
+
+    _transport = HttpTransport_urllib
+
+    def create_transport_readonly_server(self):
+        return ProxyDigestAuthServer()
+

=== modified file 'bzrlib/transport/http/_urllib2_wrappers.py'
--- a/bzrlib/transport/http/_urllib2_wrappers.py	2007-04-20 11:36:20 +0000
+++ b/bzrlib/transport/http/_urllib2_wrappers.py	2007-04-21 09:26:30 +0000
@@ -792,7 +792,7 @@
             request.add_unredirected_header(self.auth_header, client_header)
             response = self.parent.open(request)
             if response:
-                self.auth_successful(request, auth)
+                self.auth_successful(request, response, auth)
             return response
         # We are not qualified to handle the authentication.
         # Note: the authentication error handling will try all
@@ -836,13 +836,14 @@
         """
         raise NotImplementedError(self.build_auth_header)
 
-    def auth_successful(self, request, auth):
+    def auth_successful(self, request, response, auth):
         """The authentification was successful for the request.
 
         The params are stored in the request to allow reuse and
         avoid rountrips associated with authentication errors.
 
         :param request: The succesfully authenticated request.
+        :param response: The server response (may contain auth info).
         :param auth: The parameters used to succeed.
         """
         self.set_auth(request, auth)



More information about the bazaar-commits mailing list