Fixed #15354 - provide method to ensure CSRF token is always available for AJAX requests

Thanks to sayane for the report.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16192 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Luke Plant 2011-05-09 21:35:24 +00:00
parent e9342e9b32
commit b6c5f8060d
3 changed files with 71 additions and 2 deletions

View File

@ -1,6 +1,6 @@
import warnings import warnings
from django.middleware.csrf import CsrfViewMiddleware from django.middleware.csrf import CsrfViewMiddleware, get_token
from django.utils.decorators import decorator_from_middleware, available_attrs from django.utils.decorators import decorator_from_middleware, available_attrs
from functools import wraps from functools import wraps
@ -28,6 +28,26 @@ RequestContext, but without the CSRF protection that csrf_protect
enforces. enforces.
""" """
class _EnsureCsrfCookie(CsrfViewMiddleware):
def _reject(self, request, reason):
return None
def process_view(self, request, callback, callback_args, callback_kwargs):
retval = super(_EnsureCsrfCookie, self).process_view(request, callback, callback_args, callback_kwargs)
# Forces process_response to send the cookie
get_token(request)
return retval
ensure_csrf_cookie = decorator_from_middleware(_EnsureCsrfCookie)
ensure_csrf_cookie.__name__ = 'ensure_csrf_cookie'
ensure_csrf_cookie.__doc__ = """
Use this decorator to ensure that a view sets a CSRF cookie, whether or not it
uses the csrf_token template tag, or the CsrfViewMiddleware is used.
"""
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

@ -132,6 +132,10 @@ The above code could be simplified by using the `jQuery cookie plugin
`settings.crossDomain <http://api.jquery.com/jQuery.ajax>`_ in jQuery 1.5 and `settings.crossDomain <http://api.jquery.com/jQuery.ajax>`_ in jQuery 1.5 and
later to replace ``sameOrigin``. later to replace ``sameOrigin``.
In addition, if the CSRF cookie has not been sent to the client by use of
:ttag:`csrf_token`, you may need to ensure the client receives the cookie by
using :func:`~django.views.decorators.csrf.ensure_csrf_cookie`.
The decorator method The decorator method
-------------------- --------------------
@ -328,6 +332,10 @@ Utilities
# ... # ...
return render(request, "a_template.html", c) return render(request, "a_template.html", c)
.. function:: ensure_csrf_cookie(view)
This decorator forces a view to send the CSRF cookie.
Scenarios Scenarios
--------- ---------
@ -381,6 +389,15 @@ path within it that needs protection. Example::
else: else:
do_something_else() do_something_else()
Page uses AJAX without any HTML form
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A page makes a POST request via AJAX, and the page does not have an HTML form
with a :ttag:`csrf_token` that would cause the required CSRF cookie to be sent.
Solution: use :func:`~django.views.decorators.csrf.ensure_csrf_cookie` on the
view that sends the page.
Contrib and reusable apps Contrib and reusable apps
========================= =========================

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 CsrfViewMiddleware from django.middleware.csrf import CsrfViewMiddleware
from django.views.decorators.csrf import csrf_exempt, requires_csrf_token from django.views.decorators.csrf import csrf_exempt, requires_csrf_token, ensure_csrf_cookie
from django.core.context_processors import csrf from django.core.context_processors import csrf
from django.conf import settings from django.conf import settings
from django.template import RequestContext, Template from django.template import RequestContext, Template
@ -249,3 +249,35 @@ class CsrfViewMiddlewareTest(TestCase):
req.META['HTTP_REFERER'] = 'https://www.example.com' req.META['HTTP_REFERER'] = 'https://www.example.com'
req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
self.assertEqual(None, req2) self.assertEqual(None, req2)
def test_ensures_csrf_cookie_no_middleware(self):
"""
Tests that ensures_csrf_cookie decorator fulfils its promise
with no middleware
"""
@ensure_csrf_cookie
def view(request):
# Doesn't insert a token or anything
return HttpResponse(content="")
req = self._get_GET_no_csrf_cookie_request()
resp = view(req)
self.assertTrue(resp.cookies.get(settings.CSRF_COOKIE_NAME, False))
self.assertTrue('Cookie' in resp.get('Vary',''))
def test_ensures_csrf_cookie_with_middleware(self):
"""
Tests that ensures_csrf_cookie decorator fulfils its promise
with the middleware enabled.
"""
@ensure_csrf_cookie
def view(request):
# Doesn't insert a token or anything
return HttpResponse(content="")
req = self._get_GET_no_csrf_cookie_request()
CsrfViewMiddleware().process_view(req, view, (), {})
resp = view(req)
resp2 = CsrfViewMiddleware().process_response(req, resp)
self.assertTrue(resp2.cookies.get(settings.CSRF_COOKIE_NAME, False))
self.assertTrue('Cookie' in resp2.get('Vary',''))