Skip to content

Commit

Permalink
Merge pull request #3244 from bdarnell/xsrf-rename
Browse files Browse the repository at this point in the history
web: Support renaming the XSRF cookie
  • Loading branch information
bdarnell committed Mar 30, 2023
2 parents 7186b86 + eb61029 commit 85954d9
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 2 deletions.
9 changes: 9 additions & 0 deletions docs/web.rst
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,21 @@
* ``xsrf_cookie_kwargs``: May be set to a dictionary of
additional arguments to be passed to `.RequestHandler.set_cookie`
for the XSRF cookie.
* ``xsrf_cookie_name``: Controls the name used for the XSRF
cookie (default ``_xsrf``). The intended use is to take
advantage of `cookie prefixes`_. Note that cookie prefixes
interact with other cookie flags, so they must be combined
with ``xsrf_cookie_kwargs``, such as
``{"xsrf_cookie_name": "__Host-xsrf", "xsrf_cookie_kwargs":
{"secure": True}}``
* ``twitter_consumer_key``, ``twitter_consumer_secret``,
``friendfeed_consumer_key``, ``friendfeed_consumer_secret``,
``google_consumer_key``, ``google_consumer_secret``,
``facebook_api_key``, ``facebook_secret``: Used in the
`tornado.auth` module to authenticate to various APIs.

.. _cookie prefixes: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes

Template settings:

* ``autoescape``: Controls automatic escaping for templates.
Expand Down
59 changes: 59 additions & 0 deletions tornado/test/web_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2919,6 +2919,65 @@ def test_versioning(self):
self.assertEqual(response.code, 200)


# A subset of the previous test with a different cookie name
class XSRFCookieNameTest(SimpleHandlerTestCase):
class Handler(RequestHandler):
def get(self):
self.write(self.xsrf_token)

def post(self):
self.write("ok")

def get_app_kwargs(self):
return dict(
xsrf_cookies=True,
xsrf_cookie_name="__Host-xsrf",
xsrf_cookie_kwargs={"secure": True},
)

def setUp(self):
super().setUp()
self.xsrf_token = self.get_token()

def get_token(self, old_token=None):
if old_token is not None:
headers = self.cookie_headers(old_token)
else:
headers = None
response = self.fetch("/", headers=headers)
response.rethrow()
return native_str(response.body)

def cookie_headers(self, token=None):
if token is None:
token = self.xsrf_token
return {"Cookie": "__Host-xsrf=" + token}

def test_xsrf_fail_no_token(self):
with ExpectLog(gen_log, ".*'_xsrf' argument missing"):
response = self.fetch("/", method="POST", body=b"")
self.assertEqual(response.code, 403)

def test_xsrf_fail_body_no_cookie(self):
with ExpectLog(gen_log, ".*XSRF cookie does not match POST"):
response = self.fetch(
"/",
method="POST",
body=urllib.parse.urlencode(dict(_xsrf=self.xsrf_token)),
)
self.assertEqual(response.code, 403)

def test_xsrf_success_post_body(self):
response = self.fetch(
"/",
method="POST",
# Note that renaming the cookie doesn't rename the POST param
body=urllib.parse.urlencode(dict(_xsrf=self.xsrf_token)),
headers=self.cookie_headers(),
)
self.assertEqual(response.code, 200)


class XSRFCookieKwargsTest(SimpleHandlerTestCase):
class Handler(RequestHandler):
def get(self):
Expand Down
6 changes: 4 additions & 2 deletions tornado/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -1486,7 +1486,8 @@ def xsrf_token(self) -> bytes:
if version is None:
if self.current_user and "expires_days" not in cookie_kwargs:
cookie_kwargs["expires_days"] = 30
self.set_cookie("_xsrf", self._xsrf_token, **cookie_kwargs)
cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf")
self.set_cookie(cookie_name, self._xsrf_token, **cookie_kwargs)
return self._xsrf_token

def _get_raw_xsrf_token(self) -> Tuple[Optional[int], bytes, float]:
Expand All @@ -1501,7 +1502,8 @@ def _get_raw_xsrf_token(self) -> Tuple[Optional[int], bytes, float]:
for version 1 cookies)
"""
if not hasattr(self, "_raw_xsrf_token"):
cookie = self.get_cookie("_xsrf")
cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf")
cookie = self.get_cookie(cookie_name)
if cookie:
version, token, timestamp = self._decode_xsrf_token(cookie)
else:
Expand Down

0 comments on commit 85954d9

Please sign in to comment.