Kaydet (Commit) 8e70cef9 authored tarafından Luke Plant's avatar Luke Plant

Fixed #9977 - CsrfMiddleware gets template tag added, session dependency…

Fixed #9977 - CsrfMiddleware gets template tag added, session dependency removed, and turned on by default.

This is a large change to CSRF protection for Django.  It includes:

 * removing the dependency on the session framework.
 * deprecating CsrfResponseMiddleware, and replacing with a core template tag.
 * turning on CSRF protection by default by adding CsrfViewMiddleware to
   the default value of MIDDLEWARE_CLASSES.
 * protecting all contrib apps (whatever is in settings.py)
   using a decorator.

For existing users of the CSRF functionality, it should be a seamless update,
but please note that it includes DEPRECATION of features in Django 1.1,
and there are upgrade steps which are detailed in the docs.

Many thanks to 'Glenn' and 'bthomas', who did a lot of the thinking and work
on the patch, and to lots of other people including Simon Willison and
Russell Keith-Magee who refined the ideas.

Details of the rationale for these changes is found here:

http://code.djangoproject.com/wiki/CsrfProtection

As of this commit, the CSRF code is mainly in 'contrib'.  The code will be
moved to core in a separate commit, to make the changeset as readable as
possible.



git-svn-id: http://code.djangoproject.com/svn/django/trunk@11660 bcc190cf-cafb-0310-a4f2-bffc1f526a37
üst d1da2614
......@@ -470,6 +470,8 @@ answer newbie questions, and generally made Django that much better:
Gasper Zejn <zejn@kiberpipa.org>
Jarek Zgoda <jarek.zgoda@gmail.com>
Cheng Zhang
Glenn
bthomas
A big THANK YOU goes to:
......
......@@ -300,6 +300,7 @@ DEFAULT_INDEX_TABLESPACE = ''
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.csrf.middleware.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
# 'django.middleware.http.ConditionalGetMiddleware',
# 'django.middleware.gzip.GZipMiddleware',
......@@ -374,6 +375,18 @@ LOGIN_REDIRECT_URL = '/accounts/profile/'
# The number of days a password reset link is valid for
PASSWORD_RESET_TIMEOUT_DAYS = 3
########
# CSRF #
########
# 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'
# Name and domain for CSRF cookie.
CSRF_COOKIE_NAME = 'csrftoken'
CSRF_COOKIE_DOMAIN = None
###########
# TESTING #
###########
......
......@@ -60,6 +60,7 @@ TEMPLATE_LOADERS = (
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.csrf.middleware.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
......
......@@ -6,6 +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.core.exceptions import PermissionDenied
from django.db import models, transaction
from django.db.models.fields import BLANK_CHOICE_DASH
......@@ -701,6 +702,8 @@ class ModelAdmin(BaseModelAdmin):
else:
return HttpResponseRedirect(".")
@csrf_protect
@transaction.commit_on_success
def add_view(self, request, form_url='', extra_context=None):
"The 'add' admin view for this model."
model = self.model
......@@ -782,8 +785,9 @@ class ModelAdmin(BaseModelAdmin):
}
context.update(extra_context or {})
return self.render_change_form(request, context, form_url=form_url, add=True)
add_view = transaction.commit_on_success(add_view)
@csrf_protect
@transaction.commit_on_success
def change_view(self, request, object_id, extra_context=None):
"The 'change' admin view for this model."
model = self.model
......@@ -871,8 +875,8 @@ class ModelAdmin(BaseModelAdmin):
}
context.update(extra_context or {})
return self.render_change_form(request, context, change=True, obj=obj)
change_view = transaction.commit_on_success(change_view)
@csrf_protect
def changelist_view(self, request, extra_context=None):
"The 'change list' admin view for this model."
from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
......@@ -985,6 +989,7 @@ class ModelAdmin(BaseModelAdmin):
'admin/change_list.html'
], context, context_instance=context_instance)
@csrf_protect
def delete_view(self, request, object_id, extra_context=None):
"The 'delete' admin view for this model."
opts = self.model._meta
......
......@@ -3,6 +3,8 @@ 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.db.models.base import ModelBase
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse
......@@ -186,6 +188,9 @@ class AdminSite(object):
return view(request, *args, **kwargs)
if not cacheable:
inner = never_cache(inner)
# We add csrf_protect here so this function can be used as a utility
# function for any view, without having to repeat 'csrf_protect'.
inner = csrf_response_exempt(csrf_protect(inner))
return update_wrapper(inner, view)
def get_urls(self):
......
......@@ -15,7 +15,7 @@
</div>
{% endif %}{% endblock %}
{% block content %}<div id="content-main">
<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
{% if form.errors %}
......
......@@ -29,7 +29,7 @@
</ul>
{% endif %}{% endif %}
{% endblock %}
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
<div>
{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
{% if save_on_top %}{% submit_row %}{% endif %}
......
......@@ -68,7 +68,7 @@
{% endif %}
{% endblock %}
<form action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
<form action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>{% csrf_token %}
{% if cl.formset %}
{{ cl.formset.management_form }}
{% endif %}
......
......@@ -22,7 +22,7 @@
{% else %}
<p>{% blocktrans with object as escaped_object %}Are you sure you want to delete the {{ object_name }} "{{ escaped_object }}"? All of the following related items will be deleted:{% endblocktrans %}</p>
<ul>{{ deleted_objects|unordered_list }}</ul>
<form action="" method="post">
<form action="" method="post">{% csrf_token %}
<div>
<input type="hidden" name="post" value="yes" />
<input type="submit" value="{% trans "Yes, I'm sure" %}" />
......
......@@ -23,7 +23,7 @@
{% for deleteable_object in deletable_objects %}
<ul>{{ deleteable_object|unordered_list }}</ul>
{% endfor %}
<form action="" method="post">
<form action="" method="post">{% csrf_token %}
<div>
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}" />
......
......@@ -14,7 +14,7 @@
<p class="errornote">{{ error_message }}</p>
{% endif %}
<div id="content-main">
<form action="{{ app_path }}" method="post" id="login-form">
<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
<div class="form-row">
<label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" />
</div>
......
......@@ -4,7 +4,7 @@
<div id="content-main">
<form action="" method="post">
<form action="" method="post">{% csrf_token %}
{% if form.errors %}
<p class="errornote">Your template had {{ form.errors|length }} error{{ form.errors|pluralize }}:</p>
......
......@@ -11,7 +11,7 @@
<p>{% trans "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." %}</p>
<form action="" method="post">
<form action="" method="post">{% csrf_token %}
{{ form.old_password.errors }}
<p class="aligned wide"><label for="id_old_password">{% trans 'Old password:' %}</label>{{ form.old_password }}</p>
......
......@@ -13,7 +13,7 @@
<p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
<form action="" method="post">
<form action="" method="post">{% csrf_token %}
{{ form.new_password1.errors }}
<p class="aligned wide"><label for="id_new_password1">{% trans 'New password:' %}</label>{{ form.new_password1 }}</p>
{{ form.new_password2.errors }}
......
......@@ -11,7 +11,7 @@
<p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." %}</p>
<form action="" method="post">
<form action="" method="post">{% csrf_token %}
{{ form.email.errors }}
<p><label for="id_email">{% trans 'E-mail address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p>
</form>
......
......@@ -4,6 +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.core.urlresolvers import reverse
from django.shortcuts import render_to_response, get_object_or_404
from django.contrib.sites.models import Site, RequestSite
......@@ -14,6 +15,8 @@ from django.utils.translation import ugettext as _
from django.contrib.auth.models import User
from django.views.decorators.cache import never_cache
@csrf_protect
@never_cache
def login(request, template_name='registration/login.html',
redirect_field_name=REDIRECT_FIELD_NAME,
authentication_form=AuthenticationForm):
......@@ -43,7 +46,6 @@ def login(request, template_name='registration/login.html',
'site': current_site,
'site_name': current_site.name,
}, context_instance=RequestContext(request))
login = never_cache(login)
def logout(request, next_page=None, template_name='registration/logged_out.html', redirect_field_name=REDIRECT_FIELD_NAME):
"Logs out the user and displays 'You are logged out' message."
......@@ -80,6 +82,7 @@ def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_N
# prompts for a new password
# - password_reset_complete shows a success message for the above
@csrf_protect
def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
email_template_name='registration/password_reset_email.html',
password_reset_form=PasswordResetForm, token_generator=default_token_generator,
......@@ -109,6 +112,7 @@ def password_reset(request, is_admin_site=False, template_name='registration/pas
def password_reset_done(request, template_name='registration/password_reset_done.html'):
return render_to_response(template_name, context_instance=RequestContext(request))
# Doesn't need csrf_protect since no-one can guess the URL
def password_reset_confirm(request, uidb36=None, token=None, template_name='registration/password_reset_confirm.html',
token_generator=default_token_generator, set_password_form=SetPasswordForm,
post_reset_redirect=None):
......@@ -146,6 +150,8 @@ def password_reset_complete(request, template_name='registration/password_reset_
return render_to_response(template_name, context_instance=RequestContext(request,
{'login_url': settings.LOGIN_URL}))
@csrf_protect
@login_required
def password_change(request, template_name='registration/password_change_form.html',
post_change_redirect=None, password_change_form=PasswordChangeForm):
if post_change_redirect is None:
......@@ -160,7 +166,6 @@ def password_change(request, template_name='registration/password_change_form.ht
return render_to_response(template_name, {
'form': form,
}, context_instance=RequestContext(request))
password_change = login_required(password_change)
def password_change_done(request, template_name='registration/password_change_done.html'):
return render_to_response(template_name, context_instance=RequestContext(request))
......@@ -6,7 +6,7 @@
{% block content %}
<h1>{% trans "Really make this comment public?" %}</h1>
<blockquote>{{ comment|linebreaks }}</blockquote>
<form action="." method="post">
<form action="." method="post">{% csrf_token %}
{% if next %}<input type="hidden" name="next" value="{{ next }}" id="next" />{% endif %}
<p class="submit">
<input type="submit" name="submit" value="{% trans "Approve" %}" /> or <a href="{{ comment.get_absolute_url }}">cancel</a>
......
......@@ -6,7 +6,7 @@
{% block content %}
<h1>{% trans "Really remove this comment?" %}</h1>
<blockquote>{{ comment|linebreaks }}</blockquote>
<form action="." method="post">
<form action="." method="post">{% csrf_token %}
{% if next %}<input type="hidden" name="next" value="{{ next }}" id="next" />{% endif %}
<p class="submit">
<input type="submit" name="submit" value="{% trans "Remove" %}" /> or <a href="{{ comment.get_absolute_url }}">cancel</a>
......
......@@ -6,7 +6,7 @@
{% block content %}
<h1>{% trans "Really flag this comment?" %}</h1>
<blockquote>{{ comment|linebreaks }}</blockquote>
<form action="." method="post">
<form action="." method="post">{% csrf_token %}
{% if next %}<input type="hidden" name="next" value="{{ next }}" id="next" />{% endif %}
<p class="submit">
<input type="submit" name="submit" value="{% trans "Flag" %}" /> or <a href="{{ comment.get_absolute_url }}">cancel</a>
......
{% load comments i18n %}
<form action="{% comment_form_target %}" method="post">
<form action="{% comment_form_target %}" method="post">{% csrf_token %}
{% if next %}<input type="hidden" name="next" value="{{ next }}" />{% endif %}
{% for field in form %}
{% if field.is_hidden %}
......
......@@ -5,7 +5,7 @@
{% block content %}
{% load comments %}
<form action="{% comment_form_target %}" method="post">
<form action="{% comment_form_target %}" method="post">{% csrf_token %}
{% if next %}<input type="hidden" name="next" value="{{ next }}" />{% endif %}
{% if form.errors %}
<h1>{% blocktrans count form.errors|length as counter %}Please correct the error below{% plural %}Please correct the errors below{% endblocktrans %}</h1>
......
......@@ -10,6 +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
class CommentPostBadRequest(http.HttpResponseBadRequest):
"""
......@@ -22,6 +23,8 @@ class CommentPostBadRequest(http.HttpResponseBadRequest):
if settings.DEBUG:
self.content = render_to_string("comments/400-debug.html", {"why": why})
@csrf_protect
@require_POST
def post_comment(request, next=None):
"""
Post a comment.
......@@ -116,8 +119,6 @@ def post_comment(request, next=None):
return next_redirect(data, next, comment_done, c=comment._get_pk_val())
post_comment = require_POST(post_comment)
comment_done = confirmation_view(
template = "comments/posted.html",
doc = """Display a "comment was posted" success page."""
......
......@@ -5,7 +5,9 @@ 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
@csrf_protect
@login_required
def flag(request, comment_id, next=None):
"""
......@@ -30,6 +32,7 @@ def flag(request, comment_id, next=None):
template.RequestContext(request)
)
@csrf_protect
@permission_required("comments.can_moderate")
def delete(request, comment_id, next=None):
"""
......@@ -56,6 +59,7 @@ def delete(request, comment_id, next=None):
template.RequestContext(request)
)
@csrf_protect
@permission_required("comments.can_moderate")
def approve(request, comment_id, next=None):
"""
......
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() }
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.
"""
This diff is collapsed.
from django.http import HttpResponseForbidden
from django.template import Context, Template
from django.conf import settings
# We include the template inline since we need to be able to reliably display
# this error message, especially for the sake of developers, and there isn't any
# other way of making it available independent of what is in the settings file.
CSRF_FAILRE_TEMPLATE = """
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>403 Forbidden</title>
</head>
<body>
<h1>403 Forbidden</h1>
<p>CSRF verification failed. Request aborted.</p>
{% if DEBUG %}
<h2>Help</h2>
{% if reason %}
<p>Reason given for failure:</p>
<pre>
{{ reason }}
</pre>
{% endif %}
<p>In general, this can occur when there is a genuine Cross Site Request Forgery, or when
<a
href='http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ref-contrib-csrf'>Django's
CSRF mechanism</a> has not been used correctly. For POST forms, you need to
ensure:</p>
<ul>
<li>The view function uses <a
href='http://docs.djangoproject.com/en/dev/ref/templates/api/#subclassing-context-requestcontext'><tt>RequestContext</tt></a>
for the template, instead of <tt>Context</tt>.</li>
<li>In the template, there is a <tt>{% templatetag openblock %} csrf_token
{% templatetag closeblock %}</tt> template tag inside each POST form that
targets an internal URL.</li>
</ul>
<p>You're seeing the help section of this page because you have <code>DEBUG =
True</code> in your Django settings file. Change that to <code>False</code>,
and only the initial error message will be displayed. </p>
<p>You can customize this page using the CSRF_FAILURE_VIEW setting.</p>
{% endif %}
</body>
</html>
"""
def csrf_failure(request, reason=""):
"""
Default view used when request fails CSRF protection
"""
t = Template(CSRF_FAILRE_TEMPLATE)
c = Context({'DEBUG': settings.DEBUG,
'reason': reason})
return HttpResponseForbidden(t.render(c), mimetype='text/html')
......@@ -4,7 +4,7 @@
{% if form.errors %}<h1>Please correct the following errors</h1>{% else %}<h1>Submit</h1>{% endif %}
<form action="" method="post">
<form action="" method="post">{% csrf_token %}
<table>
{{ form }}
</table>
......
......@@ -15,7 +15,7 @@
<p>Security hash: {{ hash_value }}</p>
<form action="" method="post">
<form action="" method="post">{% csrf_token %}
{% for field in form %}{{ field.as_hidden }}
{% endfor %}
<input type="hidden" name="{{ stage_field }}" value="2" />
......@@ -25,7 +25,7 @@
<h1>Or edit it again</h1>
<form action="" method="post">
<form action="" method="post">{% csrf_token %}
<table>
{{ form }}
</table>
......
......@@ -147,15 +147,18 @@ class WizardPageTwoForm(forms.Form):
class WizardClass(wizard.FormWizard):
def render_template(self, *args, **kw):
return ""
return http.HttpResponse("")
def done(self, request, cleaned_data):
return http.HttpResponse(success_string)
class DummyRequest(object):
class DummyRequest(http.HttpRequest):
def __init__(self, POST=None):
super(DummyRequest, self).__init__()
self.method = POST and "POST" or "GET"
self.POST = POST
if POST is not None:
self.POST.update(POST)
self._dont_enforce_csrf_checks = True
class WizardTests(TestCase):
def test_step_starts_at_zero(self):
......
......@@ -14,6 +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
class FormWizard(object):
# Dictionary of extra template context variables.
......@@ -44,6 +45,7 @@ class FormWizard(object):
# hook methods might alter self.form_list.
return len(self.form_list)
@csrf_protect
def __call__(self, request, *args, **kwargs):
"""
Main method that does all the hard work, conforming to the Django view
......
from django.core.exceptions import ImproperlyConfigured
from django.utils.importlib import import_module
# Cache of actual callables.
_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',)
class ContextPopException(Exception):
"pop() has been called more times than push()"
......@@ -75,7 +80,10 @@ def get_standard_processors():
global _standard_context_processors
if _standard_context_processors is None:
processors = []
for path in settings.TEMPLATE_CONTEXT_PROCESSORS:
collect = []
collect.extend(_builtin_context_processors)
collect.extend(settings.TEMPLATE_CONTEXT_PROCESSORS)
for path in collect:
i = path.rfind('.')
module, attr = path[:i], path[i+1:]
try:
......
......@@ -37,6 +37,23 @@ class CommentNode(Node):
def render(self, context):
return ''
class CsrfTokenNode(Node):
def render(self, context):
csrf_token = context.get('csrf_token', None)
if csrf_token:
if csrf_token == 'NOTPROVIDED':
return mark_safe(u"")
else:
return mark_safe(u"<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='%s' /></div>" % (csrf_token))
else:
# It's very probable that the token is missing because of
# misconfiguration, so we raise a warning
from django.conf import settings
if settings.DEBUG:
import warnings
warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.")
return u''
class CycleNode(Node):
def __init__(self, cyclevars, variable_name=None):
self.cycle_iter = itertools_cycle(cyclevars)
......@@ -523,6 +540,10 @@ def cycle(parser, token):
return node
cycle = register.tag(cycle)
def csrf_token(parser, token):
return CsrfTokenNode()
register.tag(csrf_token)
def debug(parser, token):
"""
Outputs a whole load of debugging information, including the current
......
......@@ -66,6 +66,11 @@ class ClientHandler(BaseHandler):
signals.request_started.send(sender=self.__class__)
try:
request = WSGIRequest(environ)
# sneaky little hack so that we can easily get round
# CsrfViewMiddleware. This makes life easier, and is probably
# required for backwards compatibility with external tests against
# admin views.
request._dont_enforce_csrf_checks = True
response = self.get_response(request)
# Apply response middleware.
......
......@@ -13,6 +13,12 @@ their deprecation, as per the :ref:`Django deprecation policy
hooking up admin URLs. This has been deprecated since the 1.1
release.
* 1.4
* ``CsrfResponseMiddleware``. This has been deprecated since the 1.2
release, in favour of the template tag method for inserting the CSRF
token. ``CsrfMiddleware``, which combines ``CsrfResponseMiddleware``
and ``CsrfViewMiddleware``, is also deprecated.
* 2.0
* ``django.views.defaults.shortcut()``. This function has been moved
to ``django.contrib.contenttypes.views.shortcut()`` as part of the
......
......@@ -21,6 +21,7 @@ tutorial, so that the template contains an HTML ``<form>`` element:
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="/polls/{{ poll.id }}/vote/" method="post">
{% csrf_token %}
{% for choice in poll.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice }}</label><br />
......@@ -46,6 +47,28 @@ A quick rundown:
* ``forloop.counter`` indicates how many times the :ttag:`for` tag has gone
through its loop
* Since we are creating a POST form (which can have the effect of modifying
data), we unfortunately need to worry about Cross Site Request Forgeries.
Thankfully, you don't have to worry too hard, because Django comes with
very easy-to-use system for protecting against it. In short, all POST
forms that are targetted at internal URLs need the ``{% csrf_token %}``
template tag adding.
The ``{% csrf_token %}`` tag requires information from the request object, which
is not normally accessible from within the template context. To fix this, a
small adjustment needs to be made to the ``detail`` view, so that it looks like
the following::
from django.template import RequestContext
# ...
def detail(request, poll_id):
p = get_object_or_404(Poll, pk=poll_id)
return render_to_response('polls/detail.html', {'poll': p},
context_instance=RequestContext(request))
The details of how this works are explained in the documentation for
:ref:`RequestContext <subclassing-context-requestcontext>`.
Now, let's create a Django view that handles the submitted data and does
something with it. Remember, in :ref:`Tutorial 3 <intro-tutorial03>`, we
created a URLconf for the polls application that includes this line::
......@@ -58,6 +81,7 @@ create a real version. Add the following to ``mysite/polls/views.py``::
from django.shortcuts import get_object_or_404, render_to_response
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from django.template import RequestContext
from mysite.polls.models import Choice, Poll
# ...
def vote(request, poll_id):
......@@ -69,7 +93,7 @@ create a real version. Add the following to ``mysite/polls/views.py``::
return render_to_response('polls/detail.html', {
'poll': p,
'error_message': "You didn't select a choice.",
})
}, context_instance=RequestContext(request))
else:
selected_choice.votes += 1
selected_choice.save()
......
This diff is collapsed.
......@@ -177,7 +177,7 @@ Here's a full example template:
{% block content %}
<p>Step {{ step }} of {{ step_count }}</p>
<form action="." method="post">
<form action="." method="post">{% csrf_token %}
<table>
{{ form }}
</table>
......
......@@ -144,6 +144,44 @@ Default: ``600``
The default number of seconds to cache a page when the caching middleware or
``cache_page()`` decorator is used.
.. setting:: CSRF_COOKIE_NAME
CSRF_COOKIE_NAME
----------------
Default: ``'csrftoken'``
The name of the cookie to use for the CSRF authentication token. This can be whatever you
want. See :ref:`ref-contrib-csrf`.
.. setting:: CSRF_COOKIE_DOMAIN
CSRF_COOKIE_DOMAIN
------------------
Default: ``None``
The domain to be used when setting the CSRF cookie. This can be useful for
allowing cross-subdomain requests to be exluded from the normal cross site
request forgery protection. It should be set to a string such as
``".lawrence.com"`` to allow a POST request from a form on one subdomain to be
accepted by accepted by a view served from another subdomain.
.. setting:: CSRF_FAILURE_VIEW
CSRF_FAILURE_VIEW
-----------------
Default: ``'django.contrib.csrf.views.csrf_failure'``
A dotted path to the view function to be used when an incoming request
is rejected by the CSRF protection. The function should have this signature::
def csrf_failure(request, reason="")
where ``reason`` is a short message (intended for developers or logging, not for
end users) indicating the reason the request was rejected. See
:ref:`ref-contrib-csrf`.
.. setting:: DATABASE_ENGINE
DATABASE_ENGINE
......@@ -751,6 +789,7 @@ Default::
('django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.csrf.middleware.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',)
A tuple of middleware classes to use. See :ref:`topics-http-middleware`.
......
......@@ -313,6 +313,13 @@ and return a dictionary of items to be merged into the context. By default,
"django.core.context_processors.i18n",
"django.core.context_processors.media")
.. versionadded:: 1.2
In addition to these, ``RequestContext`` always uses
``'django.contrib.csrf.context_processors.csrf'``. This is a security
related context processor required by the admin and other contrib apps, and,
in case of accidental misconfiguration, it is deliberately hardcoded in and
cannot be turned off by the :setting:`TEMPLATE_CONTEXT_PROCESSORS` setting.
Each processor is applied in order. That means, if one processor adds a
variable to the context and a second processor adds a variable with the same
name, the second will override the first. The default processors are explained
......@@ -404,6 +411,14 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every
``RequestContext`` will contain a variable ``MEDIA_URL``, providing the
value of the :setting:`MEDIA_URL` setting.
django.contrib.csrf.context_processors.csrf
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 1.2
This processor adds a token that is needed by the ``csrf_token`` template tag
for protection against :ref:`Cross Site Request Forgeries <ref-contrib-csrf>`.
django.core.context_processors.request
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
......
......@@ -53,6 +53,13 @@ Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
.. templatetag:: cycle
csrf_token
~~~~~~~~~~
.. versionadded:: 1.2
This is described in the documentation for :ref:`Cross Site Request Forgeries <ref-contrib-csrf>`.
cycle
~~~~~
......
......@@ -2,6 +2,22 @@
Backwards-incompatible changes
==============================
CSRF Protection
---------------
There have been large changes to the way that CSRF protection works, detailed in
:ref:`the CSRF documentaton <ref-contrib-csrf>`. The following are the major
changes that developers must be aware of:
* ``CsrfResponseMiddleware`` and ``CsrfMiddleware`` have been deprecated, and
will be removed completely in Django 1.4, in favour of a template tag that
should be inserted into forms.
* ``CsrfViewMiddleware`` is included in :setting:`MIDDLEWARE_CLASSES` by
default. This turns on CSRF protection by default, so that views that accept
POST requests need to be written to work with the middleware. Instructions
on how to do this are found in the CSRF docs.
LazyObject
----------
......
......@@ -767,7 +767,7 @@ the following line to your URLconf::
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
<form method="post" action="{% url django.contrib.auth.views.login %}">
<form method="post" action="{% url django.contrib.auth.views.login %}">{% csrf_token %}
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
......
......@@ -29,6 +29,7 @@ created by :djadmin:`django-admin.py startproject <startproject>`::
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.csrf.middleware.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
......
This diff is collapsed.
......@@ -885,8 +885,9 @@ class AdminViewListEditable(TestCase):
# 4 action inputs (3 regular checkboxes, 1 checkbox to select all)
# main form submit button = 1
# search field and search submit button = 2
# 6 + 2 + 1 + 2 = 11 inputs
self.failUnlessEqual(response.content.count("<input"), 15)
# CSRF field = 1
# 6 + 2 + 4 + 1 + 2 + 1 = 16 inputs
self.failUnlessEqual(response.content.count("<input"), 16)
# 1 select per object = 3 selects
self.failUnlessEqual(response.content.count("<select"), 4)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment