Document and test for type differences between get_cookie and get_secure_cookie.
Closes #501.
This commit is contained in:
parent
088f0c14cd
commit
f5012b423f
|
@ -5,7 +5,7 @@ from tornado.iostream import IOStream
|
||||||
from tornado.template import DictLoader
|
from tornado.template import DictLoader
|
||||||
from tornado.testing import LogTrapTestCase, AsyncHTTPTestCase
|
from tornado.testing import LogTrapTestCase, AsyncHTTPTestCase
|
||||||
from tornado.util import b, bytes_type, ObjectDict
|
from tornado.util import b, bytes_type, ObjectDict
|
||||||
from tornado.web import RequestHandler, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler, _create_signature
|
from tornado.web import RequestHandler, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler, _create_signature, create_signed_value
|
||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
import logging
|
import logging
|
||||||
|
@ -60,6 +60,13 @@ class SecureCookieTest(LogTrapTestCase):
|
||||||
# it gets rejected
|
# it gets rejected
|
||||||
assert handler.get_secure_cookie('foo') is None
|
assert handler.get_secure_cookie('foo') is None
|
||||||
|
|
||||||
|
def test_arbitrary_bytes(self):
|
||||||
|
# Secure cookies accept arbitrary data (which is base64 encoded).
|
||||||
|
# Note that normal cookies accept only a subset of ascii.
|
||||||
|
handler = CookieTestRequestHandler()
|
||||||
|
handler.set_secure_cookie('foo', b('\xe9'))
|
||||||
|
self.assertEqual(handler.get_secure_cookie('foo'), b('\xe9'))
|
||||||
|
|
||||||
|
|
||||||
class CookieTest(AsyncHTTPTestCase, LogTrapTestCase):
|
class CookieTest(AsyncHTTPTestCase, LogTrapTestCase):
|
||||||
def get_app(self):
|
def get_app(self):
|
||||||
|
@ -294,6 +301,12 @@ class TypeCheckHandler(RequestHandler):
|
||||||
self.check_type('cookie_key', self.cookies.keys()[0], str)
|
self.check_type('cookie_key', self.cookies.keys()[0], str)
|
||||||
self.check_type('cookie_value', self.cookies.values()[0].value, str)
|
self.check_type('cookie_value', self.cookies.values()[0].value, str)
|
||||||
|
|
||||||
|
# Secure cookies return bytes because they can contain arbitrary
|
||||||
|
# data, but regular cookies are native strings.
|
||||||
|
assert self.cookies.keys() == ['asdf']
|
||||||
|
self.check_type('get_secure_cookie', self.get_secure_cookie('asdf'), bytes_type)
|
||||||
|
self.check_type('get_cookie', self.get_cookie('asdf'), str)
|
||||||
|
|
||||||
self.check_type('xsrf_token', self.xsrf_token, bytes_type)
|
self.check_type('xsrf_token', self.xsrf_token, bytes_type)
|
||||||
self.check_type('xsrf_form_html', self.xsrf_form_html(), str)
|
self.check_type('xsrf_form_html', self.xsrf_form_html(), str)
|
||||||
|
|
||||||
|
@ -413,6 +426,7 @@ class HeaderInjectionHandler(RequestHandler):
|
||||||
|
|
||||||
|
|
||||||
class WebTest(AsyncHTTPTestCase, LogTrapTestCase):
|
class WebTest(AsyncHTTPTestCase, LogTrapTestCase):
|
||||||
|
COOKIE_SECRET = "WebTest.COOKIE_SECRET"
|
||||||
def get_app(self):
|
def get_app(self):
|
||||||
loader = DictLoader({
|
loader = DictLoader({
|
||||||
"linkify.html": "{% module linkify(message) %}",
|
"linkify.html": "{% module linkify(message) %}",
|
||||||
|
@ -441,7 +455,8 @@ class WebTest(AsyncHTTPTestCase, LogTrapTestCase):
|
||||||
]
|
]
|
||||||
return Application(urls,
|
return Application(urls,
|
||||||
template_loader=loader,
|
template_loader=loader,
|
||||||
autoescape="xhtml_escape")
|
autoescape="xhtml_escape",
|
||||||
|
cookie_secret=self.COOKIE_SECRET)
|
||||||
|
|
||||||
def fetch_json(self, *args, **kwargs):
|
def fetch_json(self, *args, **kwargs):
|
||||||
response = self.fetch(*args, **kwargs)
|
response = self.fetch(*args, **kwargs)
|
||||||
|
@ -449,13 +464,15 @@ class WebTest(AsyncHTTPTestCase, LogTrapTestCase):
|
||||||
return json_decode(response.body)
|
return json_decode(response.body)
|
||||||
|
|
||||||
def test_types(self):
|
def test_types(self):
|
||||||
|
cookie_value = to_unicode(create_signed_value(self.COOKIE_SECRET,
|
||||||
|
"asdf", "qwer"))
|
||||||
response = self.fetch("/typecheck/asdf?foo=bar",
|
response = self.fetch("/typecheck/asdf?foo=bar",
|
||||||
headers={"Cookie": "cook=ie"})
|
headers={"Cookie": "asdf=" + cookie_value})
|
||||||
data = json_decode(response.body)
|
data = json_decode(response.body)
|
||||||
self.assertEqual(data, {})
|
self.assertEqual(data, {})
|
||||||
|
|
||||||
response = self.fetch("/typecheck/asdf?foo=bar", method="POST",
|
response = self.fetch("/typecheck/asdf?foo=bar", method="POST",
|
||||||
headers={"Cookie": "cook=ie"},
|
headers={"Cookie": "asdf=" + cookie_value},
|
||||||
body="foo=bar")
|
body="foo=bar")
|
||||||
|
|
||||||
def test_decode_argument(self):
|
def test_decode_argument(self):
|
||||||
|
|
|
@ -408,6 +408,9 @@ class RequestHandler(object):
|
||||||
Note that the ``expires_days`` parameter sets the lifetime of the
|
Note that the ``expires_days`` parameter sets the lifetime of the
|
||||||
cookie in the browser, but is independent of the ``max_age_days``
|
cookie in the browser, but is independent of the ``max_age_days``
|
||||||
parameter to `get_secure_cookie`.
|
parameter to `get_secure_cookie`.
|
||||||
|
|
||||||
|
Secure cookies may contain arbitrary byte values, not just unicode
|
||||||
|
strings (unlike regular cookies)
|
||||||
"""
|
"""
|
||||||
self.set_cookie(name, self.create_signed_value(name, value),
|
self.set_cookie(name, self.create_signed_value(name, value),
|
||||||
expires_days=expires_days, **kwargs)
|
expires_days=expires_days, **kwargs)
|
||||||
|
@ -424,7 +427,11 @@ class RequestHandler(object):
|
||||||
name, value)
|
name, value)
|
||||||
|
|
||||||
def get_secure_cookie(self, name, value=None, max_age_days=31):
|
def get_secure_cookie(self, name, value=None, max_age_days=31):
|
||||||
"""Returns the given signed cookie if it validates, or None."""
|
"""Returns the given signed cookie if it validates, or None.
|
||||||
|
|
||||||
|
The decoded cookie value is returned as a byte string (unlike
|
||||||
|
`get_cookie`).
|
||||||
|
"""
|
||||||
self.require_setting("cookie_secret", "secure cookies")
|
self.require_setting("cookie_secret", "secure cookies")
|
||||||
if value is None:
|
if value is None:
|
||||||
value = self.get_cookie(name)
|
value = self.get_cookie(name)
|
||||||
|
|
Loading…
Reference in New Issue