Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

New CsrfMiddleware features: automatic exceptions for known AJAX and …

…decorator for manual exceptions

git-svn-id: http://code.djangoproject.com/svn/django/trunk@9554 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 9eedc7bd0b21c936a30f000ba876dbcf5c0bed50 1 parent c0f9e85
Luke Plant authored December 03, 2008
21  django/contrib/csrf/middleware.py
@@ -7,6 +7,10 @@
7 7
 
8 8
 import re
9 9
 import itertools
  10
+try:
  11
+    from functools import wraps
  12
+except ImportError:
  13
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
10 14
 
11 15
 from django.conf import settings
12 16
 from django.http import HttpResponseForbidden
@@ -30,6 +34,12 @@ class CsrfViewMiddleware(object):
30 34
     """
31 35
     def process_view(self, request, callback, callback_args, callback_kwargs):
32 36
         if request.method == 'POST':
  37
+            if getattr(callback, 'csrf_exempt', False):
  38
+                return None
  39
+
  40
+            if request.is_ajax():
  41
+                return None
  42
+
33 43
             try:
34 44
                 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
35 45
             except KeyError:
@@ -107,3 +117,14 @@ class CsrfMiddleware(CsrfViewMiddleware, CsrfResponseMiddleware):
107 117
     and CsrfResponseMiddleware which can be used independently.
108 118
     """
109 119
     pass
  120
+
  121
+def csrf_exempt(view_func):
  122
+    """
  123
+    Marks a view function as being exempt from the CSRF checks
  124
+    """
  125
+    def wrapped_view(*args, **kwargs):
  126
+        return view_func(*args, **kwargs)
  127
+    # We could just do view.csrf_exempt = True, but decorators are
  128
+    # nicer if they don't have side-effects.
  129
+    wrapped_view.csrf_exempt = True
  130
+    return wraps(view_func)(wrapped_view)
37  django/contrib/csrf/tests.py
@@ -2,10 +2,19 @@
2 2
 
3 3
 from django.test import TestCase
4 4
 from django.http import HttpRequest, HttpResponse, HttpResponseForbidden
5  
-from django.contrib.csrf.middleware import CsrfMiddleware, _make_token
  5
+from django.contrib.csrf.middleware import CsrfMiddleware, _make_token, csrf_exempt
6 6
 from django.conf import settings
7 7
 
8 8
 
  9
+def post_form_response():
  10
+    resp = HttpResponse(content="""
  11
+<html><body><form method="POST"><input type="text" /></form></body></html>
  12
+""", mimetype="text/html")
  13
+    return resp
  14
+
  15
+def test_view(request):
  16
+    return post_form_response()
  17
+
9 18
 class CsrfMiddlewareTest(TestCase):
10 19
 
11 20
     _session_id = "1"
@@ -34,10 +43,7 @@ def _get_POST_session_request_with_token(self):
34 43
         return req
35 44
 
36 45
     def _get_post_form_response(self):
37  
-        resp = HttpResponse(content="""
38  
-<html><body><form method="POST"><input type="text" /></form></body></html>
39  
-""", mimetype="text/html")
40  
-        return resp
  46
+        return post_form_response()
41 47
 
42 48
     def _get_new_session_response(self):
43 49
         resp = self._get_post_form_response()
@@ -48,8 +54,7 @@ def _check_token_present(self, response):
48 54
         self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token(self._session_id))
49 55
 
50 56
     def get_view(self):
51  
-        def dummyview(request):
52  
-            return self._get_post_form_response()
  57
+        return test_view
53 58
 
54 59
     # Check the post processing
55 60
     def test_process_response_no_session(self):
@@ -109,3 +114,21 @@ def test_process_request_session_and_token(self):
109 114
         req = self._get_POST_session_request_with_token()
110 115
         req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
111 116
         self.assertEquals(None, req2)
  117
+
  118
+    def test_process_request_session_no_token_exempt_view(self):
  119
+        """
  120
+        Check that if a session is present and no token, but the csrf_exempt
  121
+        decorator has been applied to the view, the middleware lets it through
  122
+        """
  123
+        req = self._get_POST_session_request()
  124
+        req2 = CsrfMiddleware().process_view(req, csrf_exempt(self.get_view()), (), {})
  125
+        self.assertEquals(None, req2)
  126
+
  127
+    def test_ajax_exemption(self):
  128
+        """
  129
+        Check the AJAX requests are automatically exempted.
  130
+        """
  131
+        req = self._get_POST_session_request()
  132
+        req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
  133
+        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
  134
+        self.assertEquals(None, req2)
27  docs/ref/contrib/csrf.txt
@@ -26,7 +26,18 @@ Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to
26 26
 your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It needs to process
27 27
 the response after the SessionMiddleware, so must come before it in the
28 28
 list. It also must process the response before things like compression
29  
-happen to the response, so it must come after GZipMiddleware in the list.
  29
+happen to the response, so it must come after GZipMiddleware in the
  30
+list.
  31
+
  32
+Exceptions
  33
+----------
  34
+
  35
+To manually exclude a view function from being handled by the
  36
+CsrfMiddleware, you can use the ``csrf_exempt`` decorator (found in
  37
+the ``django.contrib.csrf.middleware`` module).
  38
+
  39
+AJAX requests sent with "X-Requested-With: XMLHttpRequest" are
  40
+automatically exempt (see below).
30 41
 
31 42
 How it works
32 43
 ============
@@ -59,6 +70,18 @@ The Content-Type is checked before modifying the response, and only
59 70
 pages that are served as 'text/html' or 'application/xml+xhtml'
60 71
 are modified.
61 72
 
  73
+AJAX requests sent with "X-Requested-With: XMLHttpRequest", as done by
  74
+many AJAX toolkits, are detected and automatically excepted from this
  75
+mechanism.  This is because in the context of a browser, this header
  76
+can only be added by using XMLHttpRequest, and browsers already
  77
+implement a same-domain policy for XMLHttpRequest.  This is not secure
  78
+if you do not trust content within the same domain or sub-domains.
  79
+
  80
+The above two functions of ``CsrfMiddleware`` are split between two
  81
+classes: ``CsrfResponseMiddleware`` and ``CsrfViewMiddleware``
  82
+respectively.  This allows the individual components to be used and/or
  83
+replaced instead of using ``CsrfMiddleware``.
  84
+
62 85
 .. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
63 86
 
64 87
 Limitations
@@ -73,4 +96,4 @@ it sends fragments of HTML in JavaScript document.write statements)
73 96
 you might bypass the filter that adds the hidden field to the form,
74 97
 in which case form submission will always fail.  It may still be possible
75 98
 to use the middleware, provided you can find some way to get the
76  
-CSRF token and ensure that is included when your form is submitted.
  99
+CSRF token and ensure that is included when your form is submitted.

0 notes on commit 9eedc7b

Please sign in to comment.
Something went wrong with that request. Please try again.