mirror of https://github.com/django/django.git
Fixed #14597 -- Added a SECURE_PROXY_SSL_HEADER setting for cases when you're behind a proxy that 'swallows' the fact that a request is HTTPS
git-svn-id: http://code.djangoproject.com/svn/django/trunk@17209 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
4d32e6abc2
commit
61f0aff811
|
@ -419,6 +419,15 @@ USE_X_FORWARDED_HOST = False
|
||||||
# actual WSGI application object.
|
# actual WSGI application object.
|
||||||
WSGI_APPLICATION = None
|
WSGI_APPLICATION = None
|
||||||
|
|
||||||
|
# If your Django app is behind a proxy that sets a header to specify secure
|
||||||
|
# connections, AND that proxy ensures that user-submitted headers with the
|
||||||
|
# same name are ignored (so that people can't spoof it), set this value to
|
||||||
|
# a tuple of (header_name, header_value). For any requests that come in with
|
||||||
|
# that header/value, request.is_secure() will return True.
|
||||||
|
# WARNING! Only set this if you fully understand what you're doing. Otherwise,
|
||||||
|
# you may be opening yourself up to a security risk.
|
||||||
|
SECURE_PROXY_SSL_HEADER = None
|
||||||
|
|
||||||
##############
|
##############
|
||||||
# MIDDLEWARE #
|
# MIDDLEWARE #
|
||||||
##############
|
##############
|
||||||
|
|
|
@ -44,7 +44,7 @@ class ModPythonRequest(http.HttpRequest):
|
||||||
# doesn't always happen, so rather than crash, we defensively encode it.
|
# doesn't always happen, so rather than crash, we defensively encode it.
|
||||||
return '%s%s' % (self.path, self._req.args and ('?' + iri_to_uri(self._req.args)) or '')
|
return '%s%s' % (self.path, self._req.args and ('?' + iri_to_uri(self._req.args)) or '')
|
||||||
|
|
||||||
def is_secure(self):
|
def _is_secure(self):
|
||||||
try:
|
try:
|
||||||
return self._req.is_https()
|
return self._req.is_https()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
|
|
@ -158,9 +158,8 @@ class WSGIRequest(http.HttpRequest):
|
||||||
# Rather than crash if this doesn't happen, we encode defensively.
|
# Rather than crash if this doesn't happen, we encode defensively.
|
||||||
return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + iri_to_uri(self.environ.get('QUERY_STRING', ''))) or '')
|
return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + iri_to_uri(self.environ.get('QUERY_STRING', ''))) or '')
|
||||||
|
|
||||||
def is_secure(self):
|
def _is_secure(self):
|
||||||
return 'wsgi.url_scheme' in self.environ \
|
return 'wsgi.url_scheme' in self.environ and self.environ['wsgi.url_scheme'] == 'https'
|
||||||
and self.environ['wsgi.url_scheme'] == 'https'
|
|
||||||
|
|
||||||
def _get_request(self):
|
def _get_request(self):
|
||||||
if not hasattr(self, '_request'):
|
if not hasattr(self, '_request'):
|
||||||
|
|
|
@ -113,6 +113,7 @@ class CompatCookie(SimpleCookie):
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.files import uploadhandler
|
from django.core.files import uploadhandler
|
||||||
from django.http.multipartparser import MultiPartParser
|
from django.http.multipartparser import MultiPartParser
|
||||||
from django.http.utils import *
|
from django.http.utils import *
|
||||||
|
@ -251,9 +252,23 @@ class HttpRequest(object):
|
||||||
location = urljoin(current_uri, location)
|
location = urljoin(current_uri, location)
|
||||||
return iri_to_uri(location)
|
return iri_to_uri(location)
|
||||||
|
|
||||||
def is_secure(self):
|
def _is_secure(self):
|
||||||
return os.environ.get("HTTPS") == "on"
|
return os.environ.get("HTTPS") == "on"
|
||||||
|
|
||||||
|
def is_secure(self):
|
||||||
|
# First, check the SECURE_PROXY_SSL_HEADER setting.
|
||||||
|
if settings.SECURE_PROXY_SSL_HEADER:
|
||||||
|
try:
|
||||||
|
header, value = settings.SECURE_PROXY_SSL_HEADER
|
||||||
|
except ValueError:
|
||||||
|
raise ImproperlyConfigured('The SECURE_PROXY_SSL_HEADER setting must be a tuple containing two values.')
|
||||||
|
if self.META.get(header, None) == value:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Failing that, fall back to _is_secure(), which is a hook for
|
||||||
|
# subclasses to implement.
|
||||||
|
return self._is_secure()
|
||||||
|
|
||||||
def is_ajax(self):
|
def is_ajax(self):
|
||||||
return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
|
return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
|
||||||
|
|
||||||
|
|
|
@ -1530,6 +1530,64 @@ better. ``django-admin.py startproject`` creates one automatically.
|
||||||
|
|
||||||
.. setting:: SEND_BROKEN_LINK_EMAILS
|
.. setting:: SEND_BROKEN_LINK_EMAILS
|
||||||
|
|
||||||
|
SECURE_PROXY_SSL_HEADER
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. versionadded:: 1.4
|
||||||
|
|
||||||
|
Default: ``None``
|
||||||
|
|
||||||
|
A tuple representing a HTTP header/value combination that signifies a request
|
||||||
|
is secure. This controls the behavior of the request object's ``is_secure()``
|
||||||
|
method.
|
||||||
|
|
||||||
|
This takes some explanation. By default, ``is_secure()`` is able to determine
|
||||||
|
whether a request is secure by looking at whether the requested URL uses
|
||||||
|
"https://".
|
||||||
|
|
||||||
|
If your Django app is behind a proxy, though, the proxy may be "swallowing" the
|
||||||
|
fact that a request is HTTPS, using a non-HTTPS connection between the proxy
|
||||||
|
and Django. In this case, ``is_secure()`` would always return ``False`` -- even
|
||||||
|
for requests that were made via HTTPS by the end user.
|
||||||
|
|
||||||
|
In this situation, you'll want to configure your proxy to set a custom HTTP
|
||||||
|
header that tells Django whether the request came in via HTTPS, and you'll want
|
||||||
|
to set ``SECURE_PROXY_SSL_HEADER`` so that Django knows what header to look
|
||||||
|
for.
|
||||||
|
|
||||||
|
You'll need to set a tuple with two elements -- the name of the header to look
|
||||||
|
for and the required value. For example::
|
||||||
|
|
||||||
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
|
||||||
|
|
||||||
|
Here, we're telling Django that we trust the ``X-Forwarded-Protocol`` header
|
||||||
|
that comes from our proxy, and any time its value is ``'https'``, then the
|
||||||
|
request is guaranteed to be secure (i.e., it originally came in via HTTPS).
|
||||||
|
Obviously, you should *only* set this setting if you control your proxy or
|
||||||
|
have some other guarantee that it sets/strips this header appropriately.
|
||||||
|
|
||||||
|
Note that the header needs to be in the format as used by ``request.META`` --
|
||||||
|
all caps and likely starting with ``HTTP_``. (Remember, Django automatically
|
||||||
|
adds ``'HTTP_'`` to the start of x-header names before making the header
|
||||||
|
available in ``request.META``.)
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
**You will probably open security holes in your site if you set this without knowing what you're doing. Seriously.**
|
||||||
|
|
||||||
|
Make sure ALL of the following are true before setting this (assuming the
|
||||||
|
values from the example above):
|
||||||
|
|
||||||
|
* Your Django app is behind a proxy.
|
||||||
|
* Your proxy strips the 'X-Forwarded-Protocol' header from all incoming
|
||||||
|
requests. In other words, if end users include that header in their
|
||||||
|
requests, the proxy will discard it.
|
||||||
|
* Your proxy sets the 'X-Forwarded-Protocol' header and sends it to Django,
|
||||||
|
but only for requests that originally come in via HTTPS.
|
||||||
|
|
||||||
|
If any of those are not true, you should keep this setting set to ``None``
|
||||||
|
and find another way of determining HTTPS, perhaps via custom middleware.
|
||||||
|
|
||||||
SEND_BROKEN_LINK_EMAILS
|
SEND_BROKEN_LINK_EMAILS
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import with_statement
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.conf import settings, global_settings
|
from django.conf import settings, global_settings
|
||||||
|
from django.http import HttpRequest
|
||||||
from django.test import TransactionTestCase, TestCase, signals
|
from django.test import TransactionTestCase, TestCase, signals
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
@ -209,6 +210,36 @@ class TrailingSlashURLTests(TestCase):
|
||||||
self.assertEqual('http://media.foo.com/stupid//',
|
self.assertEqual('http://media.foo.com/stupid//',
|
||||||
self.settings_module.MEDIA_URL)
|
self.settings_module.MEDIA_URL)
|
||||||
|
|
||||||
|
class SecureProxySslHeaderTest(TestCase):
|
||||||
|
settings_module = settings
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self._original_setting = self.settings_module.SECURE_PROXY_SSL_HEADER
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.settings_module.SECURE_PROXY_SSL_HEADER = self._original_setting
|
||||||
|
|
||||||
|
def test_none(self):
|
||||||
|
self.settings_module.SECURE_PROXY_SSL_HEADER = None
|
||||||
|
req = HttpRequest()
|
||||||
|
self.assertEqual(req.is_secure(), False)
|
||||||
|
|
||||||
|
def test_set_without_xheader(self):
|
||||||
|
self.settings_module.SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
|
||||||
|
req = HttpRequest()
|
||||||
|
self.assertEqual(req.is_secure(), False)
|
||||||
|
|
||||||
|
def test_set_with_xheader_wrong(self):
|
||||||
|
self.settings_module.SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
|
||||||
|
req = HttpRequest()
|
||||||
|
req.META['HTTP_X_FORWARDED_PROTOCOL'] = 'wrongvalue'
|
||||||
|
self.assertEqual(req.is_secure(), False)
|
||||||
|
|
||||||
|
def test_set_with_xheader_right(self):
|
||||||
|
self.settings_module.SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
|
||||||
|
req = HttpRequest()
|
||||||
|
req.META['HTTP_X_FORWARDED_PROTOCOL'] = 'https'
|
||||||
|
self.assertEqual(req.is_secure(), True)
|
||||||
|
|
||||||
class EnvironmentVariableTest(TestCase):
|
class EnvironmentVariableTest(TestCase):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue