[1.5.x] Rotate CSRF token on login

Backport of 1514f17aa60772f48839130f9a5071b9ffe15213 from master
This commit is contained in:
Andrew Godwin 2013-05-23 16:14:27 +01:00
parent 82fc3ce41d
commit fd48d2d438
3 changed files with 49 additions and 1 deletions

View File

@ -2,6 +2,7 @@ import re
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.importlib import import_module from django.utils.importlib import import_module
from django.middleware.csrf import rotate_token
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
SESSION_KEY = '_auth_user_id' SESSION_KEY = '_auth_user_id'
@ -92,6 +93,7 @@ def login(request, user):
request.session[BACKEND_SESSION_KEY] = user.backend request.session[BACKEND_SESSION_KEY] = user.backend
if hasattr(request, 'user'): if hasattr(request, 'user'):
request.user = user request.user = user
rotate_token(request)
user_logged_in.send(sender=user.__class__, request=request, user=user) user_logged_in.send(sender=user.__class__, request=request, user=user)

View File

@ -7,18 +7,21 @@ from django.contrib.auth.models import User
from django.core import mail from django.core import mail
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
from django.core.urlresolvers import reverse, NoReverseMatch from django.core.urlresolvers import reverse, NoReverseMatch
from django.http import QueryDict from django.http import QueryDict, HttpRequest
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.html import escape from django.utils.html import escape
from django.utils.http import urlquote from django.utils.http import urlquote
from django.utils._os import upath from django.utils._os import upath
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.middleware.csrf import CsrfViewMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME from django.contrib.auth import SESSION_KEY, REDIRECT_FIELD_NAME
from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm, from django.contrib.auth.forms import (AuthenticationForm, PasswordChangeForm,
SetPasswordForm, PasswordResetForm) SetPasswordForm, PasswordResetForm)
from django.contrib.auth.tests.utils import skipIfCustomUser from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.views import login as login_view
@override_settings( @override_settings(
@ -361,6 +364,41 @@ class LoginTest(AuthViewsTestCase):
self.assertTrue(good_url in response['Location'], self.assertTrue(good_url in response['Location'],
"%s should be allowed" % good_url) "%s should be allowed" % good_url)
def test_login_csrf_rotate(self, password='password'):
"""
Makes sure that a login rotates the currently-used CSRF token.
"""
# Do a GET to establish a CSRF token
# TestClient isn't used here as we're testing middleware, essentially.
req = HttpRequest()
CsrfViewMiddleware().process_view(req, login_view, (), {})
req.META["CSRF_COOKIE_USED"] = True
resp = login_view(req)
resp2 = CsrfViewMiddleware().process_response(req, resp)
csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, None)
token1 = csrf_cookie.coded_value
# Prepare the POST request
req = HttpRequest()
req.COOKIES[settings.CSRF_COOKIE_NAME] = token1
req.method = "POST"
req.POST = {'username': 'testclient', 'password': password, 'csrfmiddlewaretoken': token1}
req.REQUEST = req.POST
# Use POST request to log in
SessionMiddleware().process_request(req)
CsrfViewMiddleware().process_view(req, login_view, (), {})
req.META["SERVER_NAME"] = "testserver" # Required to have redirect work in login view
req.META["SERVER_PORT"] = 80
req.META["CSRF_COOKIE_USED"] = True
resp = login_view(req)
resp2 = CsrfViewMiddleware().process_response(req, resp)
csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, None)
token2 = csrf_cookie.coded_value
# Check the CSRF token switched
self.assertNotEqual(token1, token2)
@skipIfCustomUser @skipIfCustomUser
class LoginURLSettings(AuthViewsTestCase): class LoginURLSettings(AuthViewsTestCase):

View File

@ -53,6 +53,14 @@ def get_token(request):
return request.META.get("CSRF_COOKIE", None) return request.META.get("CSRF_COOKIE", None)
def rotate_token(request):
"""
Changes the CSRF token in use for a request - should be done on login
for security purposes.
"""
request.META["CSRF_COOKIE"] = _get_new_csrf_key()
def _sanitize_token(token): def _sanitize_token(token):
# Allow only alphanum # Allow only alphanum
if len(token) > CSRF_KEY_LENGTH: if len(token) > CSRF_KEY_LENGTH: