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
This commit is contained in:
parent
c0f9e85fbe
commit
9eedc7bd0b
|
@ -7,6 +7,10 @@ against request forgeries from other sites.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import itertools
|
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.conf import settings
|
||||||
from django.http import HttpResponseForbidden
|
from django.http import HttpResponseForbidden
|
||||||
|
@ -30,6 +34,12 @@ class CsrfViewMiddleware(object):
|
||||||
"""
|
"""
|
||||||
def process_view(self, request, callback, callback_args, callback_kwargs):
|
def process_view(self, request, callback, callback_args, callback_kwargs):
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
|
if getattr(callback, 'csrf_exempt', False):
|
||||||
|
return None
|
||||||
|
|
||||||
|
if request.is_ajax():
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
|
session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -107,3 +117,14 @@ class CsrfMiddleware(CsrfViewMiddleware, CsrfResponseMiddleware):
|
||||||
and CsrfResponseMiddleware which can be used independently.
|
and CsrfResponseMiddleware which can be used independently.
|
||||||
"""
|
"""
|
||||||
pass
|
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)
|
||||||
|
|
|
@ -2,10 +2,19 @@
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.http import HttpRequest, HttpResponse, HttpResponseForbidden
|
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
|
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):
|
class CsrfMiddlewareTest(TestCase):
|
||||||
|
|
||||||
_session_id = "1"
|
_session_id = "1"
|
||||||
|
@ -34,10 +43,7 @@ class CsrfMiddlewareTest(TestCase):
|
||||||
return req
|
return req
|
||||||
|
|
||||||
def _get_post_form_response(self):
|
def _get_post_form_response(self):
|
||||||
resp = HttpResponse(content="""
|
return post_form_response()
|
||||||
<html><body><form method="POST"><input type="text" /></form></body></html>
|
|
||||||
""", mimetype="text/html")
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def _get_new_session_response(self):
|
def _get_new_session_response(self):
|
||||||
resp = self._get_post_form_response()
|
resp = self._get_post_form_response()
|
||||||
|
@ -48,8 +54,7 @@ class CsrfMiddlewareTest(TestCase):
|
||||||
self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token(self._session_id))
|
self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token(self._session_id))
|
||||||
|
|
||||||
def get_view(self):
|
def get_view(self):
|
||||||
def dummyview(request):
|
return test_view
|
||||||
return self._get_post_form_response()
|
|
||||||
|
|
||||||
# Check the post processing
|
# Check the post processing
|
||||||
def test_process_response_no_session(self):
|
def test_process_response_no_session(self):
|
||||||
|
@ -109,3 +114,21 @@ class CsrfMiddlewareTest(TestCase):
|
||||||
req = self._get_POST_session_request_with_token()
|
req = self._get_POST_session_request_with_token()
|
||||||
req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
|
req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
|
||||||
self.assertEquals(None, req2)
|
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)
|
||||||
|
|
|
@ -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
|
your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It needs to process
|
||||||
the response after the SessionMiddleware, so must come before it in the
|
the response after the SessionMiddleware, so must come before it in the
|
||||||
list. It also must process the response before things like compression
|
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
|
How it works
|
||||||
============
|
============
|
||||||
|
@ -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'
|
pages that are served as 'text/html' or 'application/xml+xhtml'
|
||||||
are modified.
|
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
|
.. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
|
||||||
|
|
||||||
Limitations
|
Limitations
|
||||||
|
@ -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,
|
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
|
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
|
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.
|
||||||
|
|
Loading…
Reference in New Issue