Skip to content

Commit

Permalink
New CsrfMiddleware features: automatic exceptions for known AJAX and …
Browse files Browse the repository at this point in the history
…decorator for manual exceptions

git-svn-id: http://code.djangoproject.com/svn/django/trunk@9554 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
lukeplant committed Dec 3, 2008
1 parent f04eceb commit eb4cbff
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 9 deletions.
21 changes: 21 additions & 0 deletions django/contrib/csrf/middleware.py
Expand Up @@ -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
Expand All @@ -30,6 +34,12 @@ class CsrfViewMiddleware(object):
"""
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:
Expand Down Expand Up @@ -107,3 +117,14 @@ class CsrfMiddleware(CsrfViewMiddleware, CsrfResponseMiddleware):
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)
37 changes: 30 additions & 7 deletions django/contrib/csrf/tests.py
Expand Up @@ -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"
Expand Down Expand Up @@ -34,10 +43,7 @@ def _get_POST_session_request_with_token(self):
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()
Expand All @@ -48,8 +54,7 @@ def _check_token_present(self, response):
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):
Expand Down Expand Up @@ -109,3 +114,21 @@ def test_process_request_session_and_token(self):
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)
27 changes: 25 additions & 2 deletions docs/ref/contrib/csrf.txt
Expand Up @@ -26,7 +26,18 @@ Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to
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
============
Expand Down Expand Up @@ -59,6 +70,18 @@ The Content-Type is checked before modifying the response, and only
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
Expand All @@ -73,4 +96,4 @@ it sends fragments of HTML in JavaScript document.write statements)
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.
CSRF token and ensure that is included when your form is submitted.

0 comments on commit eb4cbff

Please sign in to comment.