summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorRaphael Michel <mail@raphaelmichel.de>2016-06-30 18:42:11 +0200
committerTim Graham <timograham@gmail.com>2016-11-30 08:57:27 -0500
commitddf169cdaca91e92dd5bfe6796bb6f38369ecb68 (patch)
tree4c472f6baad2d938f2889c3a3f2073c7e1724a06 /django
parentf24eea3b69d9c3139f0145cd48a8962f67671aff (diff)
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.
Diffstat (limited to 'django')
-rw-r--r--django/conf/global_settings.py1
-rw-r--r--django/middleware/csrf.py79
2 files changed, 55 insertions, 25 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index b123c1136b..206d66f15d 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -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 #
diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py
index 3f9a649c27..d7359e4912 100644
--- a/django/middleware/csrf.py
+++ b/django/middleware/csrf.py
@@ -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