From 8823021625785cbe8e9e2f4d6ff559fc4c8fa7c6 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Wed, 30 Mar 2011 17:34:26 +0000 Subject: [PATCH] Removed deprecated CsrfResponseMiddleware, and corresponding tests and docs git-svn-id: http://code.djangoproject.com/svn/django/trunk@15949 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/middleware/csrf.py | 91 ----------- docs/ref/contrib/csrf.txt | 189 ++-------------------- tests/regressiontests/csrf_tests/tests.py | 111 ++----------- 3 files changed, 22 insertions(+), 369 deletions(-) diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index a16a17427b..2f36f18e6c 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -6,7 +6,6 @@ against request forgeries from other sites. """ import hashlib -import itertools import re import random @@ -15,14 +14,8 @@ from django.core.urlresolvers import get_callable from django.utils.cache import patch_vary_headers from django.utils.http import same_origin from django.utils.log import getLogger -from django.utils.safestring import mark_safe from django.utils.crypto import constant_time_compare -_POST_FORM_RE = \ - re.compile(r'(]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) - -_HTML_TYPES = ('text/html', 'application/xhtml+xml') - logger = getLogger('django.request') # Use the system (hardware-based) random number generator if it exists. @@ -49,10 +42,6 @@ def _get_new_csrf_key(): return hashlib.md5("%s%s" % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() -def _make_legacy_session_token(session_id): - return hashlib.md5(settings.SECRET_KEY + session_id).hexdigest() - - def get_token(request): """ Returns the the CSRF token required for a POST form. The token is an @@ -214,83 +203,3 @@ class CsrfViewMiddleware(object): patch_vary_headers(response, ('Cookie',)) response.csrf_processing_done = True return response - - -class CsrfResponseMiddleware(object): - """ - DEPRECATED - Middleware that post-processes a response to add a csrfmiddlewaretoken. - - This exists for backwards compatibility and as an interim measure until - applications are converted to using use the csrf_token template tag - instead. It will be removed in Django 1.4. - """ - def __init__(self): - import warnings - warnings.warn( - "CsrfResponseMiddleware and CsrfMiddleware are deprecated; use CsrfViewMiddleware and the template tag instead (see CSRF documentation).", - DeprecationWarning - ) - - def process_response(self, request, response): - if getattr(response, 'csrf_exempt', False): - return response - - if response['Content-Type'].split(';')[0] in _HTML_TYPES: - csrf_token = get_token(request) - # If csrf_token is None, we have no token for this request, which probably - # means that this is a response from a request middleware. - if csrf_token is None: - return response - - # ensure we don't add the 'id' attribute twice (HTML validity) - idattributes = itertools.chain(("id='csrfmiddlewaretoken'",), - itertools.repeat('')) - def add_csrf_field(match): - """Returns the matched
tag plus the added element""" - return mark_safe(match.group() + "
" + \ - "
") - - # Modify any POST forms - response.content, n = _POST_FORM_RE.subn(add_csrf_field, response.content) - if n > 0: - # Content varies with the CSRF cookie, so set the Vary header. - patch_vary_headers(response, ('Cookie',)) - - # Since the content has been modified, any Etag will now be - # incorrect. We could recalculate, but only if we assume that - # the Etag was set by CommonMiddleware. The safest thing is just - # to delete. See bug #9163 - del response['ETag'] - return response - - -class CsrfMiddleware(object): - """ - Django middleware that adds protection against Cross Site - Request Forgeries by adding hidden form fields to POST forms and - checking requests for the correct value. - - CsrfMiddleware uses two middleware, CsrfViewMiddleware and - CsrfResponseMiddleware, which can be used independently. It is recommended - to use only CsrfViewMiddleware and use the csrf_token template tag in - templates for inserting the token. - """ - # We can't just inherit from CsrfViewMiddleware and CsrfResponseMiddleware - # because both have process_response methods. - def __init__(self): - self.response_middleware = CsrfResponseMiddleware() - self.view_middleware = CsrfViewMiddleware() - - def process_response(self, request, resp): - # We must do the response post-processing first, because that calls - # get_token(), which triggers a flag saying that the CSRF cookie needs - # to be sent (done in CsrfViewMiddleware.process_response) - resp2 = self.response_middleware.process_response(request, resp) - return self.view_middleware.process_response(request, resp2) - - def process_view(self, request, callback, callback_args, callback_kwargs): - return self.view_middleware.process_view(request, callback, callback_args, - callback_kwargs) diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index c28bd0319f..459a71ffe1 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -17,28 +17,18 @@ The first defense against CSRF attacks is to ensure that GET requests are side-effect free. POST requests can then be protected by following the steps below. -.. versionadded:: 1.2 - The 'contrib' apps, including the admin, use the functionality described - here. Because it is security related, a few things have been added to core - functionality to allow this to happen without any required upgrade steps. - .. _Cross Site Request Forgeries: http://www.squarefree.com/securitytips/web-developers.html#CSRF How to use it ============= -.. versionchanged:: 1.2 - The template tag functionality (the recommended way to use this) was added - in version 1.2. The previous method (still available) is described under - `Legacy method`_. - To enable CSRF protection for your views, follow these steps: 1. Add the middleware ``'django.middleware.csrf.CsrfViewMiddleware'`` to your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. (It should come - before ``CsrfResponseMiddleware`` if that is being used, and before any - view middleware that assume that CSRF attacks have been dealt with.) + and before any view middleware that assume that CSRF attacks have + been dealt with.) Alternatively, you can use the decorator ``django.views.decorators.csrf.csrf_protect`` on particular views you @@ -47,7 +37,7 @@ To enable CSRF protection for your views, follow these steps: 2. In any template that uses a POST form, use the :ttag:`csrf_token` tag inside the ```` element if the form is for an internal URL, e.g.:: - {% csrf_token %} + {% csrf_token %} This should not be done for POST forms that target external URLs, since that would cause the CSRF token to be leaked, leading to a vulnerability. @@ -78,8 +68,8 @@ To enable CSRF protection for your views, follow these steps: takes care of this step for you. The utility script ``extras/csrf_migration_helper.py`` can help to automate the -finding of code and templates that may need to be upgraded. It contains full -help on how to use it. +finding of code and templates that may need these steps. It contains full help +on how to use it. .. _csrf-ajax: @@ -146,145 +136,9 @@ Use of the decorator is **not recommended** by itself, since if you forget to use it, you will have a security hole. The 'belt and braces' strategy of using both is fine, and will incur minimal overhead. -Legacy method -------------- - -In Django 1.1, the template tag did not exist. Instead, a post-processing -middleware that re-wrote POST forms to include the CSRF token was used. If you -are upgrading a site from version 1.1 or earlier, please read this section and -the `Upgrading notes`_ below. The post-processing middleware is still available -as ``CsrfResponseMiddleware``, and it can be used by following these steps: - - 1. Follow step 1 above to install ``CsrfViewMiddleware``. - - 2. Add ``'django.middleware.csrf.CsrfResponseMiddleware'`` to your - :setting:`MIDDLEWARE_CLASSES` setting. - - ``CsrfResponseMiddleware`` needs to process the response before things - like compression or setting ofETags happen to the response, so it must - come after ``GZipMiddleware``, ``CommonMiddleware`` and - ``ConditionalGetMiddleware`` in the list. It also must come after - ``CsrfViewMiddleware``. - -Use of the ``CsrfResponseMiddleware`` is not recommended because of the -performance hit it imposes, and because of a potential security problem (see -below). It can be used as an interim measure until applications have been -updated to use the :ttag:`csrf_token` tag. It is deprecated and will be -removed in Django 1.4. - -Django 1.1 and earlier provided a single ``CsrfMiddleware`` class. This is also -still available for backwards compatibility. It combines the functions of the -two middleware. - -Note also that previous versions of these classes depended on the sessions -framework, but this dependency has now been removed, with backward compatibility -support so that upgrading will not produce any issues. - -Security of legacy method -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The post-processing ``CsrfResponseMiddleware`` adds the CSRF token to all POST -forms (unless the view has been decorated with ``csrf_response_exempt``). If -the POST form has an external untrusted site as its target, rather than an -internal page, that site will be sent the CSRF token when the form is submitted. -Armed with this leaked information, that site will then be able to successfully -launch a CSRF attack on your site against that user. The -``@csrf_response_exempt`` decorator can be used to fix this, but only if the -page doesn't also contain internal forms that require the token. - -.. _ref-csrf-upgrading-notes: - -Upgrading notes ---------------- - -When upgrading to version 1.2 or later, you may have applications that rely on -the old post-processing functionality for CSRF protection, or you may not have -enabled any CSRF protection. This section outlines the steps necessary for a -smooth upgrade, without having to fix all the applications to use the new -template tag method immediately. - -First of all, the location of the middleware and related functions have -changed. There are backwards compatible stub files so that old imports will -continue to work for now, but they are deprecated and will be removed in Django -1.4. The following changes have been made: - - * Middleware have been moved to ``django.middleware.csrf`` - * Decorators have been moved to ``django.views.decorators.csrf`` - -====================================================== ============================================== - Old New -====================================================== ============================================== -django.contrib.csrf.middleware.CsrfMiddleware django.middleware.csrf.CsrfMiddleware -django.contrib.csrf.middleware.CsrfViewMiddleware django.middleware.csrf.CsrfViewMiddleware -django.contrib.csrf.middleware.CsrfResponseMiddleware django.middleware.csrf.CsrfResponseMiddleware -django.contrib.csrf.middleware.csrf_exempt django.views.decorators.csrf.csrf_exempt -django.contrib.csrf.middleware.csrf_view_exempt django.views.decorators.csrf.csrf_view_exempt -django.contrib.csrf.middleware.csrf_response_exempt django.views.decorators.csrf.csrf_response_exempt -====================================================== ============================================== - -You should update any imports, and also the paths in your -:setting:`MIDDLEWARE_CLASSES`. - -If you have ``CsrfMiddleware`` in your :setting:`MIDDLEWARE_CLASSES`, you will now -have a working installation with CSRF protection. It is recommended at this -point that you replace ``CsrfMiddleware`` with its two components, -``CsrfViewMiddleware`` and ``CsrfResponseMiddleware`` (in that order). - -If you do not have any of the middleware in your :setting:`MIDDLEWARE_CLASSES`, -you will have a working installation but without any CSRF protection for your -views (just as you had before). It is strongly recommended to install -``CsrfViewMiddleware`` and ``CsrfResponseMiddleware``, as described above. - -Note that contrib apps, such as the admin, have been updated to use the -``csrf_protect`` decorator, so that they are secured even if you do not add the -``CsrfViewMiddleware`` to your settings. However, if you have supplied -customised templates to any of the view functions of contrib apps (whether -explicitly via a keyword argument, or by overriding built-in templates), **you -MUST update them** to include the :ttag:`csrf_token` template tag as described -above, or they will stop working. (If you cannot update these templates for -some reason, you will be forced to use ``CsrfResponseMiddleware`` for these -views to continue working). - -Note also, if you are using the comments app, and you are not going to add -``CsrfViewMiddleware`` to your settings (not recommended), you will need to add -the ``csrf_protect`` decorator to any views that include the comment forms and -target the comment views (usually using the :ttag:`comment_form_target` template -tag). - -Assuming you have followed the above, all views in your Django site will now be -protected by the ``CsrfViewMiddleware``. Contrib apps meet the requirements -imposed by the ``CsrfViewMiddleware`` using the template tag, and other -applications in your project will meet its requirements by virtue of the -``CsrfResponseMiddleware``. - -The next step is to update all your applications to use the template tag, as -described in `How to use it`_, steps 2-3. This can be done as soon as is -practical. Any applications that are updated will now require Django 1.1.2 or -later, since they will use the CSRF template tag which was not available in -earlier versions. (The template tag in 1.1.2 is actually a no-op that exists -solely to ease the transition to 1.2 — it allows apps to be created that have -CSRF protection under 1.2 without requiring users of the apps to upgrade to the -Django 1.2.X series). - -The utility script ``extras/csrf_migration_helper.py`` can help to automate the -finding of code and templates that may need to be upgraded. It contains full -help on how to use it. - -Finally, once all applications are upgraded, ``CsrfResponseMiddleware`` can be -removed from your settings. - -While ``CsrfResponseMiddleware`` is still in use, the ``csrf_response_exempt`` -decorator, described in `Exceptions`_, may be useful. The post-processing -middleware imposes a performance hit and a potential vulnerability, and any -views that have been upgraded to use the new template tag method no longer need -it. - Exceptions ---------- -.. versionchanged:: 1.2 - Import paths for the decorators below were changed. - To manually exclude a view function from being handled by either of the two CSRF middleware, you can use the ``csrf_exempt`` decorator, found in the ``django.views.decorators.csrf`` module. For example:: @@ -295,13 +149,6 @@ middleware, you can use the ``csrf_exempt`` decorator, found in the def my_view(request): return HttpResponse('Hello world') -Like the middleware, 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. - Subdomains ---------- @@ -350,8 +197,7 @@ The CSRF protection is based on the following things: outgoing POST forms. The value of this field is the value of the CSRF cookie. - This part is done by the template tag (and with the legacy method, it is done - by ``CsrfResponseMiddleware``). + This part is done by the template tag. 3. For all incoming POST requests, a CSRF cookie must be present, and the 'csrfmiddlewaretoken' field must be present and correct. If it isn't, the @@ -375,22 +221,16 @@ forms). GET requests ought never to have any potentially dangerous side effects (see `9.1.1 Safe Methods, HTTP 1.1, RFC 2616`_), and so a CSRF attack with a GET request ought to be harmless. -``CsrfResponseMiddleware`` checks the Content-Type before modifying the -response, and only pages that are served as 'text/html' or -'application/xml+xhtml' are modified. - .. _9.1.1 Safe Methods, HTTP 1.1, RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html Caching ======= -If the :ttag:`csrf_token` template tag is used by a template (or the ``get_token`` -function is called some other way), ``CsrfViewMiddleware`` will add a cookie and -a ``Vary: Cookie`` header to the response. Similarly, -``CsrfResponseMiddleware`` will send the ``Vary: Cookie`` header if it inserted -a token. This means that these middleware will play well with the cache -middleware if it is used as instructed (``UpdateCacheMiddleware`` goes before -all other middleware). +If the :ttag:`csrf_token` template tag is used by a template (or the +``get_token`` function is called some other way), ``CsrfViewMiddleware`` will +add a cookie and a ``Vary: Cookie`` header to the response. This means that the +middleware will play well with the cache middleware if it is used as instructed +(``UpdateCacheMiddleware`` goes before all other middleware). However, if you use cache decorators on individual views, the CSRF middleware will not yet have been able to set the Vary header. In this case, on any views @@ -434,13 +274,6 @@ to set cookies). Note that even without CSRF, there are other vulnerabilities, such as session fixation, that make giving subdomains to untrusted parties a bad idea, and these vulnerabilities cannot easily be fixed with current browsers. -If you are using ``CsrfResponseMiddleware`` and your app creates HTML pages and -forms in some unusual way, (e.g. 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. You should -use the template tag or :meth:`django.middleware.csrf.get_token` to get -the CSRF token and ensure it is included when your form is submitted. - Contrib and reusable apps ========================= diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/regressiontests/csrf_tests/tests.py index ba1760aa91..37a10044c4 100644 --- a/tests/regressiontests/csrf_tests/tests.py +++ b/tests/regressiontests/csrf_tests/tests.py @@ -3,7 +3,7 @@ import warnings from django.test import TestCase from django.http import HttpRequest, HttpResponse -from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware +from django.middleware.csrf import CsrfViewMiddleware from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt, requires_csrf_token from django.core.context_processors import csrf from django.conf import settings @@ -16,11 +16,6 @@ def post_form_response(): """, mimetype="text/html") return resp -def post_form_response_non_html(): - resp = post_form_response() - resp["Content-Type"] = "application/xml" - return resp - def post_form_view(request): """A view that returns a POST form (without a token)""" return post_form_response() @@ -54,7 +49,7 @@ class TestingHttpRequest(HttpRequest): def is_secure(self): return getattr(self, '_is_secure', False) -class CsrfMiddlewareTest(TestCase): +class CsrfViewMiddlewareTest(TestCase): # The csrf token is potentially from an untrusted source, so could have # characters that need dealing with. _csrf_id_cookie = "<1>\xc2\xa1" @@ -94,51 +89,12 @@ class CsrfMiddlewareTest(TestCase): def _check_token_present(self, response, csrf_id=None): self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % (csrf_id or self._csrf_id)) - # Check the post processing and outgoing cookie - def test_process_response_no_csrf_cookie(self): + def test_process_response_get_token_used(self): """ - When no prior CSRF cookie exists, check that the cookie is created and a - token is inserted. + When get_token is used, check that the cookie is created and headers + patched. """ req = self._get_GET_no_csrf_cookie_request() - CsrfMiddleware().process_view(req, post_form_view, (), {}) - - resp = post_form_response() - resp_content = resp.content # needed because process_response modifies resp - resp2 = CsrfMiddleware().process_response(req, resp) - - csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) - self.assertNotEqual(csrf_cookie, False) - self.assertNotEqual(resp_content, resp2.content) - self._check_token_present(resp2, csrf_cookie.value) - # Check the Vary header got patched correctly - self.assertTrue('Cookie' in resp2.get('Vary','')) - - def test_process_response_for_exempt_view(self): - """ - Check that a view decorated with 'csrf_view_exempt' is still - post-processed to add the CSRF token. - """ - req = self._get_GET_no_csrf_cookie_request() - CsrfMiddleware().process_view(req, csrf_view_exempt(post_form_view), (), {}) - - resp = post_form_response() - resp_content = resp.content # needed because process_response modifies resp - resp2 = CsrfMiddleware().process_response(req, resp) - - csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) - self.assertNotEqual(csrf_cookie, False) - self.assertNotEqual(resp_content, resp2.content) - self._check_token_present(resp2, csrf_cookie.value) - - def test_process_response_no_csrf_cookie_view_only_get_token_used(self): - """ - When no prior CSRF cookie exists, check that the cookie is created, even - if only CsrfViewMiddleware is used. - """ - # This is checking that CsrfViewMiddleware has the cookie setting - # code. Most of the other tests use CsrfMiddleware. - req = self._get_GET_no_csrf_cookie_request() # token_view calls get_token() indirectly CsrfViewMiddleware().process_view(req, token_view, (), {}) resp = token_view(req) @@ -146,6 +102,7 @@ class CsrfMiddlewareTest(TestCase): csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) self.assertNotEqual(csrf_cookie, False) + self.assertTrue('Cookie' in resp2.get('Vary','')) def test_process_response_get_token_not_used(self): """ @@ -167,43 +124,6 @@ class CsrfMiddlewareTest(TestCase): csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) self.assertEqual(csrf_cookie, False) - def test_process_response_existing_csrf_cookie(self): - """ - Check that the token is inserted when a prior CSRF cookie exists - """ - req = self._get_GET_csrf_cookie_request() - CsrfMiddleware().process_view(req, post_form_view, (), {}) - - resp = post_form_response() - 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_non_html(self): - """ - Check the the post-processor does nothing for content-types not in _HTML_TYPES. - """ - req = self._get_GET_no_csrf_cookie_request() - CsrfMiddleware().process_view(req, post_form_view, (), {}) - resp = post_form_response_non_html() - resp_content = resp.content # needed because process_response modifies resp - resp2 = CsrfMiddleware().process_response(req, resp) - self.assertEqual(resp_content, resp2.content) - - def test_process_response_exempt_view(self): - """ - Check that no post processing is done for an exempt view - """ - req = self._get_GET_csrf_cookie_request() - view = csrf_exempt(post_form_view) - CsrfMiddleware().process_view(req, view, (), {}) - - resp = view(req) - resp_content = resp.content - resp2 = CsrfMiddleware().process_response(req, resp) - self.assertEqual(resp_content, resp2.content) - # Check the request processing def test_process_request_no_csrf_cookie(self): """ @@ -211,7 +131,7 @@ class CsrfMiddlewareTest(TestCase): incoming request. This will stop login CSRF. """ req = self._get_POST_no_csrf_cookie_request() - req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) + req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) self.assertEqual(403, req2.status_code) def test_process_request_csrf_cookie_no_token(self): @@ -220,7 +140,7 @@ class CsrfMiddlewareTest(TestCase): rejects the incoming request. """ req = self._get_POST_csrf_cookie_request() - req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) + req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) self.assertEqual(403, req2.status_code) def test_process_request_csrf_cookie_and_token(self): @@ -228,7 +148,7 @@ class CsrfMiddlewareTest(TestCase): Check that if both a cookie and a token is present, the middleware lets it through. """ req = self._get_POST_request_with_token() - req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) + req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) self.assertEqual(None, req2) def test_process_request_csrf_cookie_no_token_exempt_view(self): @@ -237,7 +157,7 @@ class CsrfMiddlewareTest(TestCase): decorator has been applied to the view, the middleware lets it through """ req = self._get_POST_csrf_cookie_request() - req2 = CsrfMiddleware().process_view(req, csrf_exempt(post_form_view), (), {}) + req2 = CsrfViewMiddleware().process_view(req, csrf_exempt(post_form_view), (), {}) self.assertEqual(None, req2) def test_csrf_token_in_header(self): @@ -246,7 +166,7 @@ class CsrfMiddlewareTest(TestCase): """ req = self._get_POST_csrf_cookie_request() req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id - req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) + req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) self.assertEqual(None, req2) # Tests for the template tag method @@ -307,15 +227,6 @@ class CsrfMiddlewareTest(TestCase): csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME] self._check_token_present(resp, csrf_id=csrf_cookie.value) - def test_response_middleware_without_view_middleware(self): - """ - Check that CsrfResponseMiddleware finishes without error if the view middleware - has not been called, as is the case if a request middleware returns a response. - """ - req = self._get_GET_no_csrf_cookie_request() - resp = post_form_view(req) - CsrfMiddleware().process_response(req, resp) - def test_https_bad_referer(self): """ Test that a POST HTTPS request with a bad referer is rejected