From: Ben Darnell Date: Tue, 27 May 2014 01:18:36 +0000 (-0400) Subject: Add the option to request an older xsrf cookie version. X-Git-Tag: v3.2.2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c2a8c322b3f3f54f6525d7469ecab1af46862bc2;p=thirdparty%2Ftornado.git Add the option to request an older xsrf cookie version. Fix an issue with v1 cookies on py32. --- diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index 2c85bfb01..c475520b2 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -1910,6 +1910,10 @@ class SignedValueTest(unittest.TestCase): class XSRFTest(SimpleHandlerTestCase): class Handler(RequestHandler): def get(self): + version = int(self.get_argument("version", "2")) + # This would be a bad idea in a real app, but in this test + # it's fine. + self.settings["xsrf_cookie_version"] = version self.write(self.xsrf_token) def post(self): @@ -1922,12 +1926,14 @@ class XSRFTest(SimpleHandlerTestCase): super(XSRFTest, self).setUp() self.xsrf_token = self.get_token() - def get_token(self, old_token=None): + def get_token(self, old_token=None, version=None): if old_token is not None: headers = self.cookie_headers(old_token) else: headers = None - response = self.fetch("/", headers=headers) + response = self.fetch( + "/" if version is None else ("/?version=%d" % version), + headers=headers) response.rethrow() return native_str(response.body) @@ -2019,3 +2025,28 @@ class XSRFTest(SimpleHandlerTestCase): headers=self.cookie_headers(token)) self.assertEqual(response.code, 200) self.assertEqual(len(tokens_seen), 6) + + def test_versioning(self): + # Version 1 still produces distinct tokens per request. + self.assertNotEqual(self.get_token(version=1), + self.get_token(version=1)) + + # Refreshed v1 tokens are all identical. + v1_token = self.get_token(version=1) + for i in range(5): + self.assertEqual(self.get_token(v1_token, version=1), v1_token) + + # Upgrade to a v2 version of the same token + v2_token = self.get_token(v1_token) + self.assertNotEqual(v1_token, v2_token) + # Each v1 token can map to many v2 tokens. + self.assertNotEqual(v2_token, self.get_token(v1_token)) + + # The tokens are cross-compatible. + for cookie_token, body_token in ((v1_token, v2_token), + (v2_token, v1_token)): + response = self.fetch( + "/", method="POST", + body=urllib_parse.urlencode(dict(_xsrf=body_token)), + headers=self.cookie_headers(cookie_token)) + self.assertEqual(response.code, 200) diff --git a/tornado/web.py b/tornado/web.py index ed8148e16..d8430deeb 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -1071,13 +1071,20 @@ class RequestHandler(object): """ if not hasattr(self, "_xsrf_token"): version, token, timestamp = self._get_raw_xsrf_token() - mask = os.urandom(4) - self._xsrf_token = b"|".join([ - b"2", - binascii.b2a_hex(mask), - binascii.b2a_hex(_websocket_mask(mask, token)), - utf8(str(int(timestamp)))]) - if version is None or version != 2: + output_version = self.settings.get("xsrf_cookie_version", 2) + if output_version == 1: + self._xsrf_token = binascii.b2a_hex(token) + elif output_version == 2: + mask = os.urandom(4) + self._xsrf_token = b"|".join([ + b"2", + binascii.b2a_hex(mask), + binascii.b2a_hex(_websocket_mask(mask, token)), + utf8(str(int(timestamp)))]) + else: + raise ValueError("unknown xsrf cookie version %d", + output_version) + if version is None: expires_days = 30 if self.current_user else None self.set_cookie("_xsrf", self._xsrf_token, expires_days=expires_days) @@ -1113,7 +1120,7 @@ class RequestHandler(object): return None, None, None elif len(cookie) == 32: version = 1 - token = binascii.a2b_hex(cookie) + token = binascii.a2b_hex(utf8(cookie)) # We don't have a usable timestamp in older versions. timestamp = int(time.time()) return (version, token, timestamp)