Rotate CSRF token on login

This commit is contained in:
Andrew Godwin 2013-05-23 16:14:27 +01:00
parent 7e95d7a930
commit 1514f17aa6
3 changed files with 49 additions and 1 deletions

View File

@ -3,6 +3,7 @@ import re
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.utils.module_loading import import_by_path from django.utils.module_loading import import_by_path
from django.middleware.csrf import rotate_token
from .signals import user_logged_in, user_logged_out, user_login_failed from .signals import user_logged_in, user_logged_out, user_login_failed
@ -84,6 +85,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

@ -12,18 +12,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(
@ -460,6 +463,41 @@ class LoginTest(AuthViewsTestCase):
# the custom authentication form used by this login asserts # the custom authentication form used by this login asserts
# that a request is passed to the form successfully. # that a request is passed to the form successfully.
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: