]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Extract cookie-signing methods from RequestHandler so they can be used
authorBen Darnell <ben@bendarnell.com>
Fri, 9 Sep 2011 05:45:30 +0000 (22:45 -0700)
committerBen Darnell <ben@bendarnell.com>
Fri, 9 Sep 2011 05:45:30 +0000 (22:45 -0700)
outside the web stack.

Closes #339.

tornado/test/web_test.py
tornado/web.py

index e6aaa59b65e13ac53229ac029a00d61f50982c5e..ca473d89924d2bb68d91e1243d5774a4a6fc11e9 100644 (file)
@@ -3,7 +3,7 @@ from tornado.iostream import IOStream
 from tornado.template import DictLoader
 from tornado.testing import LogTrapTestCase, AsyncHTTPTestCase
 from tornado.util import b, bytes_type
-from tornado.web import RequestHandler, _O, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler
+from tornado.web import RequestHandler, _O, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler, _create_signature
 
 import binascii
 import logging
@@ -40,13 +40,16 @@ class SecureCookieTest(LogTrapTestCase):
         assert match
         timestamp = match.group(1)
         sig = match.group(2)
-        self.assertEqual(handler._cookie_signature('foo', '12345678',
-                                                   timestamp), sig)
+        self.assertEqual(
+            _create_signature(handler.application.settings["cookie_secret"],
+                              'foo', '12345678', timestamp),
+            sig)
         # shifting digits from payload to timestamp doesn't alter signature
         # (this is not desirable behavior, just confirming that that's how it
         # works)
         self.assertEqual(
-            handler._cookie_signature('foo', '1234', b('5678') + timestamp),
+            _create_signature(handler.application.settings["cookie_secret"],
+                              'foo', '1234', b('5678') + timestamp),
             sig)
         # tamper with the cookie
         handler._cookies['foo'] = utf8('1234|5678%s|%s' % (timestamp, sig))
index 11df300acabbb415a09d7e2d8a145729ce6c5948..59445209b6f52fc3db17809afa297eeee713b19d 100644 (file)
@@ -395,47 +395,16 @@ class RequestHandler(object):
         method for non-cookie uses.  To decode a value not stored
         as a cookie use the optional value argument to get_secure_cookie.
         """
-        timestamp = utf8(str(int(time.time())))
-        value = base64.b64encode(utf8(value))
-        signature = self._cookie_signature(name, value, timestamp)
-        value = b("|").join([value, timestamp, signature])
-        return value
+        self.require_setting("cookie_secret", "secure cookies")
+        return create_signed_value(self.application.settings["cookie_secret"],
+                                   name, value)
 
     def get_secure_cookie(self, name, value=None, max_age_days=31):
         """Returns the given signed cookie if it validates, or None."""
-        if value is None: value = self.get_cookie(name)
-        if not value: return None
-        parts = utf8(value).split(b("|"))
-        if len(parts) != 3: return None
-        signature = self._cookie_signature(name, parts[0], parts[1])
-        if not _time_independent_equals(parts[2], signature):
-            logging.warning("Invalid cookie signature %r", value)
-            return None
-        timestamp = int(parts[1])
-        if timestamp < time.time() - max_age_days * 86400:
-            logging.warning("Expired cookie %r", value)
-            return None
-        if timestamp > time.time() + 31 * 86400:
-            # _cookie_signature does not hash a delimiter between the
-            # parts of the cookie, so an attacker could transfer trailing
-            # digits from the payload to the timestamp without altering the
-            # signature.  For backwards compatibility, sanity-check timestamp
-            # here instead of modifying _cookie_signature.
-            logging.warning("Cookie timestamp in future; possible tampering %r", value)
-            return None
-        if parts[1].startswith(b("0")):
-            logging.warning("Tampered cookie %r", value)
-        try:
-            return base64.b64decode(parts[0])
-        except Exception:
-            return None
-
-    def _cookie_signature(self, *parts):
         self.require_setting("cookie_secret", "secure cookies")
-        hash = hmac.new(utf8(self.application.settings["cookie_secret"]),
-                        digestmod=hashlib.sha1)
-        for part in parts: hash.update(utf8(part))
-        return utf8(hash.hexdigest())
+        if value is None: value = self.get_cookie(name)
+        return decode_signed_value(self.application.settings["cookie_secret"],
+                                   name, value, max_age_days=max_age_days)
 
     def redirect(self, url, permanent=False):
         """Sends a redirect to the given (optionally relative) URL."""
@@ -1904,6 +1873,45 @@ def _time_independent_equals(a, b):
             result |= ord(x) ^ ord(y)
     return result == 0
 
+def create_signed_value(secret, name, value):
+    timestamp = utf8(str(int(time.time())))
+    value = base64.b64encode(utf8(value))
+    signature = _create_signature(secret, name, value, timestamp)
+    value = b("|").join([value, timestamp, signature])
+    return value
+
+def decode_signed_value(secret, name, value, max_age_days=31):
+    if not value: return None
+    parts = utf8(value).split(b("|"))
+    if len(parts) != 3: return None
+    signature = _create_signature(secret, name, parts[0], parts[1])
+    if not _time_independent_equals(parts[2], signature):
+        logging.warning("Invalid cookie signature %r", value)
+        return None
+    timestamp = int(parts[1])
+    if timestamp < time.time() - max_age_days * 86400:
+        logging.warning("Expired cookie %r", value)
+        return None
+    if timestamp > time.time() + 31 * 86400:
+        # _cookie_signature does not hash a delimiter between the
+        # parts of the cookie, so an attacker could transfer trailing
+        # digits from the payload to the timestamp without altering the
+        # signature.  For backwards compatibility, sanity-check timestamp
+        # here instead of modifying _cookie_signature.
+        logging.warning("Cookie timestamp in future; possible tampering %r", value)
+        return None
+    if parts[1].startswith(b("0")):
+        logging.warning("Tampered cookie %r", value)
+    try:
+        return base64.b64decode(parts[0])
+    except Exception:
+        return None
+
+def _create_signature(secret, *parts):
+    hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
+    for part in parts: hash.update(utf8(part))
+    return utf8(hash.hexdigest())
+
 
 class _O(dict):
     """Makes a dictionary behave like an object."""