Commit 36dd7444 authored by Luke Plant's avatar Luke Plant
Browse files

[1.2.X] Fixed #14565 - No csrf_token on 404 page.

This solution doesn't have the negative side-effects of [14356].

Backport of [14377] from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14380 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent fcc283a5
Loading
Loading
Loading
Loading
+21 −17
Original line number Diff line number Diff line
@@ -84,18 +84,22 @@ class CsrfViewMiddleware(object):
    This middleware should be used in conjunction with the csrf_token template
    tag.
    """
    def process_view(self, request, callback, callback_args, callback_kwargs):
        if getattr(request, 'csrf_processing_done', False):
            return None

        reject = lambda s: _get_failure_view()(request, reason=s)
        def accept():
    # The _accept and _reject methods currently only exist for the sake of the
    # requires_csrf_token decorator.
    def _accept(self, request):
        # Avoid checking the request twice by adding a custom attribute to
        # request.  This will be relevant when both decorator and middleware
        # are used.
        request.csrf_processing_done = True
        return None

    def _reject(self, request, reason):
        return _get_failure_view()(request, reason=reason)

    def process_view(self, request, callback, callback_args, callback_kwargs):
        if getattr(request, 'csrf_processing_done', False):
            return None

        # If the user doesn't have a CSRF cookie, generate one and store it in the
        # request, so it's available to the view.  We'll store it in a cookie when
        # we reach the response.
@@ -124,7 +128,7 @@ class CsrfViewMiddleware(object):
                # the creation of CSRF cookies, so that everything else continues to
                # work exactly the same (e.g. cookies are sent etc), but before the
                # any branches that call reject()
                return accept()
                return self._accept(request)

            if request.is_ajax():
                # .is_ajax() is based on the presence of X-Requested-With.  In
@@ -149,19 +153,19 @@ class CsrfViewMiddleware(object):
                #      allowing the cross-domain POST request.
                #
                # So in all cases, it is safe to allow these requests through.
                return accept()
                return self._accept(request)

            if request.is_secure():
                # Strict referer checking for HTTPS
                referer = request.META.get('HTTP_REFERER')
                if referer is None:
                    return reject(REASON_NO_REFERER)
                    return self._reject(request, REASON_NO_REFERER)

                # The following check ensures that the referer is HTTPS,
                # the domains match and the ports match.  This might be too strict.
                good_referer = 'https://%s/' % request.get_host()
                if not referer.startswith(good_referer):
                    return reject(REASON_BAD_REFERER %
                    return self._reject(request, REASON_BAD_REFERER %
                                        (referer, good_referer))

            # If the user didn't already have a CSRF cookie, then fall back to
@@ -176,7 +180,7 @@ class CsrfViewMiddleware(object):
                    # No CSRF cookie and no session cookie. For POST requests,
                    # we insist on a CSRF cookie, and in this way we can avoid
                    # all CSRF attacks, including login CSRF.
                    return reject(REASON_NO_COOKIE)
                    return self._reject(request, REASON_NO_COOKIE)
            else:
                csrf_token = request.META["CSRF_COOKIE"]

@@ -185,11 +189,11 @@ class CsrfViewMiddleware(object):
            if request_csrf_token != csrf_token:
                if cookie_is_new:
                    # probably a problem setting the CSRF cookie
                    return reject(REASON_NO_CSRF_COOKIE)
                    return self._reject(request, REASON_NO_CSRF_COOKIE)
                else:
                    return reject(REASON_BAD_TOKEN)
                    return self._reject(request, REASON_BAD_TOKEN)

        return accept()
        return self._accept(request)

    def process_response(self, request, response):
        if getattr(response, 'csrf_processing_done', False):
+16 −0
Original line number Diff line number Diff line
@@ -14,6 +14,22 @@ CsrfViewMiddleware, but it can be used on a per view basis. Using both, or
using the decorator multiple times, is harmless and efficient.
"""


class _EnsureCsrfToken(CsrfViewMiddleware):
    # We need this to behave just like the CsrfViewMiddleware, but not reject
    # requests.
    def _reject(self, request, reason):
        return None


requires_csrf_token = decorator_from_middleware(_EnsureCsrfToken)
requires_csrf_token.__name__ = 'requires_csrf_token'
csrf_protect.__doc__ = """
Use this decorator on views that need a correct csrf_token available to
RequestContext, but without the CSRF protection that csrf_protect
enforces.
"""

def csrf_response_exempt(view_func):
    """
    Modifies a view function so that its response is exempt
+8 −0
Original line number Diff line number Diff line
from django import http
from django.views.decorators.csrf import requires_csrf_token
from django.template import Context, RequestContext, loader


# This can be called when CsrfViewMiddleware.process_view has not run, therefore
# need @requires_csrf_token in case the template needs {% csrf_token %}.
@requires_csrf_token
def page_not_found(request, template_name='404.html'):
    """
    Default 404 handler.
@@ -13,6 +18,8 @@ def page_not_found(request, template_name='404.html'):
    t = loader.get_template(template_name) # You need to create a 404.html template.
    return http.HttpResponseNotFound(t.render(RequestContext(request, {'request_path': request.path})))


@requires_csrf_token
def server_error(request, template_name='500.html'):
    """
    500 error handler.
@@ -23,6 +30,7 @@ def server_error(request, template_name='500.html'):
    t = loader.get_template(template_name) # You need to create a 500.html template.
    return http.HttpResponseServerError(t.render(Context({})))


def shortcut(request, content_type_id, object_id):
    # TODO: Remove this in Django 2.0.
    # This is a legacy view that depends on the contenttypes framework.
+9 −1
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
from django.test import TestCase
from django.http import HttpRequest, HttpResponse
from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware
from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt
from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt, requires_csrf_token
from django.core.context_processors import csrf
from django.contrib.sessions.middleware import SessionMiddleware
from django.utils.importlib import import_module
@@ -322,6 +322,14 @@ class CsrfMiddlewareTest(TestCase):
        resp = token_view(req)
        self._check_token_present(resp)

    def test_get_token_for_requires_csrf_token_view(self):
        """
        Check that get_token works for a view decorated solely with requires_csrf_token
        """
        req = self._get_GET_csrf_cookie_request()
        resp = requires_csrf_token(token_view)(req)
        self._check_token_present(resp)

    def test_token_node_with_new_csrf_cookie(self):
        """
        Check that CsrfTokenNode works when a CSRF cookie is created by
+14 −3
Original line number Diff line number Diff line
@@ -9,6 +9,8 @@ from regressiontests.views.models import Author, Article, UrlArticle
class DefaultsTests(TestCase):
    """Test django views in django/views/defaults.py"""
    fixtures = ['testdata.json']
    non_existing_urls = ['/views/non_existing_url/', # this is in urls.py
                         '/views/other_non_existing_url/'] # this NOT in urls.py

    def test_shortcut_with_absolute_url(self):
        "Can view a shortcut for an Author object that has a get_absolute_url method"
@@ -49,12 +51,21 @@ class DefaultsTests(TestCase):

    def test_page_not_found(self):
        "A 404 status is returned by the page_not_found view"
        non_existing_urls = ['/views/non_existing_url/', # this is in urls.py
                             '/views/other_non_existing_url/'] # this NOT in urls.py
        for url in non_existing_urls:
        for url in self.non_existing_urls:
            response = self.client.get(url)
            self.assertEquals(response.status_code, 404)

    def test_csrf_token_in_404(self):
        """
        The 404 page should have the csrf_token available in the context
        """
        # See ticket #14565
        for url in self.non_existing_urls:
            response = self.client.get(url)
            csrf_token = response.context['csrf_token']
            self.assertNotEqual(str(csrf_token), 'NOTPROVIDED')
            self.assertNotEqual(str(csrf_token), '')

    def test_server_error(self):
        "The server_error view raises a 500 status"
        response = self.client.get('/views/server_error/')