Fixed #14565 - No csrf_token on 404 page.

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

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14377 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Luke Plant 2010-10-28 11:47:15 +00:00
parent 144ab8877f
commit 90ac02300e
5 changed files with 67 additions and 20 deletions

View File

@ -88,18 +88,22 @@ class CsrfViewMiddleware(object):
This middleware should be used in conjunction with the csrf_token template This middleware should be used in conjunction with the csrf_token template
tag. tag.
""" """
# 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): def process_view(self, request, callback, callback_args, callback_kwargs):
if getattr(request, 'csrf_processing_done', False): if getattr(request, 'csrf_processing_done', False):
return None return None
reject = lambda s: _get_failure_view()(request, reason=s)
def accept():
# 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
# If the user doesn't have a CSRF cookie, generate one and store it in the # 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 # request, so it's available to the view. We'll store it in a cookie when
# we reach the response. # we reach the response.
@ -128,7 +132,7 @@ class CsrfViewMiddleware(object):
# the creation of CSRF cookies, so that everything else continues to # the creation of CSRF cookies, so that everything else continues to
# work exactly the same (e.g. cookies are sent etc), but before the # work exactly the same (e.g. cookies are sent etc), but before the
# any branches that call reject() # any branches that call reject()
return accept() return self._accept(request)
if request.is_ajax(): if request.is_ajax():
# .is_ajax() is based on the presence of X-Requested-With. In # .is_ajax() is based on the presence of X-Requested-With. In
@ -153,7 +157,7 @@ class CsrfViewMiddleware(object):
# allowing the cross-domain POST request. # allowing the cross-domain POST request.
# #
# So in all cases, it is safe to allow these requests through. # So in all cases, it is safe to allow these requests through.
return accept() return self._accept(request)
if request.is_secure(): if request.is_secure():
# Suppose user visits http://example.com/ # Suppose user visits http://example.com/
@ -179,7 +183,7 @@ class CsrfViewMiddleware(object):
'request': request, 'request': request,
} }
) )
return reject(REASON_NO_REFERER) return self._reject(request, REASON_NO_REFERER)
# The following check ensures that the referer is HTTPS, # The following check ensures that the referer is HTTPS,
# the domains match and the ports match - the same origin policy. # the domains match and the ports match - the same origin policy.
@ -192,7 +196,7 @@ class CsrfViewMiddleware(object):
'request': request, 'request': request,
} }
) )
return reject(reason) return self._reject(request, reason)
# If the user didn't already have a CSRF cookie, then fall back to # If the user didn't already have a CSRF cookie, then fall back to
# the Django 1.1 method (hash of session ID), so a request is not # the Django 1.1 method (hash of session ID), so a request is not
@ -212,7 +216,7 @@ class CsrfViewMiddleware(object):
'request': request, 'request': request,
} }
) )
return reject(REASON_NO_COOKIE) return self._reject(request, REASON_NO_COOKIE)
else: else:
csrf_token = request.META["CSRF_COOKIE"] csrf_token = request.META["CSRF_COOKIE"]
@ -227,7 +231,7 @@ class CsrfViewMiddleware(object):
'request': request, 'request': request,
} }
) )
return reject(REASON_NO_CSRF_COOKIE) return self._reject(request, REASON_NO_CSRF_COOKIE)
else: else:
logger.warning('Forbidden (%s): %s' % (REASON_BAD_TOKEN, request.path), logger.warning('Forbidden (%s): %s' % (REASON_BAD_TOKEN, request.path),
extra={ extra={
@ -235,9 +239,9 @@ class CsrfViewMiddleware(object):
'request': request, 'request': request,
} }
) )
return reject(REASON_BAD_TOKEN) return self._reject(request, REASON_BAD_TOKEN)
return accept() return self._accept(request)
def process_response(self, request, response): def process_response(self, request, response):
if getattr(response, 'csrf_processing_done', False): if getattr(response, 'csrf_processing_done', False):

View File

@ -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. 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): def csrf_response_exempt(view_func):
""" """
Modifies a view function so that its response is exempt Modifies a view function so that its response is exempt

View File

@ -1,6 +1,11 @@
from django import http from django import http
from django.views.decorators.csrf import requires_csrf_token
from django.template import Context, RequestContext, loader 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'): def page_not_found(request, template_name='404.html'):
""" """
Default 404 handler. 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. 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}))) return http.HttpResponseNotFound(t.render(RequestContext(request, {'request_path': request.path})))
@requires_csrf_token
def server_error(request, template_name='500.html'): def server_error(request, template_name='500.html'):
""" """
500 error handler. 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. t = loader.get_template(template_name) # You need to create a 500.html template.
return http.HttpResponseServerError(t.render(Context({}))) return http.HttpResponseServerError(t.render(Context({})))
def shortcut(request, content_type_id, object_id): def shortcut(request, content_type_id, object_id):
# TODO: Remove this in Django 2.0. # TODO: Remove this in Django 2.0.
# This is a legacy view that depends on the contenttypes framework. # This is a legacy view that depends on the contenttypes framework.

View File

@ -4,7 +4,7 @@ import warnings
from django.test import TestCase from django.test import TestCase
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware 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.core.context_processors import csrf
from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sessions.middleware import SessionMiddleware
from django.utils.importlib import import_module from django.utils.importlib import import_module
@ -331,6 +331,14 @@ class CsrfMiddlewareTest(TestCase):
resp = token_view(req) resp = token_view(req)
self._check_token_present(resp) 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): def test_token_node_with_new_csrf_cookie(self):
""" """
Check that CsrfTokenNode works when a CSRF cookie is created by Check that CsrfTokenNode works when a CSRF cookie is created by

View File

@ -9,6 +9,8 @@ from regressiontests.views.models import Author, Article, UrlArticle
class DefaultsTests(TestCase): class DefaultsTests(TestCase):
"""Test django views in django/views/defaults.py""" """Test django views in django/views/defaults.py"""
fixtures = ['testdata.json'] 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): def test_shortcut_with_absolute_url(self):
"Can view a shortcut for an Author object that has a get_absolute_url method" "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): def test_page_not_found(self):
"A 404 status is returned by the page_not_found view" "A 404 status is returned by the page_not_found view"
non_existing_urls = ['/views/non_existing_url/', # this is in urls.py for url in self.non_existing_urls:
'/views/other_non_existing_url/'] # this NOT in urls.py
for url in non_existing_urls:
response = self.client.get(url) response = self.client.get(url)
self.assertEquals(response.status_code, 404) 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): def test_server_error(self):
"The server_error view raises a 500 status" "The server_error view raises a 500 status"
response = self.client.get('/views/server_error/') response = self.client.get('/views/server_error/')