Commit 1514f17a authored by Andrew Godwin's avatar Andrew Godwin
Browse files

Rotate CSRF token on login

parent 7e95d7a9
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ import re
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
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

@@ -84,6 +85,7 @@ def login(request, user):
    request.session[BACKEND_SESSION_KEY] = user.backend
    if hasattr(request, 'user'):
        request.user = user
    rotate_token(request)
    user_logged_in.send(sender=user.__class__, request=request, user=user)


+39 −1
Original line number Diff line number Diff line
@@ -12,18 +12,21 @@ from django.contrib.auth.models import User
from django.core import mail
from django.core.exceptions import SuspiciousOperation
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.html import escape
from django.utils.http import urlquote
from django.utils._os import upath
from django.test import TestCase
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.forms import (AuthenticationForm, PasswordChangeForm,
                SetPasswordForm, PasswordResetForm)
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.views import login as login_view


@override_settings(
@@ -460,6 +463,41 @@ class LoginTest(AuthViewsTestCase):
        # the custom authentication form used by this login asserts
        # 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
class LoginURLSettings(AuthViewsTestCase):
+8 −0
Original line number Diff line number Diff line
@@ -53,6 +53,14 @@ def get_token(request):
    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):
    # Allow only alphanum
    if len(token) > CSRF_KEY_LENGTH: