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