mirror of
https://github.com/django/django.git
synced 2025-11-07 07:15:35 +00:00
Refs #16859 -- Allowed storing CSRF tokens in sessions.
Major thanks to Shai for helping to refactor the tests, and to Shai, Tim, Florian, and others for extensive and helpful review.
This commit is contained in:
committed by
Tim Graham
parent
f24eea3b69
commit
ddf169cdac
@@ -548,6 +548,7 @@ CSRF_COOKIE_SECURE = False
|
||||
CSRF_COOKIE_HTTPONLY = False
|
||||
CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN'
|
||||
CSRF_TRUSTED_ORIGINS = []
|
||||
CSRF_USE_SESSIONS = False
|
||||
|
||||
############
|
||||
# MESSAGES #
|
||||
|
||||
@@ -11,6 +11,7 @@ import re
|
||||
import string
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.urls import get_callable
|
||||
from django.utils.cache import patch_vary_headers
|
||||
from django.utils.crypto import constant_time_compare, get_random_string
|
||||
@@ -32,6 +33,7 @@ REASON_INSECURE_REFERER = "Referer checking failed - Referer is insecure while h
|
||||
CSRF_SECRET_LENGTH = 32
|
||||
CSRF_TOKEN_LENGTH = 2 * CSRF_SECRET_LENGTH
|
||||
CSRF_ALLOWED_CHARS = string.ascii_letters + string.digits
|
||||
CSRF_SESSION_KEY = '_csrftoken'
|
||||
|
||||
|
||||
def _get_failure_view():
|
||||
@@ -160,20 +162,51 @@ class CsrfViewMiddleware(MiddlewareMixin):
|
||||
)
|
||||
return _get_failure_view()(request, reason=reason)
|
||||
|
||||
def process_view(self, request, callback, callback_args, callback_kwargs):
|
||||
if getattr(request, 'csrf_processing_done', False):
|
||||
return None
|
||||
|
||||
try:
|
||||
cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME]
|
||||
except KeyError:
|
||||
csrf_token = None
|
||||
def _get_token(self, request):
|
||||
if settings.CSRF_USE_SESSIONS:
|
||||
try:
|
||||
return request.session.get(CSRF_SESSION_KEY)
|
||||
except AttributeError:
|
||||
raise ImproperlyConfigured(
|
||||
'CSRF_USE_SESSIONS is enabled, but request.session is not '
|
||||
'set. SessionMiddleware must appear before CsrfViewMiddleware '
|
||||
'in MIDDLEWARE%s.' % ('_CLASSES' if settings.MIDDLEWARE is None else '')
|
||||
)
|
||||
else:
|
||||
try:
|
||||
cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
csrf_token = _sanitize_token(cookie_token)
|
||||
if csrf_token != cookie_token:
|
||||
# Cookie token needed to be replaced;
|
||||
# the cookie needs to be reset.
|
||||
request.csrf_cookie_needs_reset = True
|
||||
return csrf_token
|
||||
|
||||
def _set_token(self, request, response):
|
||||
if settings.CSRF_USE_SESSIONS:
|
||||
request.session[CSRF_SESSION_KEY] = request.META['CSRF_COOKIE']
|
||||
else:
|
||||
response.set_cookie(
|
||||
settings.CSRF_COOKIE_NAME,
|
||||
request.META['CSRF_COOKIE'],
|
||||
max_age=settings.CSRF_COOKIE_AGE,
|
||||
domain=settings.CSRF_COOKIE_DOMAIN,
|
||||
path=settings.CSRF_COOKIE_PATH,
|
||||
secure=settings.CSRF_COOKIE_SECURE,
|
||||
httponly=settings.CSRF_COOKIE_HTTPONLY,
|
||||
)
|
||||
# Set the Vary header since content varies with the CSRF cookie.
|
||||
patch_vary_headers(response, ('Cookie',))
|
||||
|
||||
def process_view(self, request, callback, callback_args, callback_kwargs):
|
||||
if getattr(request, 'csrf_processing_done', False):
|
||||
return None
|
||||
|
||||
csrf_token = self._get_token(request)
|
||||
if csrf_token is not None:
|
||||
# Use same token next time.
|
||||
request.META['CSRF_COOKIE'] = csrf_token
|
||||
|
||||
@@ -226,16 +259,21 @@ class CsrfViewMiddleware(MiddlewareMixin):
|
||||
if referer.scheme != 'https':
|
||||
return self._reject(request, REASON_INSECURE_REFERER)
|
||||
|
||||
# If there isn't a CSRF_COOKIE_DOMAIN, assume we need an exact
|
||||
# match on host:port. If not, obey the cookie rules.
|
||||
if settings.CSRF_COOKIE_DOMAIN is None:
|
||||
# request.get_host() includes the port.
|
||||
good_referer = request.get_host()
|
||||
else:
|
||||
good_referer = settings.CSRF_COOKIE_DOMAIN
|
||||
# If there isn't a CSRF_COOKIE_DOMAIN, require an exact match
|
||||
# match on host:port. If not, obey the cookie rules (or those
|
||||
# for the session cookie, if CSRF_USE_SESSIONS).
|
||||
good_referer = (
|
||||
settings.SESSION_COOKIE_DOMAIN
|
||||
if settings.CSRF_USE_SESSIONS
|
||||
else settings.CSRF_COOKIE_DOMAIN
|
||||
)
|
||||
if good_referer is not None:
|
||||
server_port = request.get_port()
|
||||
if server_port not in ('443', '80'):
|
||||
good_referer = '%s:%s' % (good_referer, server_port)
|
||||
else:
|
||||
# request.get_host() includes the port.
|
||||
good_referer = request.get_host()
|
||||
|
||||
# Here we generate a list of all acceptable HTTP referers,
|
||||
# including the current host since that has been validated
|
||||
@@ -287,15 +325,6 @@ class CsrfViewMiddleware(MiddlewareMixin):
|
||||
|
||||
# Set the CSRF cookie even if it's already set, so we renew
|
||||
# the expiry timer.
|
||||
response.set_cookie(settings.CSRF_COOKIE_NAME,
|
||||
request.META["CSRF_COOKIE"],
|
||||
max_age=settings.CSRF_COOKIE_AGE,
|
||||
domain=settings.CSRF_COOKIE_DOMAIN,
|
||||
path=settings.CSRF_COOKIE_PATH,
|
||||
secure=settings.CSRF_COOKIE_SECURE,
|
||||
httponly=settings.CSRF_COOKIE_HTTPONLY
|
||||
)
|
||||
# Content varies with the CSRF cookie, so set the Vary header.
|
||||
patch_vary_headers(response, ('Cookie',))
|
||||
self._set_token(request, response)
|
||||
response.csrf_cookie_set = True
|
||||
return response
|
||||
|
||||
Reference in New Issue
Block a user