[Inteproxy-commits] r214 - in trunk: . test

scm-commit@wald.intevation.org scm-commit at wald.intevation.org
Tue Sep 15 22:27:48 CEST 2009


Author: bh
Date: 2009-09-15 22:27:48 +0200 (Tue, 15 Sep 2009)
New Revision: 214

Modified:
   trunk/ChangeLog
   trunk/test/serversupport.py
   trunk/test/test_inteproxy.py
Log:
Add some tests with actual HTTPS servers and HTTPS proxies.  The
tests require Python 2.6 because SSL server support was add in
2.6.  When run with Python 2.5 the tests are omitted.

* test/test_inteproxy.py (ServerTest): Extend to optionally
support an upstream HTTPS proxy
(TestInteProxyOWSProxy): New. Tests with an origin server that
uses HTTPS
(TestInteProxyOWSProxyWithAuthenticatingHTTPSProxy): Tests with an
upstream HTTPS proxy

* test/serversupport.py (HTTPSServer): New.  HTTP_S server.
Requires Python 2.6 to work.
(CONNECTRequestHandler): New.  Request handler to implement the
HTTP CONNECT method with authentication
(start_copy_thread): New.  Helper function for CONNECTRequestHandler


Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog	2009-09-15 20:22:20 UTC (rev 213)
+++ trunk/ChangeLog	2009-09-15 20:27:48 UTC (rev 214)
@@ -1,5 +1,24 @@
 2009-09-15  Bernhard Herzog  <bh at intevation.de>
 
+	Add some tests with actual HTTPS servers and HTTPS proxies.  The
+	tests require Python 2.6 because SSL server support was add in
+	2.6.  When run with Python 2.5 the tests are omitted.
+
+	* test/test_inteproxy.py (ServerTest): Extend to optionally
+	support an upstream HTTPS proxy
+	(TestInteProxyOWSProxy): New. Tests with an origin server that
+	uses HTTPS
+	(TestInteProxyOWSProxyWithAuthenticatingHTTPSProxy): Tests with an
+	upstream HTTPS proxy
+
+	* test/serversupport.py (HTTPSServer): New.  HTTP_S server.
+	Requires Python 2.6 to work.
+	(CONNECTRequestHandler): New.  Request handler to implement the
+	HTTP CONNECT method with authentication
+	(start_copy_thread): New.  Helper function for CONNECTRequestHandler
+
+2009-09-15  Bernhard Herzog  <bh at intevation.de>
+
 	* inteproxy/proxycore.py
 	(InteProxyHTTPRequestHandler.create_proxy_headers): New method to
 	help construct proxy authentication headers

Modified: trunk/test/serversupport.py
===================================================================
--- trunk/test/serversupport.py	2009-09-15 20:22:20 UTC (rev 213)
+++ trunk/test/serversupport.py	2009-09-15 20:27:48 UTC (rev 214)
@@ -1,4 +1,4 @@
-# Copyright (C) 2007, 2008 by Intevation GmbH
+# Copyright (C) 2007, 2008, 2009 by Intevation GmbH
 # Authors:
 # Bernhard Herzog <bh at intevation.de>
 #
@@ -7,11 +7,21 @@
 
 """Simple HTTP server with some static data that can be run in a thread"""
 
+
+import threading
+import socket
+import urllib
+import base64
 import BaseHTTPServer
 
+try:
+    import ssl
+except ImportError:
+    ssl = None
+
 import inteproxy.httpserver as httpserver
+from inteproxy.httpconnection import connect_tcp
 
-
 class HTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
 
     def do_GET(self):
@@ -48,3 +58,113 @@
         self.contents = contents
         httpserver.HTTPServer.__init__(self, server_address,
                                        RequestHandlerClass)
+
+
+def start_copy_thread(insock, outsock):
+    def copy():
+        # copy some constants to local scope to avoid attribute errors
+        # that may occur at the end of the test runs when the
+        # interpreter shuts down
+        SHUT_WR = socket.SHUT_WR
+        SHUT_RD = socket.SHUT_RD
+        socket_error = socket.error
+        while True:
+            chunk = insock.recv(1024)
+            if not chunk:
+                outsock.shutdown(SHUT_WR)
+                try:
+                    insock.shutdown(SHUT_RD)
+                except socket_error:
+                    pass
+                return
+            outsock.send(chunk)
+
+    thread = threading.Thread(target=copy)
+    thread.setDaemon(1)
+    thread.start()
+    return thread
+
+
+class CONNECTRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+
+    # user database.  list of strings of the form "USERNAME:PASSWORD".
+    # If known_users is None, no authentication is required at all.
+    known_users = ["testuser:secret"]
+
+    def authenticate_proxy(self):
+        if self.known_users is None:
+            return True
+
+        authenticated = False
+        auth = self.headers.get("Proxy-Authorization")
+        if auth:
+            parts = auth.split()
+            if len(parts) == 2:
+                auth_scheme, encoded = parts
+                if auth_scheme.lower() == "basic":
+                    decoded = base64.b64decode(encoded)
+                    if decoded in self.known_users:
+                        authenticated = True
+
+        if not authenticated:
+            # we should send a Proxy-Authenticate too, but it doesn't
+            # matter for this test because InteProxy doesn't look at the
+            # header yet.
+            self.send_response(407, "Proxy Authentication Required")
+            self.send_header("Proxy-Authenticate",
+                             'Basic realm="inteproxytest"')
+            self.send_header("Content-Type", "text/html")
+            self.send_header('Connection', 'close')
+            self.end_headers()
+            self.wfile.write("<html><body>"
+                             "Proxy requires authentication"
+                             "</body><html>")
+        return authenticated
+
+
+    def do_CONNECT(self):
+        if not self.authenticate_proxy():
+            return
+
+        real_host, real_port = urllib.splitport(self.path)
+        if real_port is not None:
+            real_port = int(real_port)
+        else:
+            self.send_error(400,
+                            "Bad Request (no port number in CONNECT method)")
+            return
+
+        self.send_response(200)
+        self.end_headers()
+
+        # origin_sock = socket.socket()
+        # origin_sock.connect((real_host, real_port))
+        origin_sock = connect_tcp(real_host, real_port, self.log_message)
+        threads = [start_copy_thread(self.connection, origin_sock),
+                   start_copy_thread(origin_sock, self.connection)]
+
+        for thread in threads:
+            thread.join()
+
+
+
+class HTTPSServer(httpserver.HTTPServer):
+
+    supported = ssl is not None
+
+    def __init__(self, contents, server_address=("127.0.0.1", 0),
+                 RequestHandlerClass=HTTPRequestHandler,
+                 keyfile=None, certfile=None):
+        self.contents = contents
+        self.keyfile = keyfile
+        self.certfile = certfile
+        httpserver.HTTPServer.__init__(self, server_address,
+                                       RequestHandlerClass)
+
+    def get_request(self):
+        sock, client_address = httpserver.HTTPServer.get_request(self)
+        sslsock = ssl.wrap_socket(sock, server_side=True,
+                                  certfile=self.certfile,
+                                  keyfile=self.keyfile,
+                                  ssl_version=ssl.PROTOCOL_SSLv23)
+        return sslsock, client_address

Modified: trunk/test/test_inteproxy.py
===================================================================
--- trunk/test/test_inteproxy.py	2009-09-15 20:22:20 UTC (rev 213)
+++ trunk/test/test_inteproxy.py	2009-09-15 20:27:48 UTC (rev 214)
@@ -14,19 +14,58 @@
 import base64
 
 import serversupport
+import support
 
 from inteproxy.transcoder import create_transcoder_map
 from inteproxy.proxycore import MasterWorkerServer, InteProxyHTTPRequestHandler
 from inteproxy.config import Struct, parse_inteproxy_url
 import inteproxy.httpserver as httpserver
+import inteproxy.getpassword
 
 
+class ServerTest(unittest.TestCase, support.FileTestMixin):
 
-class ServerTest(unittest.TestCase):
-
     """Base class for tests that run the InteProxy in a thread"""
 
+    ssl_key_and_cert = """\
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDHZXrYyr6lMox1DzapO+mFJ253e2epjIxTNuxLL/ibukO58ume
+RbyVwNtZoSs9fmo69HCyaVIu4GRLBLYZsz+raKjpMv2FlQK5e7UJsj3dBYtGdCns
+MWQ5qlqHJP9RGvxEsj707dLz9VpMYQ3Z+uwiXkcpQ0TAfuiNPjOrL2i1GwIDAQAB
+AoGAEqjffywx/Rmm5UmALwQpTUmsX8gPomFBvIPWRw79L+2VavBdhm82P7lljZS0
+jDOpU00OwtNyWmqrWA3f0LXijkhep7amwvoPr31I9PVxk2q8KlkqR854Kdo2T0ID
+vvRJmsdjQSoraSNGwGhdRB3XXlE7mRrPUO6VyYQTMXY3SxECQQD7BJVcHnNLBRSA
+/SOgpuWfp+uPJ+vmZX+kXqzaxB4Jdu2B4+4Ul2N2VB7lNA6zo38A2H2vGQfA88HU
+yCuGU9LpAkEAy1qb1Jj2Hvgx8PlWy6aKsL90N8E4PKMh8ZtzwnJB9/5Ym8E6dwhr
+G+SWyuF/Ay7rN/O6S+YblvtCoYhRz7PdYwJBAPSaNMk9Su/RlXdUNQF5YMzBHsOz
+DSbHxSfwsdPDw9lJMIugBgG+u8c0lZ6XqbPXIA086MxVQb7+SOUF4ZPV3vECQBo8
+I1SHM2GFdbP4BwmY/9WTraOvytiP10Y7XvDcGsSqzLWzdR58OI1NrWKZOvCnMfNy
+/zrhgfe1jMAYPS3Fr68CQFKSaCQazOUnERcIaWK1/UcuBgMwmShcnENTHjQ8EBt1
+ZYnyxKia81PR6W6xYSj/QqYqoC5uoXDKdAO2jYog1yQ=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDLjCCApegAwIBAgIJAKvA4hSZg7hjMA0GCSqGSIb3DQEBBQUAMG4xCzAJBgNV
+BAYTAkRFMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9JbnRldmF0aW9u
+IEdtYkgxHDAaBgNVBAsTE0ludGVQcm94eSBUZXN0c3VpdGUxEjAQBgNVBAMTCWxv
+Y2FsaG9zdDAeFw0wOTAzMzExODI4MDFaFw0xOTAzMjkxODI4MDFaMG4xCzAJBgNV
+BAYTAkRFMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9JbnRldmF0aW9u
+IEdtYkgxHDAaBgNVBAsTE0ludGVQcm94eSBUZXN0c3VpdGUxEjAQBgNVBAMTCWxv
+Y2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAx2V62Mq+pTKMdQ82
+qTvphSdud3tnqYyMUzbsSy/4m7pDufLpnkW8lcDbWaErPX5qOvRwsmlSLuBkSwS2
+GbM/q2io6TL9hZUCuXu1CbI93QWLRnQp7DFkOapahyT/URr8RLI+9O3S8/VaTGEN
+2frsIl5HKUNEwH7ojT4zqy9otRsCAwEAAaOB0zCB0DAdBgNVHQ4EFgQUiX30Ifsl
+A022O9V/JqUIMFVaAnUwgaAGA1UdIwSBmDCBlYAUiX30IfslA022O9V/JqUIMFVa
+AnWhcqRwMG4xCzAJBgNVBAYTAkRFMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYD
+VQQKEw9JbnRldmF0aW9uIEdtYkgxHDAaBgNVBAsTE0ludGVQcm94eSBUZXN0c3Vp
+dGUxEjAQBgNVBAMTCWxvY2FsaG9zdIIJAKvA4hSZg7hjMAwGA1UdEwQFMAMBAf8w
+DQYJKoZIhvcNAQEFBQADgYEAgqZVnsRy5uM9bSlSzJPQ7s4AM2RqCnOYpqRMVzpM
+aP4hEzoxht9VdNYbL3dW1WvL14srBcT+9B/mokFM5pDT/tevNbAFDvokh1VQNhmV
+A5PtuG8pfsswvIl4+weagZgUjlxB8RGS0qFiuQqKbPnMiiRdMiJyhCHi67pA7foj
+hnY=
+-----END CERTIFICATE-----
+"""
 
+
     # The content of the remote server in the format used by
     # serversupport.HTTPServer.  Override in derived classes.
     remote_contents = []
@@ -38,9 +77,16 @@
     rewrite_urls = False
 
     def setUp(self):
+        self.ssl_cert = self.create_temp_file("cert.pem", self.ssl_key_and_cert)
         self.old_stderr = sys.stderr
         sys.stderr = open(os.path.devnull, "w")
 
+        remote_server = self.create_origin_server()
+        self.remote_server = httpserver.ServerThread(remote_server)
+        self.remote_server.start(daemon=True)
+        self.remote_server_base_url = "http://localhost:%d/" \
+                                      % (self.remote_server.server_port,)
+
         http_proxy, username, password = self.create_http_proxy()
         if http_proxy:
             self.http_proxy = httpserver.ServerThread(http_proxy)
@@ -51,18 +97,25 @@
         else:
             self.http_proxy = self.http_proxy_desc = None
 
+        https_proxy, username, password = self.create_https_proxy()
+        if https_proxy:
+            self.https_proxy = httpserver.ServerThread(https_proxy)
+            self.https_proxy.start(daemon=True)
+            self.https_proxy_desc = Struct(host="localhost",
+                                          port=self.https_proxy.server_port,
+                                          username=username, password=password)
+        else:
+            self.https_proxy = self.https_proxy_desc = None
+
+
         proxyserver = MasterWorkerServer(("127.0.0.1", 0),
                                          InteProxyHTTPRequestHandler, 5,
-                                         self.http_proxy_desc, None,
+                                         self.http_proxy_desc,
+                                         self.https_proxy_desc,
                                    transcoder_map=self.create_transcoder_map(),
                                          rewrite_urls=self.rewrite_urls)
         self.server = httpserver.ServerThread(proxyserver)
         self.server.start(daemon=True)
-        remote_server = serversupport.HTTPServer(self.remote_contents)
-        self.remote_server = httpserver.ServerThread(remote_server)
-        self.remote_server.start(daemon=True)
-        self.remote_server_base_url = "http://localhost:%d/" \
-                                      % (self.remote_server.server_port,)
 
     def create_transcoder_map(self):
         transcoder_map = transcoder_map=create_transcoder_map()
@@ -79,9 +132,23 @@
         """
         return None, None, None
 
+    def create_https_proxy(self):
+        """Override in derived classes to create an upstream https proxy
+
+        The method should return a tuple with the proxy server object
+        and the username and password for authentications against the
+        proxy.
+        """
+        return None, None, None
+
+    def create_origin_server(self):
+        return serversupport.HTTPServer(self.remote_contents)
+
     def tearDown(self):
         try:
-            for server in [self.remote_server, self.http_proxy, self.server]:
+            for server in [self.remote_server,
+                           self.http_proxy, self.https_proxy,
+                           self.server]:
                 if server is not None:
                     server.stop()
         finally:
@@ -115,6 +182,46 @@
         self.assertEquals(data, "some text")
 
 
+class TestInteProxyOWSProxy(ServerTest):
+
+    remote_contents = [
+        ("/wms?user=fred&password=12345",
+         [("Content-Type", "text/plain")], "wms data"),
+        ("/search?q=foo&user=fred&password=12345#section1",
+         [("Content-Type", "text/plain")], "some text"),
+        ]
+
+    def create_origin_server(self):
+        return serversupport.HTTPSServer(self.remote_contents,
+                                         certfile=self.ssl_cert)
+
+    def create_transcoder_map(self):
+        url_prefix = ("owsproxy://fred:12345@localhost:%d"
+                      % self.remote_server.server_port)
+        transcoder_map = create_transcoder_map()
+        transcoder_map.add_rule(parse_inteproxy_url(url_prefix + "/wms"))
+        transcoder_map.add_rule(parse_inteproxy_url(url_prefix + "/search"))
+        return transcoder_map
+
+    if serversupport.HTTPSServer.supported:
+        def test_httpproxy_passthrough(self):
+            http = httplib.HTTPConnection("localhost", self.server.server_port)
+            http.request("GET", self.remote_server_base_url + "wms")
+            response = http.getresponse()
+            self.assertEquals(response.status, 200)
+            data = response.read()
+            self.assertEquals(data, "wms data")
+
+        def test_httpproxy_with_query_and_fragment(self):
+            http = httplib.HTTPConnection("localhost", self.server.server_port)
+            http.request("GET",
+                         self.remote_server_base_url + "search?q=foo#section1")
+            response = http.getresponse()
+            self.assertEquals(response.status, 200)
+            data = response.read()
+            self.assertEquals(data, "some text")
+
+
 class TestInteProxyURLRewriting(ServerTest):
 
     remote_contents = [
@@ -343,3 +450,50 @@
 
         # there must not be a Proxy-Authenticate header either
         self.failIf("Proxy-Authenticate" in response.msg, None)
+
+
+class TestInteProxyOWSProxyWithAuthenticatingHTTPSProxy(ServerTest):
+
+    remote_contents = [
+        ("/wms?user=fred&password=12345",
+         [("Content-Type", "text/plain")], "wms data"),
+        ("/search?q=foo&user=fred&password=12345#section1",
+         [("Content-Type", "text/plain")], "some text"),
+        ]
+
+    def create_https_proxy(self):
+        server = MasterWorkerServer(("127.0.0.1", 0),
+                                    serversupport.CONNECTRequestHandler, 5,
+                                    None, None,
+                                    transcoder_map=create_transcoder_map())
+        return server, "testuser", "secret"
+
+    def create_origin_server(self):
+        return serversupport.HTTPSServer(self.remote_contents,
+                                         certfile=self.ssl_cert)
+
+    def create_transcoder_map(self):
+        url_prefix = ("owsproxy://fred:12345@localhost:%d"
+                      % self.remote_server.server_port)
+        transcoder_map = create_transcoder_map()
+        transcoder_map.add_rule(parse_inteproxy_url(url_prefix + "/wms"))
+        transcoder_map.add_rule(parse_inteproxy_url(url_prefix + "/search"))
+        return transcoder_map
+
+    if serversupport.HTTPSServer.supported:
+        def test_httpproxy_passthrough(self):
+            http = httplib.HTTPConnection("localhost", self.server.server_port)
+            http.request("GET", self.remote_server_base_url + "wms")
+            response = http.getresponse()
+            self.assertEquals(response.status, 200)
+            data = response.read()
+            self.assertEquals(data, "wms data")
+
+        def test_httpproxy_with_query_and_fragment(self):
+            http = httplib.HTTPConnection("localhost", self.server.server_port)
+            http.request("GET",
+                         self.remote_server_base_url + "search?q=foo#section1")
+            response = http.getresponse()
+            self.assertEquals(response.status, 200)
+            data = response.read()
+            self.assertEquals(data, "some text")



More information about the Inteproxy-commits mailing list