Refs #32902 -- Added CSRF test when rotate_token() is called between resetting the token and processing response.
This commit is contained in:
parent
019424e44e
commit
311401d9a2
|
@ -15,7 +15,7 @@ from django.views.decorators.csrf import csrf_exempt, requires_csrf_token
|
||||||
|
|
||||||
from .views import (
|
from .views import (
|
||||||
ensure_csrf_cookie_view, non_token_view_using_request_processor,
|
ensure_csrf_cookie_view, non_token_view_using_request_processor,
|
||||||
post_form_view, token_view,
|
post_form_view, sandwiched_rotate_token_view, token_view,
|
||||||
)
|
)
|
||||||
|
|
||||||
# This is a test (unmasked) CSRF cookie / secret.
|
# This is a test (unmasked) CSRF cookie / secret.
|
||||||
|
@ -69,14 +69,30 @@ class CsrfFunctionTests(SimpleTestCase):
|
||||||
self.assertMaskedSecretCorrect(masked, secret)
|
self.assertMaskedSecretCorrect(masked, secret)
|
||||||
|
|
||||||
|
|
||||||
|
class TestingSessionStore(SessionStore):
|
||||||
|
"""
|
||||||
|
A version of SessionStore that stores what cookie values are passed to
|
||||||
|
set_cookie() when CSRF_USE_SESSIONS=True.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
# This is a list of the cookie values passed to set_cookie() over
|
||||||
|
# the course of the request-response.
|
||||||
|
self._cookies_set = []
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
super().__setitem__(key, value)
|
||||||
|
self._cookies_set.append(value)
|
||||||
|
|
||||||
|
|
||||||
class TestingHttpRequest(HttpRequest):
|
class TestingHttpRequest(HttpRequest):
|
||||||
"""
|
"""
|
||||||
A version of HttpRequest that allows us to change some things
|
A version of HttpRequest that lets one track and change some things more
|
||||||
more easily
|
easily.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.session = SessionStore()
|
self.session = TestingSessionStore()
|
||||||
|
|
||||||
def is_secure(self):
|
def is_secure(self):
|
||||||
return getattr(self, '_is_secure_override', False)
|
return getattr(self, '_is_secure_override', False)
|
||||||
|
@ -99,6 +115,21 @@ class CsrfViewMiddlewareTestMixin:
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError('This method must be implemented by a subclass.')
|
raise NotImplementedError('This method must be implemented by a subclass.')
|
||||||
|
|
||||||
|
def _get_cookies_set(self, req, resp):
|
||||||
|
"""
|
||||||
|
Return a list of the cookie values passed to set_cookie() over the
|
||||||
|
course of the request-response.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError('This method must be implemented by a subclass.')
|
||||||
|
|
||||||
|
def assertCookiesSet(self, req, resp, expected_secrets):
|
||||||
|
"""
|
||||||
|
Assert that set_cookie() was called with the given sequence of secrets.
|
||||||
|
"""
|
||||||
|
cookies_set = self._get_cookies_set(req, resp)
|
||||||
|
secrets_set = [_unmask_cipher_token(cookie) for cookie in cookies_set]
|
||||||
|
self.assertEqual(secrets_set, expected_secrets)
|
||||||
|
|
||||||
def _get_request(self, method=None, cookie=None):
|
def _get_request(self, method=None, cookie=None):
|
||||||
if method is None:
|
if method is None:
|
||||||
method = 'GET'
|
method = 'GET'
|
||||||
|
@ -332,6 +363,21 @@ class CsrfViewMiddlewareTestMixin:
|
||||||
resp = mw.process_view(req, post_form_view, (), {})
|
resp = mw.process_view(req, post_form_view, (), {})
|
||||||
self.assertIsNone(resp)
|
self.assertIsNone(resp)
|
||||||
|
|
||||||
|
def test_rotate_token_triggers_second_reset(self):
|
||||||
|
"""
|
||||||
|
If rotate_token() is called after the token is reset in
|
||||||
|
CsrfViewMiddleware's process_response() and before another call to
|
||||||
|
the same process_response(), the cookie is reset a second time.
|
||||||
|
"""
|
||||||
|
req = self._get_POST_request_with_token()
|
||||||
|
resp = sandwiched_rotate_token_view(req)
|
||||||
|
self.assertContains(resp, 'OK')
|
||||||
|
csrf_cookie = self._read_csrf_cookie(req, resp)
|
||||||
|
actual_secret = _unmask_cipher_token(csrf_cookie)
|
||||||
|
# set_cookie() was called a second time with a different secret.
|
||||||
|
self.assertCookiesSet(req, resp, [TEST_SECRET, actual_secret])
|
||||||
|
self.assertNotEqual(actual_secret, TEST_SECRET)
|
||||||
|
|
||||||
# Tests for the template tag method
|
# Tests for the template tag method
|
||||||
def test_token_node_no_csrf_cookie(self):
|
def test_token_node_no_csrf_cookie(self):
|
||||||
"""
|
"""
|
||||||
|
@ -875,6 +921,9 @@ class CsrfViewMiddlewareTests(CsrfViewMiddlewareTestMixin, SimpleTestCase):
|
||||||
csrf_cookie = resp.cookies[settings.CSRF_COOKIE_NAME]
|
csrf_cookie = resp.cookies[settings.CSRF_COOKIE_NAME]
|
||||||
return csrf_cookie.value
|
return csrf_cookie.value
|
||||||
|
|
||||||
|
def _get_cookies_set(self, req, resp):
|
||||||
|
return resp._cookies_set
|
||||||
|
|
||||||
def test_ensures_csrf_cookie_no_middleware(self):
|
def test_ensures_csrf_cookie_no_middleware(self):
|
||||||
"""
|
"""
|
||||||
The ensure_csrf_cookie() decorator works without middleware.
|
The ensure_csrf_cookie() decorator works without middleware.
|
||||||
|
@ -1089,6 +1138,9 @@ class CsrfViewMiddlewareUseSessionsTests(CsrfViewMiddlewareTestMixin, SimpleTest
|
||||||
return False
|
return False
|
||||||
return req.session[CSRF_SESSION_KEY]
|
return req.session[CSRF_SESSION_KEY]
|
||||||
|
|
||||||
|
def _get_cookies_set(self, req, resp):
|
||||||
|
return req.session._cookies_set
|
||||||
|
|
||||||
def test_no_session_on_request(self):
|
def test_no_session_on_request(self):
|
||||||
msg = (
|
msg = (
|
||||||
'CSRF_USE_SESSIONS is enabled, but request.session is not set. '
|
'CSRF_USE_SESSIONS is enabled, but request.session is not set. '
|
||||||
|
|
|
@ -1,8 +1,47 @@
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.middleware.csrf import get_token
|
from django.middleware.csrf import get_token, rotate_token
|
||||||
from django.template import Context, RequestContext, Template
|
from django.template import Context, RequestContext, Template
|
||||||
from django.template.context_processors import csrf
|
from django.template.context_processors import csrf
|
||||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
from django.utils.decorators import decorator_from_middleware
|
||||||
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
|
from django.views.decorators.csrf import csrf_protect, ensure_csrf_cookie
|
||||||
|
|
||||||
|
|
||||||
|
class TestingHttpResponse(HttpResponse):
|
||||||
|
"""
|
||||||
|
A version of HttpResponse that stores what cookie values are passed to
|
||||||
|
set_cookie() when CSRF_USE_SESSIONS=False.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
# This is a list of the cookie values passed to set_cookie() over
|
||||||
|
# the course of the request-response.
|
||||||
|
self._cookies_set = []
|
||||||
|
|
||||||
|
def set_cookie(self, key, value, **kwargs):
|
||||||
|
super().set_cookie(key, value, **kwargs)
|
||||||
|
self._cookies_set.append(value)
|
||||||
|
|
||||||
|
|
||||||
|
class _CsrfCookieRotator(MiddlewareMixin):
|
||||||
|
|
||||||
|
def process_response(self, request, response):
|
||||||
|
rotate_token(request)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
csrf_rotating_token = decorator_from_middleware(_CsrfCookieRotator)
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_protect
|
||||||
|
@csrf_rotating_token
|
||||||
|
@ensure_csrf_cookie
|
||||||
|
def sandwiched_rotate_token_view(request):
|
||||||
|
"""
|
||||||
|
This is a view that calls rotate_token() in process_response() between two
|
||||||
|
calls to CsrfViewMiddleware.process_response().
|
||||||
|
"""
|
||||||
|
return TestingHttpResponse('OK')
|
||||||
|
|
||||||
|
|
||||||
def post_form_view(request):
|
def post_form_view(request):
|
||||||
|
|
Loading…
Reference in New Issue