Fixed #28699 -- Fixed CSRF validation with remote user middleware.

Ensured process_view() always accesses the CSRF token from the session
or cookie, rather than the request, as rotate_token() may have been called
by an authentication middleware during the process_request() phase.
This commit is contained in:
Colton Hicks 2020-01-31 23:42:24 -08:00 committed by Carlton Gibson
parent bc1c034076
commit f283ffaa84
3 changed files with 36 additions and 2 deletions

View File

@ -201,6 +201,7 @@ answer newbie questions, and generally made Django that much better:
Colin Wood <cwood06@gmail.com> Colin Wood <cwood06@gmail.com>
Collin Anderson <cmawebsite@gmail.com> Collin Anderson <cmawebsite@gmail.com>
Collin Grady <collin@collingrady.com> Collin Grady <collin@collingrady.com>
Colton Hicks <coltonbhicks@gmail.com>
Craig Blaszczyk <masterjakul@gmail.com> Craig Blaszczyk <masterjakul@gmail.com>
crankycoder@gmail.com crankycoder@gmail.com
Curtis Maloney (FunkyBob) <curtis@tinbrain.net> Curtis Maloney (FunkyBob) <curtis@tinbrain.net>

View File

@ -280,7 +280,10 @@ class CsrfViewMiddleware(MiddlewareMixin):
reason = REASON_BAD_REFERER % referer.geturl() reason = REASON_BAD_REFERER % referer.geturl()
return self._reject(request, reason) return self._reject(request, reason)
csrf_token = request.META.get('CSRF_COOKIE') # Access csrf_token via self._get_token() as rotate_token() may
# have been called by an authentication middleware during the
# process_request() phase.
csrf_token = self._get_token(request)
if csrf_token is None: if csrf_token is None:
# No CSRF cookie. For POST requests, we insist on a CSRF cookie, # No CSRF cookie. For POST requests, we insist on a CSRF cookie,
# and in this way we can avoid all CSRF attacks, including login # and in this way we can avoid all CSRF attacks, including login

View File

@ -5,7 +5,8 @@ from django.contrib.auth import authenticate
from django.contrib.auth.backends import RemoteUserBackend from django.contrib.auth.backends import RemoteUserBackend
from django.contrib.auth.middleware import RemoteUserMiddleware from django.contrib.auth.middleware import RemoteUserMiddleware
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.test import TestCase, modify_settings, override_settings from django.middleware.csrf import _get_new_csrf_string, _mask_cipher_secret
from django.test import Client, TestCase, modify_settings, override_settings
from django.utils import timezone from django.utils import timezone
@ -50,6 +51,35 @@ class RemoteUserTest(TestCase):
self.assertTrue(response.context['user'].is_anonymous) self.assertTrue(response.context['user'].is_anonymous)
self.assertEqual(User.objects.count(), num_users) self.assertEqual(User.objects.count(), num_users)
def test_csrf_validation_passes_after_process_request_login(self):
"""
CSRF check must access the CSRF token from the session or cookie,
rather than the request, as rotate_token() may have been called by an
authentication middleware during the process_request() phase.
"""
csrf_client = Client(enforce_csrf_checks=True)
csrf_secret = _get_new_csrf_string()
csrf_token = _mask_cipher_secret(csrf_secret)
csrf_token_form = _mask_cipher_secret(csrf_secret)
headers = {self.header: 'fakeuser'}
data = {'csrfmiddlewaretoken': csrf_token_form}
# Verify that CSRF is configured for the view
csrf_client.cookies.load({settings.CSRF_COOKIE_NAME: csrf_token})
response = csrf_client.post('/remote_user/', **headers)
self.assertEqual(response.status_code, 403)
self.assertIn(b'CSRF verification failed.', response.content)
# This request will call django.contrib.auth.login() which will call
# django.middleware.csrf.rotate_token() thus changing the value of
# request.META['CSRF_COOKIE'] from the user submitted value set by
# CsrfViewMiddleware.process_request() to the new csrftoken value set
# by rotate_token(). Csrf validation should still pass when the view is
# later processed by CsrfViewMiddleware.process_view()
csrf_client.cookies.load({settings.CSRF_COOKIE_NAME: csrf_token})
response = csrf_client.post('/remote_user/', data, **headers)
self.assertEqual(response.status_code, 200)
def test_unknown_user(self): def test_unknown_user(self):
""" """
Tests the case where the username passed in the header does not exist Tests the case where the username passed in the header does not exist