Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #15354 - provide method to ensure CSRF token is always availabl…

…e 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
  • Loading branch information...
commit b6c5f8060d45a7cffe42928a4b67b6a4e2ba9264 1 parent e9342e9
@spookylukey spookylukey authored
View
22 django/views/decorators/csrf.py
@@ -1,6 +1,6 @@
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 functools import wraps
@@ -28,6 +28,26 @@ def _reject(self, request, reason):
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):
"""
Modifies a view function so that its response is exempt
View
17 docs/ref/contrib/csrf.txt
@@ -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
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
--------------------
@@ -328,6 +332,10 @@ Utilities
# ...
return render(request, "a_template.html", c)
+.. function:: ensure_csrf_cookie(view)
+
+ This decorator forces a view to send the CSRF cookie.
+
Scenarios
---------
@@ -381,6 +389,15 @@ path within it that needs protection. Example::
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
=========================
View
34 tests/regressiontests/csrf_tests/tests.py
@@ -4,7 +4,7 @@
from django.test import TestCase
from django.http import HttpRequest, HttpResponse
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.conf import settings
from django.template import RequestContext, Template
@@ -249,3 +249,35 @@ def test_https_good_referer_2(self):
req.META['HTTP_REFERER'] = 'https://www.example.com'
req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
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',''))
Please sign in to comment.
Something went wrong with that request. Please try again.