Fixed #33569 -- Added SECURE_PROXY_SSL_HEADER support for list of protocols in the header value.

This commit is contained in:
Thomas Schmidt 2022-03-07 17:25:52 -05:00 committed by Mariusz Felisiak
parent d46e158ee2
commit 1cf60ce601
4 changed files with 39 additions and 5 deletions

View File

@ -261,7 +261,8 @@ class HttpRequest:
) )
header_value = self.META.get(header) header_value = self.META.get(header)
if header_value is not None: if header_value is not None:
return "https" if header_value == secure_value else "http" header_value, *_ = header_value.split(",", 1)
return "https" if header_value.strip() == secure_value else "http"
return self._get_scheme() return self._get_scheme()
def is_secure(self): def is_secure(self):

View File

@ -2442,8 +2442,17 @@ required value. For example::
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
This tells Django to trust the ``X-Forwarded-Proto`` header that comes from our This tells Django to trust the ``X-Forwarded-Proto`` header that comes from our
proxy, and any time its value is ``'https'``, then the request is guaranteed to proxy and that the request is guaranteed to be secure (i.e., it originally came
be secure (i.e., it originally came in via HTTPS). in via HTTPS) when:
* the header value is ``'https'``, or
* its initial, leftmost value is ``'https'`` in the case of a comma-separated
list of protocols (e.g. ``'https,http,http'``).
.. versionchanged:: 4.1
Support for a comma-separated list of protocols in the header value was
added.
You should *only* set this setting if you control your proxy or have some other You should *only* set this setting if you control your proxy or have some other
guarantee that it sets/strips this header appropriately. guarantee that it sets/strips this header appropriately.
@ -2463,8 +2472,9 @@ available in ``request.META``.)
* Your Django app is behind a proxy. * Your Django app is behind a proxy.
* Your proxy strips the ``X-Forwarded-Proto`` header from all incoming * Your proxy strips the ``X-Forwarded-Proto`` header from all incoming
requests. In other words, if end users include that header in their requests, even when it contains a comma-separated list of protocols. In
requests, the proxy will discard it. other words, if end users include that header in their requests, the
proxy will discard it.
* Your proxy sets the ``X-Forwarded-Proto`` header and sends it to Django, * Your proxy sets the ``X-Forwarded-Proto`` header and sends it to Django,
but only for requests that originally come in via HTTPS. but only for requests that originally come in via HTTPS.

View File

@ -293,6 +293,9 @@ Security
* The new :setting:`SECRET_KEY_FALLBACKS` setting allows providing a list of * The new :setting:`SECRET_KEY_FALLBACKS` setting allows providing a list of
values for secret key rotation. values for secret key rotation.
* The :setting:`SECURE_PROXY_SSL_HEADER` setting now supports a comma-separated
list of protocols in the header value.
Serialization Serialization
~~~~~~~~~~~~~ ~~~~~~~~~~~~~

View File

@ -424,6 +424,26 @@ class SecureProxySslHeaderTest(SimpleTestCase):
req.META["HTTP_X_FORWARDED_PROTO"] = "https" req.META["HTTP_X_FORWARDED_PROTO"] = "https"
self.assertIs(req.is_secure(), True) self.assertIs(req.is_secure(), True)
@override_settings(SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https"))
def test_set_with_xheader_leftmost_right(self):
req = HttpRequest()
req.META["HTTP_X_FORWARDED_PROTO"] = "https, http"
self.assertIs(req.is_secure(), True)
req.META["HTTP_X_FORWARDED_PROTO"] = "https , http"
self.assertIs(req.is_secure(), True)
@override_settings(SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https"))
def test_set_with_xheader_leftmost_not_secure(self):
req = HttpRequest()
req.META["HTTP_X_FORWARDED_PROTO"] = "http, https"
self.assertIs(req.is_secure(), False)
@override_settings(SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https"))
def test_set_with_xheader_multiple_not_secure(self):
req = HttpRequest()
req.META["HTTP_X_FORWARDED_PROTO"] = "http ,wrongvalue,http,http"
self.assertIs(req.is_secure(), False)
@override_settings(SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https")) @override_settings(SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https"))
def test_xheader_preferred_to_underlying_request(self): def test_xheader_preferred_to_underlying_request(self):
class ProxyRequest(HttpRequest): class ProxyRequest(HttpRequest):