Made CSRF middleware skip post-processing for 'csrf_exempt' decorated views.

This commit also decomposes the decorator into two decorators which can be
used separately, adds some tests, updates docs and fixes some code comments.



git-svn-id: http://code.djangoproject.com/svn/django/trunk@9815 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Luke Plant 2009-02-07 17:47:02 +00:00
parent fffade6633
commit 9a2e338107
3 changed files with 69 additions and 23 deletions

View File

@ -65,9 +65,12 @@ class CsrfResponseMiddleware(object):
session. session.
""" """
def process_response(self, request, response): def process_response(self, request, response):
if getattr(response, 'csrf_exempt', False):
return response
csrf_token = None csrf_token = None
try: try:
# This covers a corner case in which the outgoing request # This covers a corner case in which the outgoing response
# both contains a form and sets a session cookie. This # both contains a form and sets a session cookie. This
# really should not be needed, since it is best if views # really should not be needed, since it is best if views
# that create a new session (login pages) also do a # that create a new session (login pages) also do a
@ -123,13 +126,35 @@ class CsrfMiddleware(CsrfViewMiddleware, CsrfResponseMiddleware):
""" """
pass pass
def csrf_response_exempt(view_func):
"""
Modifies a view function so that its response is exempt
from the post-processing of the CSRF middleware.
"""
def wrapped_view(*args, **kwargs):
resp = view_func(*args, **kwargs)
resp.csrf_exempt = True
return resp
return wraps(view_func)(wrapped_view)
def csrf_view_exempt(view_func):
"""
Marks a view function as being exempt from CSRF view protection.
"""
# We could just do view_func.csrf_exempt = True, but decorators
# are nicer if they don't have side-effects, so we return a new
# function.
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.csrf_exempt = True
return wraps(view_func)(wrapped_view)
def csrf_exempt(view_func): def csrf_exempt(view_func):
""" """
Marks a view function as being exempt from the CSRF checks Marks a view function as being exempt from the CSRF checks
and post processing.
This is the same as using both the csrf_exempt_view and
csrf_exempt_response decorators.
""" """
def wrapped_view(*args, **kwargs): return csrf_response_exempt(csrf_view_exempt(view_func))
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)

View File

@ -63,7 +63,7 @@ class CsrfMiddlewareTest(TestCase):
""" """
req = self._get_GET_no_session_request() req = self._get_GET_no_session_request()
resp = self._get_post_form_response() resp = self._get_post_form_response()
resp_content = resp.content resp_content = resp.content # needed because process_response modifies resp
resp2 = CsrfMiddleware().process_response(req, resp) resp2 = CsrfMiddleware().process_response(req, resp)
self.assertEquals(resp_content, resp2.content) self.assertEquals(resp_content, resp2.content)
@ -73,7 +73,7 @@ class CsrfMiddlewareTest(TestCase):
""" """
req = self._get_GET_session_request() req = self._get_GET_session_request()
resp = self._get_post_form_response() resp = self._get_post_form_response()
resp_content = resp.content resp_content = resp.content # needed because process_response modifies resp
resp2 = CsrfMiddleware().process_response(req, resp) resp2 = CsrfMiddleware().process_response(req, resp)
self.assertNotEqual(resp_content, resp2.content) self.assertNotEqual(resp_content, resp2.content)
self._check_token_present(resp2) self._check_token_present(resp2)
@ -84,11 +84,21 @@ class CsrfMiddlewareTest(TestCase):
""" """
req = self._get_GET_no_session_request() # no session in request req = self._get_GET_no_session_request() # no session in request
resp = self._get_new_session_response() # but new session started resp = self._get_new_session_response() # but new session started
resp_content = resp.content resp_content = resp.content # needed because process_response modifies resp
resp2 = CsrfMiddleware().process_response(req, resp) resp2 = CsrfMiddleware().process_response(req, resp)
self.assertNotEqual(resp_content, resp2.content) self.assertNotEqual(resp_content, resp2.content)
self._check_token_present(resp2) self._check_token_present(resp2)
def test_process_response_exempt_view(self):
"""
Check that no post processing is done for an exempt view
"""
req = self._get_POST_session_request()
resp = csrf_exempt(self.get_view())(req)
resp_content = resp.content
resp2 = CsrfMiddleware().process_response(req, resp)
self.assertEquals(resp_content, resp2.content)
# Check the request processing # Check the request processing
def test_process_request_no_session(self): def test_process_request_no_session(self):
""" """
@ -126,7 +136,7 @@ class CsrfMiddlewareTest(TestCase):
def test_ajax_exemption(self): def test_ajax_exemption(self):
""" """
Check the AJAX requests are automatically exempted. Check that AJAX requests are automatically exempted.
""" """
req = self._get_POST_session_request() req = self._get_POST_session_request()
req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'

View File

@ -29,6 +29,16 @@ list. It also must process the response before things like compression
happen to the response, so it must come after GZipMiddleware in the happen to the response, so it must come after GZipMiddleware in the
list. list.
The ``CsrfMiddleware`` class is actually composed of two middleware:
``CsrfViewMiddleware`` which performs the checks on incoming requests,
and ``CsrfResponseMiddleware`` which performs post-processing of the
result. This allows the individual components to be used and/or
replaced instead of using ``CsrfMiddleware``.
.. versionchanged:: 1.1
(previous versions of Django did not provide these two components
of ``CsrfMiddleware`` as described above)
Exceptions Exceptions
---------- ----------
@ -44,9 +54,16 @@ the ``django.contrib.csrf.middleware`` module. For example::
return HttpResponse('Hello world') return HttpResponse('Hello world')
my_view = csrf_exempt(my_view) my_view = csrf_exempt(my_view)
You don't have to worry about doing this for most AJAX views. Any request sent Like the middleware itself, the ``csrf_exempt`` decorator is composed
with "X-Requested-With: XMLHttpRequest" is automatically exempt. (See the next of two parts: a ``csrf_view_exempt`` decorator and a
section.) ``csrf_response_exempt`` decorator, found in the same module. These
disable the view protection mechanism (``CsrfViewMiddleware``) and the
response post-processing (``CsrfResponseMiddleware``) respectively.
They can be used individually if required.
You don't have to worry about doing this for most AJAX views. Any
request sent with "X-Requested-With: XMLHttpRequest" is automatically
exempt. (See the next section.)
How it works How it works
============ ============
@ -58,10 +75,12 @@ CsrfMiddleware does two things:
a hash of the session ID plus a secret. If there is no session ID set, a hash of the session ID plus a secret. If there is no session ID set,
this modification of the response isn't done, so there is very little this modification of the response isn't done, so there is very little
performance penalty for those requests that don't have a session. performance penalty for those requests that don't have a session.
(This is done by ``CsrfResponseMiddleware``).
2. On all incoming POST requests that have the session cookie set, it 2. On all incoming POST requests that have the session cookie set, it
checks that the 'csrfmiddlewaretoken' is present and correct. If it checks that the 'csrfmiddlewaretoken' is present and correct. If it
isn't, the user will get a 403 error. isn't, the user will get a 403 error. (This is done by
``CsrfViewMiddleware``)
This ensures that only forms that have originated from your Web site This ensures that only forms that have originated from your Web site
can be used to POST data back. can be used to POST data back.
@ -87,14 +106,6 @@ be added by using ``XMLHttpRequest``, and browsers already implement a
same-domain policy for ``XMLHttpRequest``. (Note that this is not secure if you same-domain policy for ``XMLHttpRequest``. (Note that this is not secure if you
don't trust content within the same domain or subdomains.) don't trust content within the same domain or subdomains.)
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``.
.. versionchanged:: 1.1
(previous versions of Django did not provide these two components
of ``CsrfMiddleware`` as described above)
.. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html .. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html