summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2021-01-12 19:55:02 -0500
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-03-18 20:00:22 +0100
commitdba44a7a7a3581ec722e06fa0f9f33dfc00ed5cd (patch)
treea999c49d722b06cc70740b55be21c8f73ae343eb
parent9bf5e9418f425666726559c9f1981a516da30aab (diff)
Refs #16010 -- Required CSRF_TRUSTED_ORIGINS setting to include the scheme.
-rw-r--r--django/core/checks/__init__.py1
-rw-r--r--django/core/checks/compatibility/django_4_0.py18
-rw-r--r--django/middleware/csrf.py10
-rw-r--r--docs/ref/checks.txt3
-rw-r--r--docs/ref/settings.txt17
-rw-r--r--docs/releases/4.0.txt16
-rw-r--r--tests/check_framework/test_4_0_compatibility.py27
-rw-r--r--tests/csrf_tests/tests.py4
8 files changed, 89 insertions, 7 deletions
diff --git a/django/core/checks/__init__.py b/django/core/checks/__init__.py
index 7eb526a41c..296e991ddc 100644
--- a/django/core/checks/__init__.py
+++ b/django/core/checks/__init__.py
@@ -7,6 +7,7 @@ from .registry import Tags, register, run_checks, tag_exists
# Import these to force registration of checks
import django.core.checks.async_checks # NOQA isort:skip
import django.core.checks.caches # NOQA isort:skip
+import django.core.checks.compatibility.django_4_0 # NOQA isort:skip
import django.core.checks.database # NOQA isort:skip
import django.core.checks.files # NOQA isort:skip
import django.core.checks.model_checks # NOQA isort:skip
diff --git a/django/core/checks/compatibility/django_4_0.py b/django/core/checks/compatibility/django_4_0.py
new file mode 100644
index 0000000000..7788629735
--- /dev/null
+++ b/django/core/checks/compatibility/django_4_0.py
@@ -0,0 +1,18 @@
+from django.conf import settings
+
+from .. import Error, Tags, register
+
+
+@register(Tags.compatibility)
+def check_csrf_trusted_origins(app_configs, **kwargs):
+ errors = []
+ for origin in settings.CSRF_TRUSTED_ORIGINS:
+ if '://' not in origin:
+ errors.append(Error(
+ 'As of Django 4.0, the values in the CSRF_TRUSTED_ORIGINS '
+ 'setting must start with a scheme (usually http:// or '
+ 'https://) but found %s. See the release notes for details.'
+ % origin,
+ id='4_0.E001',
+ ))
+ return errors
diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py
index 368b51f316..10d678db41 100644
--- a/django/middleware/csrf.py
+++ b/django/middleware/csrf.py
@@ -15,6 +15,7 @@ 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
from django.utils.deprecation import MiddlewareMixin
+from django.utils.functional import cached_property
from django.utils.http import is_same_domain
from django.utils.log import log_response
@@ -136,6 +137,13 @@ class CsrfViewMiddleware(MiddlewareMixin):
This middleware should be used in conjunction with the {% csrf_token %}
template tag.
"""
+ @cached_property
+ def csrf_trusted_origins_hosts(self):
+ return [
+ urlparse(origin).netloc.lstrip('*')
+ for origin in settings.CSRF_TRUSTED_ORIGINS
+ ]
+
# The _accept and _reject methods currently only exist for the sake of the
# requires_csrf_token decorator.
def _accept(self, request):
@@ -272,7 +280,7 @@ class CsrfViewMiddleware(MiddlewareMixin):
# Create a list of all acceptable HTTP referers, including the
# current host if it's permitted by ALLOWED_HOSTS.
- good_hosts = list(settings.CSRF_TRUSTED_ORIGINS)
+ good_hosts = list(self.csrf_trusted_origins_hosts)
if good_referer is not None:
good_hosts.append(good_referer)
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index 6e787ef1a4..0dbd15b5c9 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -123,6 +123,9 @@ upgrading Django.
* **2_0.W001**: Your URL pattern ``<pattern>`` has a ``route`` that contains
``(?P<``, begins with a ``^``, or ends with a ``$``. This was likely an
oversight when migrating from ``url()`` to :func:`~django.urls.path`.
+* **4_0.E001**: As of Django 4.0, the values in the
+ :setting:`CSRF_TRUSTED_ORIGINS` setting must start with a scheme (usually
+ ``http://`` or ``https://``) but found ``<hostname>``.
Caches
------
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 18eb941ce8..704bee63a4 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -457,15 +457,24 @@ should be ``'HTTP_X_XSRF_TOKEN'``.
Default: ``[]`` (Empty list)
-A list of hosts which are trusted origins for unsafe requests (e.g. ``POST``).
+A list of trusted origins for unsafe requests (e.g. ``POST``).
+
For a :meth:`secure <django.http.HttpRequest.is_secure>` unsafe
request, Django's CSRF protection requires that the request have a ``Referer``
header that matches the origin present in the ``Host`` header. This prevents,
for example, a ``POST`` request from ``subdomain.example.com`` from succeeding
against ``api.example.com``. If you need cross-origin unsafe requests over
-HTTPS, continuing the example, add ``"subdomain.example.com"`` to this list.
-The setting also supports subdomains, so you could add ``".example.com"``, for
-example, to allow access from all subdomains of ``example.com``.
+HTTPS, continuing the example, add ``'https://subdomain.example.com'`` to this
+list (and/or ``http://...`` if requests originate from an insecure page).
+
+The setting also supports subdomains, so you could add
+``'https://*.example.com'``, for example, to allow access from all subdomains
+of ``example.com``.
+
+.. versionchanged:: 4.0
+
+ The values in older versions must only include the hostname (possibly with
+ a leading dot) and not the scheme or an asterisk.
.. setting:: DATABASES
diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt
index e15413b501..e4107fdad1 100644
--- a/docs/releases/4.0.txt
+++ b/docs/releases/4.0.txt
@@ -307,6 +307,22 @@ Upstream support for Oracle 12.2 ends in March 2022 and for Oracle 18c it ends
in June 2021. Django 3.2 will be supported until April 2024. Django 4.0
officially supports Oracle 19c.
+.. _csrf-trusted-origins-changes-4.0:
+
+``CSRF_TRUSTED_ORIGINS`` changes
+--------------------------------
+
+Format change
+~~~~~~~~~~~~~
+
+Values in the :setting:`CSRF_TRUSTED_ORIGINS` setting must include the scheme
+(e.g. ``'http://'`` or ``'https://'``) instead of only the hostname.
+
+Also, values that started with a dot, must now also include an asterisk before
+the dot. For example, change ``'.example.com'`` to ``'https://*.example.com'``.
+
+A system check detects any required changes.
+
Miscellaneous
-------------
diff --git a/tests/check_framework/test_4_0_compatibility.py b/tests/check_framework/test_4_0_compatibility.py
new file mode 100644
index 0000000000..9f288f252a
--- /dev/null
+++ b/tests/check_framework/test_4_0_compatibility.py
@@ -0,0 +1,27 @@
+from django.core.checks import Error
+from django.core.checks.compatibility.django_4_0 import (
+ check_csrf_trusted_origins,
+)
+from django.test import SimpleTestCase
+from django.test.utils import override_settings
+
+
+class CheckCSRFTrustedOrigins(SimpleTestCase):
+
+ @override_settings(CSRF_TRUSTED_ORIGINS=['example.com'])
+ def test_invalid_url(self):
+ self.assertEqual(check_csrf_trusted_origins(None), [
+ Error(
+ 'As of Django 4.0, the values in the CSRF_TRUSTED_ORIGINS '
+ 'setting must start with a scheme (usually http:// or '
+ 'https://) but found example.com. See the release notes for '
+ 'details.',
+ id='4_0.E001',
+ )
+ ])
+
+ @override_settings(
+ CSRF_TRUSTED_ORIGINS=['http://example.com', 'https://example.com'],
+ )
+ def test_valid_urls(self):
+ self.assertEqual(check_csrf_trusted_origins(None), [])
diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py
index b1b37c8601..f733d25b02 100644
--- a/tests/csrf_tests/tests.py
+++ b/tests/csrf_tests/tests.py
@@ -399,7 +399,7 @@ class CsrfViewMiddlewareTestMixin:
resp = mw.process_view(req, post_form_view, (), {})
self.assertIsNone(resp)
- @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_TRUSTED_ORIGINS=['dashboard.example.com'])
+ @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_TRUSTED_ORIGINS=['https://dashboard.example.com'])
def test_https_csrf_trusted_origin_allowed(self):
"""
A POST HTTPS request with a referer added to the CSRF_TRUSTED_ORIGINS
@@ -414,7 +414,7 @@ class CsrfViewMiddlewareTestMixin:
resp = mw.process_view(req, post_form_view, (), {})
self.assertIsNone(resp)
- @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_TRUSTED_ORIGINS=['.example.com'])
+ @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_TRUSTED_ORIGINS=['https://*.example.com'])
def test_https_csrf_wildcard_trusted_origin_allowed(self):
"""
A POST HTTPS request with a referer that matches a CSRF_TRUSTED_ORIGINS