|
@@ -6,10 +6,12 @@ import re
|
|
|
import warnings
|
|
|
|
|
|
from django.conf import settings
|
|
|
+from django.core.exceptions import ImproperlyConfigured
|
|
|
from django.http import HttpRequest, HttpResponse
|
|
|
from django.middleware.csrf import (
|
|
|
- CSRF_TOKEN_LENGTH, REASON_BAD_TOKEN, REASON_NO_CSRF_COOKIE,
|
|
|
- CsrfViewMiddleware, _compare_salted_tokens as equivalent_tokens, get_token,
|
|
|
+ CSRF_SESSION_KEY, CSRF_TOKEN_LENGTH, REASON_BAD_TOKEN,
|
|
|
+ REASON_NO_CSRF_COOKIE, CsrfViewMiddleware,
|
|
|
+ _compare_salted_tokens as equivalent_tokens, get_token,
|
|
|
)
|
|
|
from django.template import RequestContext, Template
|
|
|
from django.template.context_processors import csrf
|
|
@@ -58,20 +60,27 @@ class TestingHttpRequest(HttpRequest):
|
|
|
A version of HttpRequest that allows us to change some things
|
|
|
more easily
|
|
|
"""
|
|
|
+ def __init__(self):
|
|
|
+ super(TestingHttpRequest, self).__init__()
|
|
|
+ # A real session backend isn't needed.
|
|
|
+ self.session = {}
|
|
|
+
|
|
|
def is_secure(self):
|
|
|
return getattr(self, '_is_secure_override', False)
|
|
|
|
|
|
|
|
|
-class CsrfViewMiddlewareTest(SimpleTestCase):
|
|
|
+class CsrfViewMiddlewareTestMixin(object):
|
|
|
+ """
|
|
|
+ Shared methods and tests for session-based and cookie-based tokens.
|
|
|
+ """
|
|
|
+
|
|
|
_csrf_id = _csrf_id_cookie = '1bcdefghij2bcdefghij3bcdefghij4bcdefghij5bcdefghij6bcdefghijABCD'
|
|
|
|
|
|
def _get_GET_no_csrf_cookie_request(self):
|
|
|
return TestingHttpRequest()
|
|
|
|
|
|
def _get_GET_csrf_cookie_request(self):
|
|
|
- req = TestingHttpRequest()
|
|
|
- req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id_cookie
|
|
|
- return req
|
|
|
+ raise NotImplementedError('This method must be implemented by a subclass.')
|
|
|
|
|
|
def _get_POST_csrf_cookie_request(self):
|
|
|
req = self._get_GET_csrf_cookie_request()
|
|
@@ -88,16 +97,6 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
|
|
req.POST['csrfmiddlewaretoken'] = self._csrf_id
|
|
|
return req
|
|
|
|
|
|
- def _get_POST_bare_secret_csrf_cookie_request(self):
|
|
|
- req = self._get_POST_no_csrf_cookie_request()
|
|
|
- req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id_cookie[:32]
|
|
|
- return req
|
|
|
-
|
|
|
- def _get_POST_bare_secret_csrf_cookie_request_with_token(self):
|
|
|
- req = self._get_POST_bare_secret_csrf_cookie_request()
|
|
|
- req.POST['csrfmiddlewaretoken'] = self._csrf_id_cookie[:32]
|
|
|
- return req
|
|
|
-
|
|
|
def _check_token_present(self, response, csrf_id=None):
|
|
|
text = text_type(response.content, response.charset)
|
|
|
match = re.search("name='csrfmiddlewaretoken' value='(.*?)'", text)
|
|
@@ -107,77 +106,6 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
|
|
"Could not find csrfmiddlewaretoken to match %s" % csrf_token
|
|
|
)
|
|
|
|
|
|
- def test_process_view_token_too_long(self):
|
|
|
- """
|
|
|
- If the token is longer than expected, it is ignored and a new token is
|
|
|
- created.
|
|
|
- """
|
|
|
- req = self._get_GET_no_csrf_cookie_request()
|
|
|
- req.COOKIES[settings.CSRF_COOKIE_NAME] = 'x' * 100000
|
|
|
- CsrfViewMiddleware().process_view(req, token_view, (), {})
|
|
|
- resp = token_view(req)
|
|
|
- resp2 = CsrfViewMiddleware().process_response(req, resp)
|
|
|
- csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
|
|
|
- self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH)
|
|
|
-
|
|
|
- def test_process_view_token_invalid_chars(self):
|
|
|
- """
|
|
|
- If the token contains non-alphanumeric characters, it is ignored and a
|
|
|
- new token is created.
|
|
|
- """
|
|
|
- token = ('!@#' + self._csrf_id)[:CSRF_TOKEN_LENGTH]
|
|
|
- req = self._get_GET_no_csrf_cookie_request()
|
|
|
- req.COOKIES[settings.CSRF_COOKIE_NAME] = token
|
|
|
- CsrfViewMiddleware().process_view(req, token_view, (), {})
|
|
|
- resp = token_view(req)
|
|
|
- resp2 = CsrfViewMiddleware().process_response(req, resp)
|
|
|
- csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
|
|
|
- self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH)
|
|
|
- self.assertNotEqual(csrf_cookie.value, token)
|
|
|
-
|
|
|
- def test_process_view_token_invalid_bytes(self):
|
|
|
- """
|
|
|
- If the token contains improperly encoded unicode, it is ignored and a
|
|
|
- new token is created.
|
|
|
- """
|
|
|
- token = (b"<1>\xc2\xa1" + force_bytes(self._csrf_id, 'ascii'))[:CSRF_TOKEN_LENGTH]
|
|
|
- req = self._get_GET_no_csrf_cookie_request()
|
|
|
- req.COOKIES[settings.CSRF_COOKIE_NAME] = token
|
|
|
- # We expect a UnicodeWarning here, because we used broken utf-8 on purpose
|
|
|
- with warnings.catch_warnings():
|
|
|
- warnings.filterwarnings("ignore", category=UnicodeWarning)
|
|
|
- CsrfViewMiddleware().process_view(req, token_view, (), {})
|
|
|
- resp = token_view(req)
|
|
|
- resp2 = CsrfViewMiddleware().process_response(req, resp)
|
|
|
- csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
|
|
|
- self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH)
|
|
|
- self.assertNotEqual(csrf_cookie.value, token)
|
|
|
-
|
|
|
- def test_process_response_get_token_used(self):
|
|
|
- """
|
|
|
- When get_token is used, check that the cookie is created and headers
|
|
|
- patched.
|
|
|
- """
|
|
|
- req = self._get_GET_no_csrf_cookie_request()
|
|
|
-
|
|
|
- # Put tests for CSRF_COOKIE_* settings here
|
|
|
- with self.settings(CSRF_COOKIE_NAME='myname',
|
|
|
- CSRF_COOKIE_DOMAIN='.example.com',
|
|
|
- CSRF_COOKIE_PATH='/test/',
|
|
|
- CSRF_COOKIE_SECURE=True,
|
|
|
- CSRF_COOKIE_HTTPONLY=True):
|
|
|
- # token_view calls get_token() indirectly
|
|
|
- CsrfViewMiddleware().process_view(req, token_view, (), {})
|
|
|
- resp = token_view(req)
|
|
|
- resp2 = CsrfViewMiddleware().process_response(req, resp)
|
|
|
- csrf_cookie = resp2.cookies.get('myname', False)
|
|
|
- self.assertIsNot(csrf_cookie, False)
|
|
|
- self.assertEqual(csrf_cookie['domain'], '.example.com')
|
|
|
- self.assertIs(csrf_cookie['secure'], True)
|
|
|
- self.assertIs(csrf_cookie['httponly'], True)
|
|
|
- self.assertEqual(csrf_cookie['path'], '/test/')
|
|
|
- self.assertIn('Cookie', resp2.get('Vary', ''))
|
|
|
-
|
|
|
def test_process_response_get_token_not_used(self):
|
|
|
"""
|
|
|
If get_token() is not called, the view middleware does not
|
|
@@ -371,20 +299,6 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
|
|
"CSRF cookie was changed on an accepted request"
|
|
|
)
|
|
|
|
|
|
- def test_bare_secret_accepted_and_replaced(self):
|
|
|
- """
|
|
|
- The csrf token is reset from a bare secret.
|
|
|
- """
|
|
|
- req = self._get_POST_bare_secret_csrf_cookie_request_with_token()
|
|
|
- req2 = CsrfViewMiddleware().process_view(req, token_view, (), {})
|
|
|
- self.assertIsNone(req2)
|
|
|
- resp = token_view(req)
|
|
|
- resp = CsrfViewMiddleware().process_response(req, resp)
|
|
|
- self.assertIn(settings.CSRF_COOKIE_NAME, resp.cookies, "Cookie was not reset from bare secret")
|
|
|
- csrf_cookie = resp.cookies[settings.CSRF_COOKIE_NAME]
|
|
|
- self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH)
|
|
|
- self._check_token_present(resp, csrf_id=csrf_cookie.value)
|
|
|
-
|
|
|
@override_settings(DEBUG=True, ALLOWED_HOSTS=['www.example.com'])
|
|
|
def test_https_bad_referer(self):
|
|
|
"""
|
|
@@ -465,11 +379,7 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
|
|
req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
|
|
self.assertIsNone(req2)
|
|
|
|
|
|
- @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_COOKIE_DOMAIN='.example.com', USE_X_FORWARDED_PORT=True)
|
|
|
- def test_https_good_referer_behind_proxy(self):
|
|
|
- """
|
|
|
- A POST HTTPS request is accepted when USE_X_FORWARDED_PORT=True.
|
|
|
- """
|
|
|
+ def _test_https_good_referer_behind_proxy(self):
|
|
|
req = self._get_POST_request_with_token()
|
|
|
req._is_secure_override = True
|
|
|
req.META.update({
|
|
@@ -508,12 +418,7 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
|
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
|
|
self.assertIsNone(response)
|
|
|
|
|
|
- @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_COOKIE_DOMAIN='.example.com')
|
|
|
- def test_https_good_referer_matches_cookie_domain(self):
|
|
|
- """
|
|
|
- A POST HTTPS request with a good referer should be accepted from a
|
|
|
- subdomain that's allowed by CSRF_COOKIE_DOMAIN.
|
|
|
- """
|
|
|
+ def _test_https_good_referer_matches_cookie_domain(self):
|
|
|
req = self._get_POST_request_with_token()
|
|
|
req._is_secure_override = True
|
|
|
req.META['HTTP_REFERER'] = 'https://foo.example.com/'
|
|
@@ -521,12 +426,7 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
|
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
|
|
self.assertIsNone(response)
|
|
|
|
|
|
- @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_COOKIE_DOMAIN='.example.com')
|
|
|
- def test_https_good_referer_matches_cookie_domain_with_different_port(self):
|
|
|
- """
|
|
|
- A POST HTTPS request with a good referer should be accepted from a
|
|
|
- subdomain that's allowed by CSRF_COOKIE_DOMAIN and a non-443 port.
|
|
|
- """
|
|
|
+ def _test_https_good_referer_matches_cookie_domain_with_different_port(self):
|
|
|
req = self._get_POST_request_with_token()
|
|
|
req._is_secure_override = True
|
|
|
req.META['HTTP_HOST'] = 'www.example.com'
|
|
@@ -535,21 +435,98 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
|
|
response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
|
|
self.assertIsNone(response)
|
|
|
|
|
|
- @override_settings(CSRF_COOKIE_DOMAIN='.example.com', DEBUG=True)
|
|
|
- def test_https_reject_insecure_referer(self):
|
|
|
+ def test_ensures_csrf_cookie_no_logging(self):
|
|
|
"""
|
|
|
- A POST HTTPS request from an insecure referer should be rejected.
|
|
|
+ ensure_csrf_cookie() doesn't log warnings (#19436).
|
|
|
"""
|
|
|
- req = self._get_POST_request_with_token()
|
|
|
- req._is_secure_override = True
|
|
|
- req.META['HTTP_REFERER'] = 'http://example.com/'
|
|
|
- req.META['SERVER_PORT'] = '443'
|
|
|
- response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
|
|
- self.assertContains(
|
|
|
- response,
|
|
|
- 'Referer checking failed - Referer is insecure while host is secure.',
|
|
|
- status_code=403,
|
|
|
- )
|
|
|
+ @ensure_csrf_cookie
|
|
|
+ def view(request):
|
|
|
+ # Doesn't insert a token or anything
|
|
|
+ return HttpResponse(content="")
|
|
|
+
|
|
|
+ class TestHandler(logging.Handler):
|
|
|
+ def emit(self, record):
|
|
|
+ raise Exception("This shouldn't have happened!")
|
|
|
+
|
|
|
+ logger = logging.getLogger('django.request')
|
|
|
+ test_handler = TestHandler()
|
|
|
+ old_log_level = logger.level
|
|
|
+ try:
|
|
|
+ logger.addHandler(test_handler)
|
|
|
+ logger.setLevel(logging.WARNING)
|
|
|
+
|
|
|
+ req = self._get_GET_no_csrf_cookie_request()
|
|
|
+ view(req)
|
|
|
+ finally:
|
|
|
+ logger.removeHandler(test_handler)
|
|
|
+ logger.setLevel(old_log_level)
|
|
|
+
|
|
|
+ def test_post_data_read_failure(self):
|
|
|
+ """
|
|
|
+ #20128 -- IOErrors during POST data reading should be caught and
|
|
|
+ treated as if the POST data wasn't there.
|
|
|
+ """
|
|
|
+ class CsrfPostRequest(HttpRequest):
|
|
|
+ """
|
|
|
+ HttpRequest that can raise an IOError when accessing POST data
|
|
|
+ """
|
|
|
+ def __init__(self, token, raise_error):
|
|
|
+ super(CsrfPostRequest, self).__init__()
|
|
|
+ self.method = 'POST'
|
|
|
+
|
|
|
+ self.raise_error = False
|
|
|
+ self.COOKIES[settings.CSRF_COOKIE_NAME] = token
|
|
|
+
|
|
|
+ # Handle both cases here to prevent duplicate code in the
|
|
|
+ # session tests.
|
|
|
+ self.session = {}
|
|
|
+ self.session[CSRF_SESSION_KEY] = token
|
|
|
+
|
|
|
+ self.POST['csrfmiddlewaretoken'] = token
|
|
|
+ self.raise_error = raise_error
|
|
|
+
|
|
|
+ def _load_post_and_files(self):
|
|
|
+ raise IOError('error reading input data')
|
|
|
+
|
|
|
+ def _get_post(self):
|
|
|
+ if self.raise_error:
|
|
|
+ self._load_post_and_files()
|
|
|
+ return self._post
|
|
|
+
|
|
|
+ def _set_post(self, post):
|
|
|
+ self._post = post
|
|
|
+
|
|
|
+ POST = property(_get_post, _set_post)
|
|
|
+
|
|
|
+ token = ('ABC' + self._csrf_id)[:CSRF_TOKEN_LENGTH]
|
|
|
+
|
|
|
+ req = CsrfPostRequest(token, raise_error=False)
|
|
|
+ resp = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
|
|
+ self.assertIsNone(resp)
|
|
|
+
|
|
|
+ req = CsrfPostRequest(token, raise_error=True)
|
|
|
+ with patch_logger('django.security.csrf', 'warning') as logger_calls:
|
|
|
+ resp = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
|
|
+ self.assertEqual(resp.status_code, 403)
|
|
|
+ self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_BAD_TOKEN)
|
|
|
+
|
|
|
+
|
|
|
+class CsrfViewMiddlewareTests(CsrfViewMiddlewareTestMixin, SimpleTestCase):
|
|
|
+
|
|
|
+ def _get_GET_csrf_cookie_request(self):
|
|
|
+ req = TestingHttpRequest()
|
|
|
+ req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id_cookie
|
|
|
+ return req
|
|
|
+
|
|
|
+ def _get_POST_bare_secret_csrf_cookie_request(self):
|
|
|
+ req = self._get_POST_no_csrf_cookie_request()
|
|
|
+ req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id_cookie[:32]
|
|
|
+ return req
|
|
|
+
|
|
|
+ def _get_POST_bare_secret_csrf_cookie_request_with_token(self):
|
|
|
+ req = self._get_POST_bare_secret_csrf_cookie_request()
|
|
|
+ req.POST['csrfmiddlewaretoken'] = self._csrf_id_cookie[:32]
|
|
|
+ return req
|
|
|
|
|
|
def test_ensures_csrf_cookie_no_middleware(self):
|
|
|
"""
|
|
@@ -582,32 +559,6 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
|
|
self.assertTrue(resp2.cookies.get(settings.CSRF_COOKIE_NAME, False))
|
|
|
self.assertIn('Cookie', resp2.get('Vary', ''))
|
|
|
|
|
|
- def test_ensures_csrf_cookie_no_logging(self):
|
|
|
- """
|
|
|
- ensure_csrf_cookie() doesn't log warnings (#19436).
|
|
|
- """
|
|
|
- @ensure_csrf_cookie
|
|
|
- def view(request):
|
|
|
- # Doesn't insert a token or anything
|
|
|
- return HttpResponse(content="")
|
|
|
-
|
|
|
- class TestHandler(logging.Handler):
|
|
|
- def emit(self, record):
|
|
|
- raise Exception("This shouldn't have happened!")
|
|
|
-
|
|
|
- logger = logging.getLogger('django.request')
|
|
|
- test_handler = TestHandler()
|
|
|
- old_log_level = logger.level
|
|
|
- try:
|
|
|
- logger.addHandler(test_handler)
|
|
|
- logger.setLevel(logging.WARNING)
|
|
|
-
|
|
|
- req = self._get_GET_no_csrf_cookie_request()
|
|
|
- view(req)
|
|
|
- finally:
|
|
|
- logger.removeHandler(test_handler)
|
|
|
- logger.setLevel(old_log_level)
|
|
|
-
|
|
|
def test_csrf_cookie_age(self):
|
|
|
"""
|
|
|
CSRF cookie age can be set using settings.CSRF_COOKIE_AGE.
|
|
@@ -651,45 +602,209 @@ class CsrfViewMiddlewareTest(SimpleTestCase):
|
|
|
max_age = resp2.cookies.get('csrfcookie').get('max-age')
|
|
|
self.assertEqual(max_age, '')
|
|
|
|
|
|
- def test_post_data_read_failure(self):
|
|
|
+ def test_process_view_token_too_long(self):
|
|
|
"""
|
|
|
- #20128 -- IOErrors during POST data reading should be caught and
|
|
|
- treated as if the POST data wasn't there.
|
|
|
+ If the token is longer than expected, it is ignored and a new token is
|
|
|
+ created.
|
|
|
"""
|
|
|
- class CsrfPostRequest(HttpRequest):
|
|
|
- """
|
|
|
- HttpRequest that can raise an IOError when accessing POST data
|
|
|
- """
|
|
|
- def __init__(self, token, raise_error):
|
|
|
- super(CsrfPostRequest, self).__init__()
|
|
|
- self.method = 'POST'
|
|
|
+ req = self._get_GET_no_csrf_cookie_request()
|
|
|
+ req.COOKIES[settings.CSRF_COOKIE_NAME] = 'x' * 100000
|
|
|
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
|
|
|
+ resp = token_view(req)
|
|
|
+ resp2 = CsrfViewMiddleware().process_response(req, resp)
|
|
|
+ csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
|
|
|
+ self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH)
|
|
|
|
|
|
- self.raise_error = False
|
|
|
- self.COOKIES[settings.CSRF_COOKIE_NAME] = token
|
|
|
- self.POST['csrfmiddlewaretoken'] = token
|
|
|
- self.raise_error = raise_error
|
|
|
+ def test_process_view_token_invalid_bytes(self):
|
|
|
+ """
|
|
|
+ If the token contains improperly encoded unicode, it is ignored and a
|
|
|
+ new token is created.
|
|
|
+ """
|
|
|
+ token = (b"<1>\xc2\xa1" + force_bytes(self._csrf_id, 'ascii'))[:CSRF_TOKEN_LENGTH]
|
|
|
+ req = self._get_GET_no_csrf_cookie_request()
|
|
|
+ req.COOKIES[settings.CSRF_COOKIE_NAME] = token
|
|
|
+ # We expect a UnicodeWarning here, because we used broken utf-8 on purpose
|
|
|
+ with warnings.catch_warnings():
|
|
|
+ warnings.filterwarnings("ignore", category=UnicodeWarning)
|
|
|
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
|
|
|
+ resp = token_view(req)
|
|
|
+ resp2 = CsrfViewMiddleware().process_response(req, resp)
|
|
|
+ csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
|
|
|
+ self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH)
|
|
|
+ self.assertNotEqual(csrf_cookie.value, token)
|
|
|
|
|
|
- def _load_post_and_files(self):
|
|
|
- raise IOError('error reading input data')
|
|
|
+ def test_process_view_token_invalid_chars(self):
|
|
|
+ """
|
|
|
+ If the token contains non-alphanumeric characters, it is ignored and a
|
|
|
+ new token is created.
|
|
|
+ """
|
|
|
+ token = ('!@#' + self._csrf_id)[:CSRF_TOKEN_LENGTH]
|
|
|
+ req = self._get_GET_no_csrf_cookie_request()
|
|
|
+ req.COOKIES[settings.CSRF_COOKIE_NAME] = token
|
|
|
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
|
|
|
+ resp = token_view(req)
|
|
|
+ resp2 = CsrfViewMiddleware().process_response(req, resp)
|
|
|
+ csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
|
|
|
+ self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH)
|
|
|
+ self.assertNotEqual(csrf_cookie.value, token)
|
|
|
|
|
|
- def _get_post(self):
|
|
|
- if self.raise_error:
|
|
|
- self._load_post_and_files()
|
|
|
- return self._post
|
|
|
+ def test_bare_secret_accepted_and_replaced(self):
|
|
|
+ """
|
|
|
+ The csrf token is reset from a bare secret.
|
|
|
+ """
|
|
|
+ req = self._get_POST_bare_secret_csrf_cookie_request_with_token()
|
|
|
+ req2 = CsrfViewMiddleware().process_view(req, token_view, (), {})
|
|
|
+ self.assertIsNone(req2)
|
|
|
+ resp = token_view(req)
|
|
|
+ resp = CsrfViewMiddleware().process_response(req, resp)
|
|
|
+ self.assertIn(settings.CSRF_COOKIE_NAME, resp.cookies, "Cookie was not reset from bare secret")
|
|
|
+ csrf_cookie = resp.cookies[settings.CSRF_COOKIE_NAME]
|
|
|
+ self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH)
|
|
|
+ self._check_token_present(resp, csrf_id=csrf_cookie.value)
|
|
|
|
|
|
- def _set_post(self, post):
|
|
|
- self._post = post
|
|
|
+ @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_COOKIE_DOMAIN='.example.com', USE_X_FORWARDED_PORT=True)
|
|
|
+ def test_https_good_referer_behind_proxy(self):
|
|
|
+ """
|
|
|
+ A POST HTTPS request is accepted when USE_X_FORWARDED_PORT=True.
|
|
|
+ """
|
|
|
+ self._test_https_good_referer_behind_proxy()
|
|
|
|
|
|
- POST = property(_get_post, _set_post)
|
|
|
+ @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_COOKIE_DOMAIN='.example.com')
|
|
|
+ def test_https_good_referer_matches_cookie_domain(self):
|
|
|
+ """
|
|
|
+ A POST HTTPS request with a good referer should be accepted from a
|
|
|
+ subdomain that's allowed by CSRF_COOKIE_DOMAIN.
|
|
|
+ """
|
|
|
+ self._test_https_good_referer_matches_cookie_domain()
|
|
|
|
|
|
- token = ('ABC' + self._csrf_id)[:CSRF_TOKEN_LENGTH]
|
|
|
+ @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_COOKIE_DOMAIN='.example.com')
|
|
|
+ def test_https_good_referer_matches_cookie_domain_with_different_port(self):
|
|
|
+ """
|
|
|
+ A POST HTTPS request with a good referer should be accepted from a
|
|
|
+ subdomain that's allowed by CSRF_COOKIE_DOMAIN and a non-443 port.
|
|
|
+ """
|
|
|
+ self._test_https_good_referer_matches_cookie_domain_with_different_port()
|
|
|
|
|
|
- req = CsrfPostRequest(token, raise_error=False)
|
|
|
- resp = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
|
|
- self.assertIsNone(resp)
|
|
|
+ @override_settings(CSRF_COOKIE_DOMAIN='.example.com', DEBUG=True)
|
|
|
+ def test_https_reject_insecure_referer(self):
|
|
|
+ """
|
|
|
+ A POST HTTPS request from an insecure referer should be rejected.
|
|
|
+ """
|
|
|
+ req = self._get_POST_request_with_token()
|
|
|
+ req._is_secure_override = True
|
|
|
+ req.META['HTTP_REFERER'] = 'http://example.com/'
|
|
|
+ req.META['SERVER_PORT'] = '443'
|
|
|
+ response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
|
|
+ self.assertContains(
|
|
|
+ response,
|
|
|
+ 'Referer checking failed - Referer is insecure while host is secure.',
|
|
|
+ status_code=403,
|
|
|
+ )
|
|
|
|
|
|
- req = CsrfPostRequest(token, raise_error=True)
|
|
|
- with patch_logger('django.security.csrf', 'warning') as logger_calls:
|
|
|
- resp = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
|
|
- self.assertEqual(resp.status_code, 403)
|
|
|
- self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_BAD_TOKEN)
|
|
|
+
|
|
|
+@override_settings(CSRF_USE_SESSIONS=True, CSRF_COOKIE_DOMAIN=None)
|
|
|
+class CsrfViewMiddlewareUseSessionsTests(CsrfViewMiddlewareTestMixin, SimpleTestCase):
|
|
|
+ """
|
|
|
+ CSRF tests with CSRF_USE_SESSIONS=True.
|
|
|
+ """
|
|
|
+
|
|
|
+ def _get_POST_bare_secret_csrf_cookie_request(self):
|
|
|
+ req = self._get_POST_no_csrf_cookie_request()
|
|
|
+ req.session[CSRF_SESSION_KEY] = self._csrf_id_cookie[:32]
|
|
|
+ return req
|
|
|
+
|
|
|
+ def _get_GET_csrf_cookie_request(self):
|
|
|
+ req = TestingHttpRequest()
|
|
|
+ req.session[CSRF_SESSION_KEY] = self._csrf_id_cookie
|
|
|
+ return req
|
|
|
+
|
|
|
+ def test_no_session_on_request(self):
|
|
|
+ msg = (
|
|
|
+ 'CSRF_USE_SESSIONS is enabled, but request.session is not set. '
|
|
|
+ 'SessionMiddleware must appear before CsrfViewMiddleware in MIDDLEWARE.'
|
|
|
+ )
|
|
|
+ with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
|
|
+ CsrfViewMiddleware().process_view(HttpRequest(), None, (), {})
|
|
|
+
|
|
|
+ def test_process_response_get_token_used(self):
|
|
|
+ """The ensure_csrf_cookie() decorator works without middleware."""
|
|
|
+ @ensure_csrf_cookie
|
|
|
+ def view(request):
|
|
|
+ # Doesn't insert a token or anything
|
|
|
+ return HttpResponse(content="")
|
|
|
+
|
|
|
+ req = self._get_GET_no_csrf_cookie_request()
|
|
|
+ view(req)
|
|
|
+ self.assertTrue(req.session.get(CSRF_SESSION_KEY, False))
|
|
|
+
|
|
|
+ def test_ensures_csrf_cookie_with_middleware(self):
|
|
|
+ """
|
|
|
+ The ensure_csrf_cookie() decorator works with the CsrfViewMiddleware
|
|
|
+ enabled.
|
|
|
+ """
|
|
|
+ @ensure_csrf_cookie
|
|
|
+ def view(request):
|
|
|
+ # Doesn't insert a token or anything
|
|
|
+ return HttpResponse(content="")
|
|
|
+
|
|
|
+ req = self._get_GET_no_csrf_cookie_request()
|
|
|
+ CsrfViewMiddleware().process_view(req, view, (), {})
|
|
|
+ resp = view(req)
|
|
|
+ CsrfViewMiddleware().process_response(req, resp)
|
|
|
+ self.assertTrue(req.session.get(CSRF_SESSION_KEY, False))
|
|
|
+
|
|
|
+ def test_token_node_with_new_csrf_cookie(self):
|
|
|
+ """
|
|
|
+ CsrfTokenNode works when a CSRF cookie is created by the middleware
|
|
|
+ (when one was not already present).
|
|
|
+ """
|
|
|
+ req = self._get_GET_no_csrf_cookie_request()
|
|
|
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
|
|
|
+ resp = token_view(req)
|
|
|
+ CsrfViewMiddleware().process_response(req, resp)
|
|
|
+ csrf_cookie = req.session[CSRF_SESSION_KEY]
|
|
|
+ self._check_token_present(resp, csrf_id=csrf_cookie)
|
|
|
+
|
|
|
+ @override_settings(
|
|
|
+ ALLOWED_HOSTS=['www.example.com'],
|
|
|
+ SESSION_COOKIE_DOMAIN='.example.com',
|
|
|
+ USE_X_FORWARDED_PORT=True,
|
|
|
+ DEBUG=True,
|
|
|
+ )
|
|
|
+ def test_https_good_referer_behind_proxy(self):
|
|
|
+ """
|
|
|
+ A POST HTTPS request is accepted when USE_X_FORWARDED_PORT=True.
|
|
|
+ """
|
|
|
+ self._test_https_good_referer_behind_proxy()
|
|
|
+
|
|
|
+ @override_settings(ALLOWED_HOSTS=['www.example.com'], SESSION_COOKIE_DOMAIN='.example.com')
|
|
|
+ def test_https_good_referer_matches_cookie_domain(self):
|
|
|
+ """
|
|
|
+ A POST HTTPS request with a good referer should be accepted from a
|
|
|
+ subdomain that's allowed by SESSION_COOKIE_DOMAIN.
|
|
|
+ """
|
|
|
+ self._test_https_good_referer_matches_cookie_domain()
|
|
|
+
|
|
|
+ @override_settings(ALLOWED_HOSTS=['www.example.com'], SESSION_COOKIE_DOMAIN='.example.com')
|
|
|
+ def test_https_good_referer_matches_cookie_domain_with_different_port(self):
|
|
|
+ """
|
|
|
+ A POST HTTPS request with a good referer should be accepted from a
|
|
|
+ subdomain that's allowed by SESSION_COOKIE_DOMAIN and a non-443 port.
|
|
|
+ """
|
|
|
+ self._test_https_good_referer_matches_cookie_domain_with_different_port()
|
|
|
+
|
|
|
+ @override_settings(SESSION_COOKIE_DOMAIN='.example.com', DEBUG=True)
|
|
|
+ def test_https_reject_insecure_referer(self):
|
|
|
+ """
|
|
|
+ A POST HTTPS request from an insecure referer should be rejected.
|
|
|
+ """
|
|
|
+ req = self._get_POST_request_with_token()
|
|
|
+ req._is_secure_override = True
|
|
|
+ req.META['HTTP_REFERER'] = 'http://example.com/'
|
|
|
+ req.META['SERVER_PORT'] = '443'
|
|
|
+ response = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
|
|
+ self.assertContains(
|
|
|
+ response,
|
|
|
+ 'Referer checking failed - Referer is insecure while host is secure.',
|
|
|
+ status_code=403,
|
|
|
+ )
|