diff options
Diffstat (limited to 'django')
| -rw-r--r-- | django/conf/global_settings.py | 4 | ||||
| -rw-r--r-- | django/conf/project_template/settings.py | 2 | ||||
| -rw-r--r-- | django/contrib/admin/options.py | 2 | ||||
| -rw-r--r-- | django/contrib/admin/sites.py | 3 | ||||
| -rw-r--r-- | django/contrib/auth/views.py | 2 | ||||
| -rw-r--r-- | django/contrib/comments/views/comments.py | 2 | ||||
| -rw-r--r-- | django/contrib/comments/views/moderation.py | 2 | ||||
| -rw-r--r-- | django/contrib/csrf/context_processors.py | 20 | ||||
| -rw-r--r-- | django/contrib/csrf/decorators.py | 10 | ||||
| -rw-r--r-- | django/contrib/csrf/middleware.py | 299 | ||||
| -rw-r--r-- | django/contrib/csrf/models.py | 1 | ||||
| -rw-r--r-- | django/contrib/csrf/tests.py | 323 | ||||
| -rw-r--r-- | django/contrib/formtools/wizard.py | 2 | ||||
| -rw-r--r-- | django/core/context_processors.py | 19 | ||||
| -rw-r--r-- | django/middleware/csrf.py | 262 | ||||
| -rw-r--r-- | django/template/context.py | 2 | ||||
| -rw-r--r-- | django/views/csrf.py (renamed from django/contrib/csrf/views.py) | 0 | ||||
| -rw-r--r-- | django/views/decorators/csrf.py | 47 |
18 files changed, 344 insertions, 658 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 62c7dd90c2..a0ce96a818 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -300,7 +300,7 @@ DEFAULT_INDEX_TABLESPACE = '' MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.csrf.middleware.CsrfViewMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', # 'django.middleware.http.ConditionalGetMiddleware', # 'django.middleware.gzip.GZipMiddleware', @@ -381,7 +381,7 @@ PASSWORD_RESET_TIMEOUT_DAYS = 3 # Dotted path to callable to be used as view when a request is # rejected by the CSRF middleware. -CSRF_FAILURE_VIEW = 'django.contrib.csrf.views.csrf_failure' +CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure' # Name and domain for CSRF cookie. CSRF_COOKIE_NAME = 'csrftoken' diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py index f83f3d505a..9b0b516c80 100644 --- a/django/conf/project_template/settings.py +++ b/django/conf/project_template/settings.py @@ -60,7 +60,7 @@ TEMPLATE_LOADERS = ( MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.csrf.middleware.CsrfViewMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', ) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index c702e87340..0119e430b8 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -6,7 +6,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.admin import widgets from django.contrib.admin import helpers from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict -from django.contrib.csrf.decorators import csrf_protect +from django.views.decorators.csrf import csrf_protect from django.core.exceptions import PermissionDenied from django.db import models, transaction from django.db.models.fields import BLANK_CHOICE_DASH diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index d686540e56..33126999c8 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -3,8 +3,7 @@ from django import http, template from django.contrib.admin import ModelAdmin from django.contrib.admin import actions from django.contrib.auth import authenticate, login -from django.contrib.csrf.middleware import csrf_response_exempt -from django.contrib.csrf.decorators import csrf_protect +from django.views.decorators.csrf import csrf_protect, csrf_response_exempt from django.db.models.base import ModelBase from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 9d36710211..d427874df0 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -4,7 +4,7 @@ from django.contrib.auth.decorators import login_required from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm, PasswordChangeForm from django.contrib.auth.tokens import default_token_generator -from django.contrib.csrf.decorators import csrf_protect +from django.views.decorators.csrf import csrf_protect from django.core.urlresolvers import reverse from django.shortcuts import render_to_response, get_object_or_404 from django.contrib.sites.models import Site, RequestSite diff --git a/django/contrib/comments/views/comments.py b/django/contrib/comments/views/comments.py index ada7e9c77e..7fbe80eead 100644 --- a/django/contrib/comments/views/comments.py +++ b/django/contrib/comments/views/comments.py @@ -10,7 +10,7 @@ from django.utils.html import escape from django.views.decorators.http import require_POST from django.contrib import comments from django.contrib.comments import signals -from django.contrib.csrf.decorators import csrf_protect +from django.views.decorators.csrf import csrf_protect class CommentPostBadRequest(http.HttpResponseBadRequest): """ diff --git a/django/contrib/comments/views/moderation.py b/django/contrib/comments/views/moderation.py index 76db326c31..73304ba416 100644 --- a/django/contrib/comments/views/moderation.py +++ b/django/contrib/comments/views/moderation.py @@ -5,7 +5,7 @@ from django.contrib.auth.decorators import login_required, permission_required from utils import next_redirect, confirmation_view from django.contrib import comments from django.contrib.comments import signals -from django.contrib.csrf.decorators import csrf_protect +from django.views.decorators.csrf import csrf_protect @csrf_protect @login_required diff --git a/django/contrib/csrf/context_processors.py b/django/contrib/csrf/context_processors.py deleted file mode 100644 index b78030a0b2..0000000000 --- a/django/contrib/csrf/context_processors.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.contrib.csrf.middleware import get_token -from django.utils.functional import lazy - -def csrf(request): - """ - Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if - it has not been provided by either a view decorator or the middleware - """ - def _get_val(): - token = get_token(request) - if token is None: - # In order to be able to provide debugging info in the - # case of misconfiguration, we use a sentinel value - # instead of returning an empty dict. - return 'NOTPROVIDED' - else: - return token - _get_val = lazy(_get_val, str) - - return {'csrf_token': _get_val() } diff --git a/django/contrib/csrf/decorators.py b/django/contrib/csrf/decorators.py deleted file mode 100644 index 67e33bce5c..0000000000 --- a/django/contrib/csrf/decorators.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.contrib.csrf.middleware import CsrfViewMiddleware -from django.utils.decorators import decorator_from_middleware - -csrf_protect = decorator_from_middleware(CsrfViewMiddleware) -csrf_protect.__name__ = "csrf_protect" -csrf_protect.__doc__ = """ -This decorator adds CSRF protection in exactly the same way as -CsrfViewMiddleware, but it can be used on a per view basis. Using both, or -using the decorator multiple times, is harmless and efficient. -""" diff --git a/django/contrib/csrf/middleware.py b/django/contrib/csrf/middleware.py index daee12379e..4885cfcc3e 100644 --- a/django/contrib/csrf/middleware.py +++ b/django/contrib/csrf/middleware.py @@ -1,294 +1,7 @@ -""" -Cross Site Request Forgery Middleware. +from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware, CsrfResponseMiddleware +from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt, csrf_response_exempt -This module provides a middleware that implements protection -against request forgeries from other sites. -""" - -import itertools -import re -import random -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.core.urlresolvers import get_callable -from django.utils.cache import patch_vary_headers -from django.utils.hashcompat import md5_constructor -from django.utils.safestring import mark_safe - -_POST_FORM_RE = \ - re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) - -_HTML_TYPES = ('text/html', 'application/xhtml+xml') - -# Use the system (hardware-based) random number generator if it exists. -if hasattr(random, 'SystemRandom'): - randrange = random.SystemRandom().randrange -else: - randrange = random.randrange -_MAX_CSRF_KEY = 18446744073709551616L # 2 << 63 - -def _get_failure_view(): - """ - Returns the view to be used for CSRF rejections - """ - return get_callable(settings.CSRF_FAILURE_VIEW) - -def _get_new_csrf_key(): - return md5_constructor("%s%s" - % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() - -def _make_legacy_session_token(session_id): - return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() - -def get_token(request): - """ - Returns the the CSRF token required for a POST form. - - A side effect of calling this function is to make the the csrf_protect - decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' - header to the outgoing response. For this reason, you may need to use this - function lazily, as is done by the csrf context processor. - """ - request.META["CSRF_COOKIE_USED"] = True - return request.META.get("CSRF_COOKIE", None) - -class CsrfViewMiddleware(object): - """ - Middleware that requires a present and correct csrfmiddlewaretoken - for POST requests that have a CSRF cookie, and sets an outgoing - CSRF cookie. - - This middleware should be used in conjunction with the csrf_token template - tag. - """ - def process_view(self, request, callback, callback_args, callback_kwargs): - if getattr(callback, 'csrf_exempt', False): - return None - - if getattr(request, 'csrf_processing_done', False): - return None - - reject = lambda s: _get_failure_view()(request, reason=s) - def accept(): - # Avoid checking the request twice by adding a custom attribute to - # request. This will be relevant when both decorator and middleware - # are used. - request.csrf_processing_done = True - return None - - # If the user doesn't have a CSRF cookie, generate one and store it in the - # request, so it's available to the view. We'll store it in a cookie when - # we reach the response. - try: - request.META["CSRF_COOKIE"] = request.COOKIES[settings.CSRF_COOKIE_NAME] - cookie_is_new = False - except KeyError: - # No cookie, so create one. - request.META["CSRF_COOKIE"] = _get_new_csrf_key() - cookie_is_new = True - - if request.method == 'POST': - if getattr(request, '_dont_enforce_csrf_checks', False): - # Mechanism to turn off CSRF checks for test suite. It comes after - # the creation of CSRF cookies, so that everything else continues to - # work exactly the same (e.g. cookies are sent etc), but before the - # any branches that call reject() - return accept() - - if request.is_ajax(): - # .is_ajax() is based on the presence of X-Requested-With. In - # the context of a browser, this can only be sent if using - # XmlHttpRequest. Browsers implement careful policies for - # XmlHttpRequest: - # - # * Normally, only same-domain requests are allowed. - # - # * Some browsers (e.g. Firefox 3.5 and later) relax this - # carefully: - # - # * if it is a 'simple' GET or POST request (which can - # include no custom headers), it is allowed to be cross - # domain. These requests will not be recognized as AJAX. - # - # * if a 'preflight' check with the server confirms that the - # server is expecting and allows the request, cross domain - # requests even with custom headers are allowed. These - # requests will be recognized as AJAX, but can only get - # through when the developer has specifically opted in to - # allowing the cross-domain POST request. - # - # So in all cases, it is safe to allow these requests through. - return accept() - - if request.is_secure(): - # Strict referer checking for HTTPS - referer = request.META.get('HTTP_REFERER') - if referer is None: - return reject("Referer checking failed - no Referer.") - - # The following check ensures that the referer is HTTPS, - # the domains match and the ports match. This might be too strict. - good_referer = 'https://%s/' % request.get_host() - if not referer.startswith(good_referer): - return reject("Referer checking failed - %s does not match %s." % - (referer, good_referer)) - - # If the user didn't already have a CSRF key, then accept the - # session key for the middleware token, so CSRF protection isn't lost - # for the period between upgrading to CSRF cookes to the first time - # each user comes back to the site to receive one. - if cookie_is_new: - try: - session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] - csrf_token = _make_legacy_session_token(session_id) - except KeyError: - # No CSRF cookie and no session cookie. For POST requests, - # we insist on a CSRF cookie, and in this way we can avoid - # all CSRF attacks, including login CSRF. - return reject("No CSRF cookie.") - else: - csrf_token = request.META["CSRF_COOKIE"] - - # check incoming token - request_csrf_token = request.POST.get('csrfmiddlewaretoken', None) - if request_csrf_token != csrf_token: - return reject("CSRF token missing or incorrect.") - - return accept() - - def process_response(self, request, response): - if getattr(response, 'csrf_processing_done', False): - return response - - # If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was - # never called, probaby because a request middleware returned a response - # (for example, contrib.auth redirecting to a login page). - if request.META.get("CSRF_COOKIE") is None: - return response - - if not request.META.get("CSRF_COOKIE_USED", False): - return response - - # Set the CSRF cookie even if it's already set, so we renew the expiry timer. - response.set_cookie(settings.CSRF_COOKIE_NAME, - request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52, - domain=settings.CSRF_COOKIE_DOMAIN) - # Content varies with the CSRF cookie, so set the Vary header. - 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).", - PendingDeprecationWarning - ) - - 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 <form> tag plus the added <input> element""" - return mark_safe(match.group() + "<div style='display:none;'>" + \ - "<input type='hidden' " + idattributes.next() + \ - " name='csrfmiddlewaretoken' value='" + csrf_token + \ - "' /></div>") - - # 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) - -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): - """ - Marks a view function as being exempt from the CSRF checks - and post processing. - - This is the same as using both the csrf_view_exempt and - csrf_response_exempt decorators. - """ - return csrf_response_exempt(csrf_view_exempt(view_func)) +import warnings +warnings.warn("This import for CSRF functionality is deprecated. Please use django.middleware.csrf for the middleware and django.views.decorators.csrf for decorators.", + PendingDeprecationWarning + ) diff --git a/django/contrib/csrf/models.py b/django/contrib/csrf/models.py deleted file mode 100644 index 71abcc5198..0000000000 --- a/django/contrib/csrf/models.py +++ /dev/null @@ -1 +0,0 @@ -# models.py file for tests to run. diff --git a/django/contrib/csrf/tests.py b/django/contrib/csrf/tests.py deleted file mode 100644 index 14015736c4..0000000000 --- a/django/contrib/csrf/tests.py +++ /dev/null @@ -1,323 +0,0 @@ -# -*- coding: utf-8 -*- - -from django.test import TestCase -from django.http import HttpRequest, HttpResponse -from django.contrib.csrf.middleware import CsrfMiddleware, CsrfViewMiddleware, csrf_exempt -from django.contrib.csrf.context_processors import csrf -from django.contrib.sessions.middleware import SessionMiddleware -from django.utils.importlib import import_module -from django.conf import settings -from django.template import RequestContext, Template - -# Response/views used for CsrfResponseMiddleware and CsrfViewMiddleware tests -def post_form_response(): - resp = HttpResponse(content=""" -<html><body><form method="POST"><input type="text" /></form></body></html> -""", 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() - -# Response/views used for template tag tests -def _token_template(): - return Template("{% csrf_token %}") - -def _render_csrf_token_template(req): - context = RequestContext(req, processors=[csrf]) - template = _token_template() - return template.render(context) - -def token_view(request): - """A view that uses {% csrf_token %}""" - return HttpResponse(_render_csrf_token_template(request)) - -def non_token_view_using_request_processor(request): - """ - A view that doesn't use the token, but does use the csrf view processor. - """ - context = RequestContext(request, processors=[csrf]) - template = Template("") - return HttpResponse(template.render(context)) - -class TestingHttpRequest(HttpRequest): - """ - A version of HttpRequest that allows us to change some things - more easily - """ - def is_secure(self): - return getattr(self, '_is_secure', False) - -class CsrfMiddlewareTest(TestCase): - _csrf_id = "1" - - # This is a valid session token for this ID and secret key. This was generated using - # the old code that we're to be backwards-compatible with. Don't use the CSRF code - # to generate this hash, or we're merely testing the code against itself and not - # checking backwards-compatibility. This is also the output of (echo -n test1 | md5sum). - _session_token = "5a105e8b9d40e1329780d62ea2265d8a" - _session_id = "1" - _secret_key_for_session_test= "test" - - def _get_GET_no_csrf_cookie_request(self): - return TestingHttpRequest() - - def _get_GET_csrf_cookie_request(self): - req = TestingHttpRequest() - req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id - return req - - def _get_POST_csrf_cookie_request(self): - req = self._get_GET_csrf_cookie_request() - req.method = "POST" - return req - - def _get_POST_no_csrf_cookie_request(self): - req = self._get_GET_no_csrf_cookie_request() - req.method = "POST" - return req - - def _get_POST_request_with_token(self): - req = self._get_POST_csrf_cookie_request() - req.POST['csrfmiddlewaretoken'] = self._csrf_id - return req - - def _get_POST_session_request_with_token(self): - req = self._get_POST_no_csrf_cookie_request() - req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id - req.POST['csrfmiddlewaretoken'] = self._session_token - return req - - def _get_POST_session_request_no_token(self): - req = self._get_POST_no_csrf_cookie_request() - req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id - return req - - 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): - """ - When no prior CSRF cookie exists, check that the cookie is created and a - token is inserted. - """ - 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.assert_('Cookie' in resp2.get('Vary','')) - - 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) - resp2 = CsrfViewMiddleware().process_response(req, resp) - - csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) - self.assertNotEqual(csrf_cookie, False) - - def test_process_response_get_token_not_used(self): - """ - Check that if get_token() is not called, the view middleware does not - add a cookie. - """ - # This is important to make pages cacheable. Pages which do call - # get_token(), assuming they use the token, are not cacheable because - # the token is specific to the user - req = self._get_GET_no_csrf_cookie_request() - # non_token_view_using_request_processor does not call get_token(), but - # does use the csrf request processor. By using this, we are testing - # that the view processor is properly lazy and doesn't call get_token() - # until needed. - CsrfViewMiddleware().process_view(req, non_token_view_using_request_processor, (), {}) - resp = non_token_view_using_request_processor(req) - resp2 = CsrfViewMiddleware().process_response(req, resp) - - 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.assertEquals(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_POST_csrf_cookie_request() - resp = csrf_exempt(post_form_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_no_csrf_cookie(self): - """ - Check that if neither a CSRF cookie nor a session cookie are present, - the middleware rejects the incoming request. This will stop login CSRF. - """ - req = self._get_POST_no_csrf_cookie_request() - req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) - self.assertEquals(403, req2.status_code) - - def test_process_request_csrf_cookie_no_token(self): - """ - Check that if a CSRF cookie is present but no token, the middleware - rejects the incoming request. - """ - req = self._get_POST_csrf_cookie_request() - req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) - self.assertEquals(403, req2.status_code) - - def test_process_request_csrf_cookie_and_token(self): - """ - 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, (), {}) - self.assertEquals(None, req2) - - def test_process_request_session_cookie_no_csrf_cookie_token(self): - """ - When no CSRF cookie exists, but the user has a session, check that a token - using the session cookie as a legacy CSRF cookie is accepted. - """ - orig_secret_key = settings.SECRET_KEY - settings.SECRET_KEY = self._secret_key_for_session_test - try: - req = self._get_POST_session_request_with_token() - req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) - self.assertEquals(None, req2) - finally: - settings.SECRET_KEY = orig_secret_key - - def test_process_request_session_cookie_no_csrf_cookie_no_token(self): - """ - Check that if a session cookie is present but no token and no CSRF cookie, - the request is rejected. - """ - req = self._get_POST_session_request_no_token() - req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) - self.assertEquals(403, req2.status_code) - - def test_process_request_csrf_cookie_no_token_exempt_view(self): - """ - Check that if a CSRF cookie 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_csrf_cookie_request() - req2 = CsrfMiddleware().process_view(req, csrf_exempt(post_form_view), (), {}) - self.assertEquals(None, req2) - - def test_ajax_exemption(self): - """ - Check that AJAX requests are automatically exempted. - """ - req = self._get_POST_csrf_cookie_request() - req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - req2 = CsrfMiddleware().process_view(req, post_form_view, (), {}) - self.assertEquals(None, req2) - - # Tests for the template tag method - def test_token_node_no_csrf_cookie(self): - """ - Check that CsrfTokenNode works when no CSRF cookie is set - """ - req = self._get_GET_no_csrf_cookie_request() - resp = token_view(req) - self.assertEquals(u"", resp.content) - - def test_token_node_with_csrf_cookie(self): - """ - Check that CsrfTokenNode works when a CSRF cookie is set - """ - req = self._get_GET_csrf_cookie_request() - CsrfViewMiddleware().process_view(req, token_view, (), {}) - resp = token_view(req) - self._check_token_present(resp) - - def test_token_node_with_new_csrf_cookie(self): - """ - Check that CsrfTokenNode works when a CSRF cookie is created by - the middleware (when one was not already present) - """ - req = self._get_GET_no_csrf_cookie_request() - CsrfViewMiddleware().process_view(req, token_view, (), {}) - resp = token_view(req) - resp2 = CsrfViewMiddleware().process_response(req, resp) - 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 - """ - req = self._get_POST_request_with_token() - req._is_secure = True - req.META['HTTP_HOST'] = 'www.example.com' - req.META['HTTP_REFERER'] = 'https://www.evil.org/somepage' - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) - self.assertNotEqual(None, req2) - self.assertEquals(403, req2.status_code) - - def test_https_good_referer(self): - """ - Test that a POST HTTPS request with a good referer is accepted - """ - req = self._get_POST_request_with_token() - req._is_secure = True - req.META['HTTP_HOST'] = 'www.example.com' - req.META['HTTP_REFERER'] = 'https://www.example.com/somepage' - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) - self.assertEquals(None, req2) diff --git a/django/contrib/formtools/wizard.py b/django/contrib/formtools/wizard.py index 60fe314217..4729e155b8 100644 --- a/django/contrib/formtools/wizard.py +++ b/django/contrib/formtools/wizard.py @@ -14,7 +14,7 @@ from django.template.context import RequestContext from django.utils.hashcompat import md5_constructor from django.utils.translation import ugettext_lazy as _ from django.contrib.formtools.utils import security_hash -from django.contrib.csrf.decorators import csrf_protect +from django.views.decorators.csrf import csrf_protect class FormWizard(object): # Dictionary of extra template context variables. diff --git a/django/core/context_processors.py b/django/core/context_processors.py index 0dbd8449b6..b950dba0f6 100644 --- a/django/core/context_processors.py +++ b/django/core/context_processors.py @@ -8,6 +8,7 @@ RequestContext. """ from django.conf import settings +from django.middleware.csrf import get_token from django.utils.functional import lazy, memoize, SimpleLazyObject def auth(request): @@ -40,6 +41,24 @@ def auth(request): 'perms': lazy(lambda: PermWrapper(get_user()), PermWrapper)(), } +def csrf(request): + """ + Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if + it has not been provided by either a view decorator or the middleware + """ + def _get_val(): + token = get_token(request) + if token is None: + # In order to be able to provide debugging info in the + # case of misconfiguration, we use a sentinel value + # instead of returning an empty dict. + return 'NOTPROVIDED' + else: + return token + _get_val = lazy(_get_val, str) + + return {'csrf_token': _get_val() } + def debug(request): "Returns context variables helpful for debugging." context_extras = {} diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py new file mode 100644 index 0000000000..339487bbcc --- /dev/null +++ b/django/middleware/csrf.py @@ -0,0 +1,262 @@ +""" +Cross Site Request Forgery Middleware. + +This module provides a middleware that implements protection +against request forgeries from other sites. +""" + +import itertools +import re +import random +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.core.urlresolvers import get_callable +from django.utils.cache import patch_vary_headers +from django.utils.hashcompat import md5_constructor +from django.utils.safestring import mark_safe + +_POST_FORM_RE = \ + re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) + +_HTML_TYPES = ('text/html', 'application/xhtml+xml') + +# Use the system (hardware-based) random number generator if it exists. +if hasattr(random, 'SystemRandom'): + randrange = random.SystemRandom().randrange +else: + randrange = random.randrange +_MAX_CSRF_KEY = 18446744073709551616L # 2 << 63 + +def _get_failure_view(): + """ + Returns the view to be used for CSRF rejections + """ + return get_callable(settings.CSRF_FAILURE_VIEW) + +def _get_new_csrf_key(): + return md5_constructor("%s%s" + % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest() + +def _make_legacy_session_token(session_id): + return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() + +def get_token(request): + """ + Returns the the CSRF token required for a POST form. + + A side effect of calling this function is to make the the csrf_protect + decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie' + header to the outgoing response. For this reason, you may need to use this + function lazily, as is done by the csrf context processor. + """ + request.META["CSRF_COOKIE_USED"] = True + return request.META.get("CSRF_COOKIE", None) + +class CsrfViewMiddleware(object): + """ + Middleware that requires a present and correct csrfmiddlewaretoken + for POST requests that have a CSRF cookie, and sets an outgoing + CSRF cookie. + + This middleware should be used in conjunction with the csrf_token template + tag. + """ + def process_view(self, request, callback, callback_args, callback_kwargs): + if getattr(callback, 'csrf_exempt', False): + return None + + if getattr(request, 'csrf_processing_done', False): + return None + + reject = lambda s: _get_failure_view()(request, reason=s) + def accept(): + # Avoid checking the request twice by adding a custom attribute to + # request. This will be relevant when both decorator and middleware + # are used. + request.csrf_processing_done = True + return None + + # If the user doesn't have a CSRF cookie, generate one and store it in the + # request, so it's available to the view. We'll store it in a cookie when + # we reach the response. + try: + request.META["CSRF_COOKIE"] = request.COOKIES[settings.CSRF_COOKIE_NAME] + cookie_is_new = False + except KeyError: + # No cookie, so create one. + request.META["CSRF_COOKIE"] = _get_new_csrf_key() + cookie_is_new = True + + if request.method == 'POST': + if getattr(request, '_dont_enforce_csrf_checks', False): + # Mechanism to turn off CSRF checks for test suite. It comes after + # the creation of CSRF cookies, so that everything else continues to + # work exactly the same (e.g. cookies are sent etc), but before the + # any branches that call reject() + return accept() + + if request.is_ajax(): + # .is_ajax() is based on the presence of X-Requested-With. In + # the context of a browser, this can only be sent if using + # XmlHttpRequest. Browsers implement careful policies for + # XmlHttpRequest: + # + # * Normally, only same-domain requests are allowed. + # + # * Some browsers (e.g. Firefox 3.5 and later) relax this + # carefully: + # + # * if it is a 'simple' GET or POST request (which can + # include no custom headers), it is allowed to be cross + # domain. These requests will not be recognized as AJAX. + # + # * if a 'preflight' check with the server confirms that the + # server is expecting and allows the request, cross domain + # requests even with custom headers are allowed. These + # requests will be recognized as AJAX, but can only get + # through when the developer has specifically opted in to + # allowing the cross-domain POST request. + # + # So in all cases, it is safe to allow these requests through. + return accept() + + if request.is_secure(): + # Strict referer checking for HTTPS + referer = request.META.get('HTTP_REFERER') + if referer is None: + return reject("Referer checking failed - no Referer.") + + # The following check ensures that the referer is HTTPS, + # the domains match and the ports match. This might be too strict. + good_referer = 'https://%s/' % request.get_host() + if not referer.startswith(good_referer): + return reject("Referer checking failed - %s does not match %s." % + (referer, good_referer)) + + # If the user didn't already have a CSRF key, then accept the + # session key for the middleware token, so CSRF protection isn't lost + # for the period between upgrading to CSRF cookes to the first time + # each user comes back to the site to receive one. + if cookie_is_new: + try: + session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] + csrf_token = _make_legacy_session_token(session_id) + except KeyError: + # No CSRF cookie and no session cookie. For POST requests, + # we insist on a CSRF cookie, and in this way we can avoid + # all CSRF attacks, including login CSRF. + return reject("No CSRF cookie.") + else: + csrf_token = request.META["CSRF_COOKIE"] + + # check incoming token + request_csrf_token = request.POST.get('csrfmiddlewaretoken', None) + if request_csrf_token != csrf_token: + return reject("CSRF token missing or incorrect.") + + return accept() + + def process_response(self, request, response): + if getattr(response, 'csrf_processing_done', False): + return response + + # If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was + # never called, probaby because a request middleware returned a response + # (for example, contrib.auth redirecting to a login page). + if request.META.get("CSRF_COOKIE") is None: + return response + + if not request.META.get("CSRF_COOKIE_USED", False): + return response + + # Set the CSRF cookie even if it's already set, so we renew the expiry timer. + response.set_cookie(settings.CSRF_COOKIE_NAME, + request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52, + domain=settings.CSRF_COOKIE_DOMAIN) + # Content varies with the CSRF cookie, so set the Vary header. + 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).", + PendingDeprecationWarning + ) + + 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 <form> tag plus the added <input> element""" + return mark_safe(match.group() + "<div style='display:none;'>" + \ + "<input type='hidden' " + idattributes.next() + \ + " name='csrfmiddlewaretoken' value='" + csrf_token + \ + "' /></div>") + + # 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/django/template/context.py b/django/template/context.py index 5fbdaf3a0d..f57a3aaa64 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -6,7 +6,7 @@ _standard_context_processors = None # We need the CSRF processor no matter what the user has in their settings, # because otherwise it is a security vulnerability, and we can't afford to leave # this to human error or failure to read migration instructions. -_builtin_context_processors = ('django.contrib.csrf.context_processors.csrf',) +_builtin_context_processors = ('django.core.context_processors.csrf',) class ContextPopException(Exception): "pop() has been called more times than push()" diff --git a/django/contrib/csrf/views.py b/django/views/csrf.py index dd8a8966b1..dd8a8966b1 100644 --- a/django/contrib/csrf/views.py +++ b/django/views/csrf.py diff --git a/django/views/decorators/csrf.py b/django/views/decorators/csrf.py new file mode 100644 index 0000000000..b789872efe --- /dev/null +++ b/django/views/decorators/csrf.py @@ -0,0 +1,47 @@ +from django.middleware.csrf import CsrfViewMiddleware +from django.utils.decorators import decorator_from_middleware +try: + from functools import wraps +except ImportError: + from django.utils.functional import wraps # Python 2.3, 2.4 fallback. + +csrf_protect = decorator_from_middleware(CsrfViewMiddleware) +csrf_protect.__name__ = "csrf_protect" +csrf_protect.__doc__ = """ +This decorator adds CSRF protection in exactly the same way as +CsrfViewMiddleware, but it can be used on a per view basis. Using both, or +using the decorator multiple times, is harmless and efficient. +""" + +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): + """ + Marks a view function as being exempt from the CSRF checks + and post processing. + + This is the same as using both the csrf_view_exempt and + csrf_response_exempt decorators. + """ + return csrf_response_exempt(csrf_view_exempt(view_func)) |
