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):
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)
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)
"""
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)
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)