Commit 8b001486 authored by Ryan Kaskel's avatar Ryan Kaskel Committed by Tim Graham
Browse files

Fixed #19321 -- Allowed redirect middleware HTTP responses to be overridden.

Thanks Melevir for the suggestion.
parent 36e220f9
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -8,6 +8,11 @@ from django import http


class RedirectFallbackMiddleware(object):

    # Defined as class-level attributes to be subclassing-friendly.
    response_gone_class = http.HttpResponseGone
    response_redirect_class = http.HttpResponsePermanentRedirect

    def __init__(self):
        if 'django.contrib.sites' not in settings.INSTALLED_APPS:
            raise ImproperlyConfigured(
@@ -16,8 +21,9 @@ class RedirectFallbackMiddleware(object):
            )

    def process_response(self, request, response):
        # No need to check for a redirect for non-404 responses.
        if response.status_code != 404:
            return response # No need to check for a redirect for non-404 responses.
            return response

        full_path = request.get_full_path()
        current_site = get_current_site(request)
@@ -37,8 +43,8 @@ class RedirectFallbackMiddleware(object):
                pass
        if r is not None:
            if r.new_path == '':
                return http.HttpResponseGone()
            return http.HttpResponsePermanentRedirect(r.new_path)
                return self.response_gone_class()
            return self.response_redirect_class(r.new_path)

        # No redirect was found. Return the response.
        return response
+30 −0
Original line number Diff line number Diff line
from django import http
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.exceptions import ImproperlyConfigured
@@ -61,3 +62,32 @@ class RedirectTests(TestCase):
    def test_sites_not_installed(self):
        with self.assertRaises(ImproperlyConfigured):
            RedirectFallbackMiddleware()


class OverriddenRedirectFallbackMiddleware(RedirectFallbackMiddleware):
    # Use HTTP responses different from the defaults
    response_gone_class = http.HttpResponseForbidden
    response_redirect_class = http.HttpResponseRedirect


@override_settings(
    MIDDLEWARE_CLASSES=list(settings.MIDDLEWARE_CLASSES) +
        ['django.contrib.redirects.tests.OverriddenRedirectFallbackMiddleware'],
    SITE_ID=1,
)
class OverriddenRedirectMiddlewareTests(TestCase):

    def setUp(self):
        self.site = Site.objects.get(pk=settings.SITE_ID)

    def test_response_gone_class(self):
        Redirect.objects.create(
            site=self.site, old_path='/initial/', new_path='')
        response = self.client.get('/initial/')
        self.assertEqual(response.status_code, 403)

    def test_response_redirect_class(self):
        Redirect.objects.create(
            site=self.site, old_path='/initial/', new_path='/new_target/')
        response = self.client.get('/initial/')
        self.assertEqual(response.status_code, 302)
+33 −6
Original line number Diff line number Diff line
@@ -26,10 +26,11 @@ How it works
``manage.py migrate`` creates a ``django_redirect`` table in your database. This
is a simple lookup table with ``site_id``, ``old_path`` and ``new_path`` fields.

The ``RedirectFallbackMiddleware`` does all of the work. Each time any Django
application raises a 404 error, this middleware checks the redirects database
for the requested URL as a last resort. Specifically, it checks for a redirect
with the given ``old_path`` with a site ID that corresponds to the
The :class:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware`
does all of the work. Each time any Django application raises a 404
error, this middleware checks the redirects database for the requested
URL as a last resort. Specifically, it checks for a redirect with the
given ``old_path`` with a site ID that corresponds to the
:setting:`SITE_ID` setting.

* If it finds a match, and ``new_path`` is not empty, it redirects to
@@ -43,8 +44,8 @@ The middleware only gets activated for 404s -- not for 500s or responses of any
other status code.

Note that the order of :setting:`MIDDLEWARE_CLASSES` matters. Generally, you
can put ``RedirectFallbackMiddleware`` at the end of the list, because it's a
last resort.
can put :class:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware`
at the end of the list, because it's a last resort.

For more on middleware, read the :doc:`middleware docs
</topics/http/middleware>`.
@@ -69,3 +70,29 @@ Via the Python API
    objects via the :doc:`Django database API </topics/db/queries>`.

.. _django/contrib/redirects/models.py: https://github.com/django/django/blob/master/django/contrib/redirects/models.py

Middleware
==========

.. class:: middleware.RedirectFallbackMiddleware

    You can change the :class:`~django.http.HttpResponse` classes used
    by the middleware by creating a subclass of
    :class:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware`
    and overriding ``response_gone_class`` and/or ``response_redirect_class``.

    .. attribute:: response_gone_class

        The :class:`~django.http.HttpResponse` class used when a
        :class:`~django.contrib.redirects.models.Redirect` is not
        found for the requested path or has a blank ``new_path``
        value.

        Defaults to :class:`~django.http.HttpResponseGone`.

    .. attribute:: response_redirect_class

        The :class:`~django.http.HttpResponse` class that handles the
        redirect.

        Defaults to :class:`~django.http.HttpResponsePermanentRedirect`.
+11 −0
Original line number Diff line number Diff line
@@ -193,6 +193,17 @@ Minor features
  follow the :setting:`SESSION_COOKIE_SECURE` and
  :setting:`SESSION_COOKIE_HTTPONLY` settings.

:mod:`django.contrib.redirects`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

* :class:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware`
  has two new attributes
  (:attr:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware.response_gone_class`
  and
  :attr:`~django.contrib.redirects.middleware.RedirectFallbackMiddleware.response_redirect_class`)
  that specify the types of :class:`~django.http.HttpResponse` instances the
  middleware returns.

:mod:`django.contrib.sessions`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^