Commit 9a2e3381 authored by Luke Plant's avatar Luke Plant
Browse files

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
parent fffade66
Loading
Loading
Loading
Loading
+30 −5
Original line number Diff line number Diff line
@@ -65,9 +65,12 @@ class CsrfResponseMiddleware(object):
    session.
    """
    def process_response(self, request, response):
	if getattr(response, 'csrf_exempt', False):
	    return response

        csrf_token = None
        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
            # really should not be needed, since it is best if views
            # that create a new session (login pages) also do a
@@ -123,13 +126,35 @@ class CsrfMiddleware(CsrfViewMiddleware, CsrfResponseMiddleware):
    """
    pass

def csrf_exempt(view_func):
def csrf_response_exempt(view_func):
    """
    Marks a view function as being exempt from the CSRF checks
    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)
    # 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)

def csrf_exempt(view_func):
    """
    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.
    """
    return csrf_response_exempt(csrf_view_exempt(view_func))
+14 −4
Original line number Diff line number Diff line
@@ -63,7 +63,7 @@ class CsrfMiddlewareTest(TestCase):
        """
        req = self._get_GET_no_session_request()
        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)
        self.assertEquals(resp_content, resp2.content)

@@ -73,7 +73,7 @@ class CsrfMiddlewareTest(TestCase):
        """
        req = self._get_GET_session_request()
        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)
        self.assertNotEqual(resp_content, resp2.content)
        self._check_token_present(resp2)
@@ -84,11 +84,21 @@ class CsrfMiddlewareTest(TestCase):
        """
        req = self._get_GET_no_session_request() # no session in request
        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)
        self.assertNotEqual(resp_content, resp2.content)
        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
    def test_process_request_no_session(self):
        """
@@ -126,7 +136,7 @@ class CsrfMiddlewareTest(TestCase):

    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.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+23 −12
Original line number Diff line number Diff line
@@ -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
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
----------

@@ -44,9 +54,16 @@ the ``django.contrib.csrf.middleware`` module. For example::
        return HttpResponse('Hello world')
    my_view = csrf_exempt(my_view)

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.)
Like the middleware itself, the ``csrf_exempt`` decorator is composed
of two parts: a ``csrf_view_exempt`` decorator and a
``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
============
@@ -58,10 +75,12 @@ CsrfMiddleware does two things:
   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
   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
   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
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
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