Author: lukeplant
Date: 2008-12-02 18:34:18 -0600 (Tue, 02 Dec 2008)
New Revision: 9554

Modified:
   django/trunk/django/contrib/csrf/middleware.py
   django/trunk/django/contrib/csrf/tests.py
   django/trunk/docs/ref/contrib/csrf.txt
Log:
New CsrfMiddleware features: automatic exceptions for known AJAX and decorator 
for manual exceptions


Modified: django/trunk/django/contrib/csrf/middleware.py
===================================================================
--- django/trunk/django/contrib/csrf/middleware.py      2008-12-03 00:31:31 UTC 
(rev 9553)
+++ django/trunk/django/contrib/csrf/middleware.py      2008-12-03 00:34:18 UTC 
(rev 9554)
@@ -7,6 +7,10 @@
 
 import re
 import itertools
+try:
+    from functools import wraps
+except ImportError:
+    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
 
 from django.conf import settings
 from django.http import HttpResponseForbidden
@@ -30,6 +34,12 @@
     """
     def process_view(self, request, callback, callback_args, callback_kwargs):
         if request.method == 'POST':
+            if getattr(callback, 'csrf_exempt', False):
+                return None
+
+            if request.is_ajax():
+                return None
+
             try:
                 session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
             except KeyError:
@@ -107,3 +117,14 @@
     and CsrfResponseMiddleware which can be used independently.
     """
     pass
+
+def csrf_exempt(view_func):
+    """
+    Marks a view function as being exempt from the CSRF checks
+    """
+    def wrapped_view(*args, **kwargs):
+        return view_func(*args, **kwargs)
+    # We could just do view.csrf_exempt = True, but decorators are
+    # nicer if they don't have side-effects.
+    wrapped_view.csrf_exempt = True
+    return wraps(view_func)(wrapped_view)

Modified: django/trunk/django/contrib/csrf/tests.py
===================================================================
--- django/trunk/django/contrib/csrf/tests.py   2008-12-03 00:31:31 UTC (rev 
9553)
+++ django/trunk/django/contrib/csrf/tests.py   2008-12-03 00:34:18 UTC (rev 
9554)
@@ -2,10 +2,19 @@
 
 from django.test import TestCase
 from django.http import HttpRequest, HttpResponse, HttpResponseForbidden
-from django.contrib.csrf.middleware import CsrfMiddleware, _make_token
+from django.contrib.csrf.middleware import CsrfMiddleware, _make_token, 
csrf_exempt
 from django.conf import settings
 
 
+def post_form_response():
+    resp = HttpResponse(content="""
+<html><body><form method="POST"><input type="text" /></form></body></html>
+""", mimetype="text/html")
+    return resp
+
+def test_view(request):
+    return post_form_response()
+
 class CsrfMiddlewareTest(TestCase):
 
     _session_id = "1"
@@ -34,10 +43,7 @@
         return req
 
     def _get_post_form_response(self):
-        resp = HttpResponse(content="""
-<html><body><form method="POST"><input type="text" /></form></body></html>
-""", mimetype="text/html")
-        return resp
+        return post_form_response()
 
     def _get_new_session_response(self):
         resp = self._get_post_form_response()
@@ -48,8 +54,7 @@
         self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" 
% _make_token(self._session_id))
 
     def get_view(self):
-        def dummyview(request):
-            return self._get_post_form_response()
+        return test_view
 
     # Check the post processing
     def test_process_response_no_session(self):
@@ -109,3 +114,21 @@
         req = self._get_POST_session_request_with_token()
         req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
         self.assertEquals(None, req2)
+
+    def test_process_request_session_no_token_exempt_view(self):
+        """
+        Check that if a session is present and no token, but the csrf_exempt
+        decorator has been applied to the view, the middleware lets it through
+        """
+        req = self._get_POST_session_request()
+        req2 = CsrfMiddleware().process_view(req, 
csrf_exempt(self.get_view()), (), {})
+        self.assertEquals(None, req2)
+
+    def test_ajax_exemption(self):
+        """
+        Check the AJAX requests are automatically exempted.
+        """
+        req = self._get_POST_session_request()
+        req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
+        self.assertEquals(None, req2)

Modified: django/trunk/docs/ref/contrib/csrf.txt
===================================================================
--- django/trunk/docs/ref/contrib/csrf.txt      2008-12-03 00:31:31 UTC (rev 
9553)
+++ django/trunk/docs/ref/contrib/csrf.txt      2008-12-03 00:34:18 UTC (rev 
9554)
@@ -26,8 +26,19 @@
 your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It needs to 
process
 the response after the SessionMiddleware, so must come before it in the
 list. It also must process the response before things like compression
-happen to the response, so it must come after GZipMiddleware in the list.
+happen to the response, so it must come after GZipMiddleware in the
+list.
 
+Exceptions
+----------
+
+To manually exclude a view function from being handled by the
+CsrfMiddleware, you can use the ``csrf_exempt`` decorator (found in
+the ``django.contrib.csrf.middleware`` module).
+
+AJAX requests sent with "X-Requested-With: XMLHttpRequest" are
+automatically exempt (see below).
+
 How it works
 ============
 
@@ -59,6 +70,18 @@
 pages that are served as 'text/html' or 'application/xml+xhtml'
 are modified.
 
+AJAX requests sent with "X-Requested-With: XMLHttpRequest", as done by
+many AJAX toolkits, are detected and automatically excepted from this
+mechanism.  This is because in the context of a browser, this header
+can only be added by using XMLHttpRequest, and browsers already
+implement a same-domain policy for XMLHttpRequest.  This is not secure
+if you do not trust content within the same domain or sub-domains.
+
+The above two functions of ``CsrfMiddleware`` are split between two
+classes: ``CsrfResponseMiddleware`` and ``CsrfViewMiddleware``
+respectively.  This allows the individual components to be used and/or
+replaced instead of using ``CsrfMiddleware``.
+
 .. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: 
http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
 
 Limitations
@@ -73,4 +96,4 @@
 you might bypass the filter that adds the hidden field to the form,
 in which case form submission will always fail.  It may still be possible
 to use the middleware, provided you can find some way to get the
-CSRF token and ensure that is included when your form is submitted.
\ No newline at end of file
+CSRF token and ensure that is included when your form is submitted.


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to