Commit 61f0aff8 authored by Adrian Holovaty's avatar Adrian Holovaty
Browse files

Fixed #14597 -- Added a SECURE_PROXY_SSL_HEADER setting for cases when you're...

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
parent 4d32e6ab
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -419,6 +419,15 @@ USE_X_FORWARDED_HOST = False
# actual WSGI application object.
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 #
##############
+1 −1
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ class ModPythonRequest(http.HttpRequest):
        # 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 '')

    def is_secure(self):
    def _is_secure(self):
        try:
            return self._req.is_https()
        except AttributeError:
+2 −3
Original line number Diff line number Diff line
@@ -158,9 +158,8 @@ class WSGIRequest(http.HttpRequest):
        # 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 '')

    def is_secure(self):
        return 'wsgi.url_scheme' in self.environ \
            and self.environ['wsgi.url_scheme'] == 'https'
    def _is_secure(self):
        return 'wsgi.url_scheme' in self.environ and self.environ['wsgi.url_scheme'] == 'https'

    def _get_request(self):
        if not hasattr(self, '_request'):
+16 −1
Original line number Diff line number Diff line
@@ -113,6 +113,7 @@ class CompatCookie(SimpleCookie):

from django.conf import settings
from django.core import signing
from django.core.exceptions import ImproperlyConfigured
from django.core.files import uploadhandler
from django.http.multipartparser import MultiPartParser
from django.http.utils import *
@@ -251,9 +252,23 @@ class HttpRequest(object):
            location = urljoin(current_uri, location)
        return iri_to_uri(location)

    def is_secure(self):
    def _is_secure(self):
        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):
        return self.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'

+58 −0
Original line number Diff line number Diff line
@@ -1530,6 +1530,64 @@ better. ``django-admin.py startproject`` creates one automatically.

.. 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
-----------------------

Loading