Rotate CSRF token on login
This commit is contained in:
parent
7e95d7a930
commit
1514f17aa6
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in New Issue