[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