[CalendarServer-changes] [8093] CalendarServer/trunk/twext/web2

source_changes at macosforge.org source_changes at macosforge.org
Tue Sep 13 13:39:52 PDT 2011


Revision: 8093
          http://trac.macosforge.org/projects/calendarserver/changeset/8093
Author:   glyph at apple.com
Date:     2011-09-13 13:39:51 -0700 (Tue, 13 Sep 2011)
Log Message:
-----------
HTTP Strict Transport Security implementation.

Modified Paths:
--------------
    CalendarServer/trunk/twext/web2/channel/http.py
    CalendarServer/trunk/twext/web2/test/test_http.py

Modified: CalendarServer/trunk/twext/web2/channel/http.py
===================================================================
--- CalendarServer/trunk/twext/web2/channel/http.py	2011-09-13 19:14:09 UTC (rev 8092)
+++ CalendarServer/trunk/twext/web2/channel/http.py	2011-09-13 20:39:51 UTC (rev 8093)
@@ -74,9 +74,27 @@
         )
         self.transport.loseConnection()
 
+
+
 class SSLRedirectRequest(Request):
-    """ For redirecting HTTP to HTTPS port """
+    """
+    An L{SSLRedirectRequest} prevents processing if the request is over plain
+    HTTP; instead, it redirects to HTTPS.
 
+    If the request is already secured, it instead sets the
+    Strict-Transport-Security header as documented by the U{HTTP Strict
+    Transport Security specification
+    <http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-02>}.
+
+    @ivar maxAge: the number of seconds that a client must wait after receiving
+        an HTTPS response, before they may attempt to make an HTTP request
+        again.
+
+    @type maxAge: C{int}
+    """
+
+    maxAge = 600
+
     def process(self):
         ignored, secure = self.chanRequest.getHostInfo()
         if not secure:
@@ -90,10 +108,22 @@
                     "https://%s:%d%s"
                     % (config.ServerHostName, config.SSLPort, self.uri)
                 )
-            self.writeResponse(RedirectResponse(location))
+            return super(SSLRedirectRequest, self).writeResponse(
+                RedirectResponse(location)
+            )
         else:
             return super(SSLRedirectRequest, self).process()
 
+
+    def writeResponse(self, response):
+        """
+        Response filter to add HSTS header.
+        """
+        response.headers.addRawHeader("Strict-Transport-Security",
+                                      "max-age={max_age:d}"
+                                      .format(max_age=self.maxAge))
+        return super(SSLRedirectRequest, self).writeResponse(response)
+
 # >%
 
 PERSIST_NO_PIPELINE, PERSIST_PIPELINE = (1,2)

Modified: CalendarServer/trunk/twext/web2/test/test_http.py
===================================================================
--- CalendarServer/trunk/twext/web2/test/test_http.py	2011-09-13 19:14:09 UTC (rev 8092)
+++ CalendarServer/trunk/twext/web2/test/test_http.py	2011-09-13 20:39:51 UTC (rev 8093)
@@ -14,6 +14,7 @@
 from twisted.internet.defer import waitForDeferred, deferredGenerator
 from twisted.protocols import loopback
 from twisted.python import util, runtime
+from twext.web2.channel.http import SSLRedirectRequest
 from twisted.internet.task import deferLater
 
 class PreconditionTestCase(unittest.TestCase):
@@ -292,19 +293,30 @@
     def pauseProducing(self):
         self.paused = True
 
+
     def resumeProducing(self):
         self.paused = False
 
+
     def stopProducing(self):
         self.loseConnection()
 
+
     def loseWriteConnection(self):
         # HACK.
         self.loseConnection()
 
-class TestRequest(http.Request):
+
+    def getHost(self):
+        """
+        Synthesize a slightly more realistic 'host' thing.
+        """
+        return address.IPv4Address('TCP', 'localhost', 4321)
+
+
+class TestRequestMixin(object):
     def __init__(self, *args, **kwargs):
-        http.Request.__init__(self, *args, **kwargs)
+        super(TestRequestMixin, self).__init__(*args, **kwargs)
         self.cmds = []
         headers = list(self.headers.getAllRawHeaders())
         headers.sort()
@@ -324,6 +336,19 @@
     def _finished(self, x):
         self._reallyFinished(x)
 
+
+class TestRequest(TestRequestMixin, http.Request):
+    """
+    Stub request for testing.
+    """
+
+
+class TestSSLRedirectRequest(TestRequestMixin, SSLRedirectRequest):
+    """
+    Stub request for HSTS testing.
+    """
+
+
 class TestResponse(object):
     implements(iweb.IResponse)
 
@@ -369,11 +394,14 @@
         self.callLaters.append(f)
 
 class HTTPTests(unittest.TestCase):
+
+    requestClass = TestRequest
+
     def connect(self, logFile=None, **protocol_kwargs):
         cxn = TestConnection()
 
         def makeTestRequest(*args):
-            cxn.requests.append(TestRequest(*args))
+            cxn.requests.append(self.requestClass(*args))
             return cxn.requests[-1]
 
         factory = channel.HTTPFactory(requestFactory=makeTestRequest,
@@ -628,7 +656,7 @@
         cxn.client.loseConnection()
         self.assertDone(cxn)
 
-    def testHTTP1_1_chunking(self):
+    def testHTTP1_1_chunking(self, extraHeaders=""):
         cxn = self.connect()
         cmds = [[]]
         data = ""
@@ -651,7 +679,17 @@
         response = TestResponse()
         cxn.requests[0].writeResponse(response)
         response.write("Output")
-        data += "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n6\r\nOutput\r\n"
+        expected = ["HTTP/1.1 200 OK"]
+        if extraHeaders:
+            expected.append(extraHeaders)
+        expected.extend([
+            "Transfer-Encoding: chunked",
+            "",
+            "6",
+            "Output",
+            "",
+        ])
+        data += "\r\n".join(expected)
         self.compareResult(cxn, cmds, data)
 
         response.write("blahblahblah")
@@ -665,6 +703,18 @@
         cxn.client.loseConnection()
         self.assertDone(cxn)
 
+
+    def test_http1_1_sts(self):
+        """
+        L{SSLRedirectRequest} uses strict transport security, and will set the
+        appropriate header.
+        """
+        self.requestClass = TestSSLRedirectRequest
+        return self.testHTTP1_1_chunking(
+            "Strict-Transport-Security: max-age=600"
+        )
+
+
     def testHTTP1_1_expect_continue(self):
         cxn = self.connect()
         cmds = [[]]
@@ -1083,6 +1133,7 @@
 
 try:
     from twisted.internet import ssl
+    ssl # pyflakes
 except ImportError:
     # happens the first time the interpreter tries to import it
     ssl = None
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110913/4c680f1c/attachment.html>


More information about the calendarserver-changes mailing list