Index: django/conf/project_template/settings.py
===================================================================
--- django/conf/project_template/settings.py	(revision 10131)
+++ django/conf/project_template/settings.py	(working copy)
@@ -60,6 +60,7 @@
 MIDDLEWARE_CLASSES = (
     'django.middleware.common.CommonMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.contrib.csrf.middleware.CsrfViewMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
 )
 
@@ -74,6 +75,7 @@
 INSTALLED_APPS = (
     'django.contrib.auth',
     'django.contrib.contenttypes',
+    'django.contrib.csrf',
     'django.contrib.sessions',
     'django.contrib.sites',
 )
Index: django/conf/global_settings.py
===================================================================
--- django/conf/global_settings.py	(revision 10131)
+++ django/conf/global_settings.py	(working copy)
@@ -165,6 +165,7 @@
     'django.core.context_processors.debug',
     'django.core.context_processors.i18n',
     'django.core.context_processors.media',
+    'django.contrib.csrf.context_processors.csrf',
 #    'django.core.context_processors.request',
 )
 
@@ -303,6 +304,7 @@
 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',
@@ -377,6 +379,21 @@
 # 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'
+
+# Some 'salt' to stop CSRF middleware being abused into generating and
+# returning md5hash(SECRET_KEY + user supplied string).  This probably
+# is not needed with built-in session backends. It does not matter if
+# this setting is not changed in individual settings files, because
+# the SECRET_KEY is also used for hashing.
+CSRF_SALT = 'g7x2(s0$h!^^_ykw81nm383yl58#@lpkfw_yv_yf'
+
 ###########
 # TESTING #
 ###########
Index: django/contrib/formtools/templates/formtools/preview.html
===================================================================
--- django/contrib/formtools/templates/formtools/preview.html	(revision 10131)
+++ django/contrib/formtools/templates/formtools/preview.html	(working copy)
@@ -1,5 +1,5 @@
 {% extends "base.html" %}
-
+{% load csrf %}
 {% block content %}
 
 <h1>Preview your submission</h1>
@@ -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>
Index: django/contrib/formtools/templates/formtools/form.html
===================================================================
--- django/contrib/formtools/templates/formtools/form.html	(revision 10131)
+++ django/contrib/formtools/templates/formtools/form.html	(working copy)
@@ -1,10 +1,10 @@
 {% extends "base.html" %}
-
+{% load csrf %}
 {% block content %}
 
 {% 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>
Index: django/contrib/comments/templates/comments/approve.html
===================================================================
--- django/contrib/comments/templates/comments/approve.html	(revision 10131)
+++ django/contrib/comments/templates/comments/approve.html	(working copy)
@@ -1,12 +1,12 @@
 {% extends "comments/base.html" %}
-{% load i18n %}
+{% load i18n csrf %}
 
 {% block title %}{% trans "Approve a comment" %}{% endblock %}
 
 {% block content %}
   <h1>{% trans "Really make this comment public?" %}</h1>
   <blockquote>{{ comment|linebreaks }}</blockquote>
-  <form action="." method="post">
+  <form action="." method="post">{% csrf_token %}
     <input type="hidden" name="next" value="{{ next }}" id="next" />
     <p class="submit">
       <input type="submit" name="submit" value="{% trans "Approve" %}" /> or <a href="{{ comment.get_absolute_url }}">cancel</a>
Index: django/contrib/comments/templates/comments/preview.html
===================================================================
--- django/contrib/comments/templates/comments/preview.html	(revision 10131)
+++ django/contrib/comments/templates/comments/preview.html	(working copy)
@@ -1,11 +1,11 @@
 {% extends "comments/base.html" %}
-{% load i18n %}
+{% load i18n csrf %}
 
 {% block title %}{% trans "Preview your comment" %}{% endblock %}
 
 {% block content %}
   {% load comments %}
-  <form action="{% comment_form_target %}" method="post">
+  <form action="{% comment_form_target %}" method="post">{% csrf_token %}
     {% if form.errors %}
     <h1>{% blocktrans count form.errors|length as counter %}Please correct the error below{% plural %}Please correct the errors below{% endblocktrans %}</h1>
     {% else %}
Index: django/contrib/comments/templates/comments/delete.html
===================================================================
--- django/contrib/comments/templates/comments/delete.html	(revision 10131)
+++ django/contrib/comments/templates/comments/delete.html	(working copy)
@@ -1,12 +1,12 @@
 {% extends "comments/base.html" %}
-{% load i18n %}
+{% load i18n csrf %}
 
 {% block title %}{% trans "Remove a comment" %}{% endblock %}
 
 {% block content %}
 <h1>{% trans "Really remove this comment?" %}</h1>
   <blockquote>{{ comment|linebreaks }}</blockquote>
-  <form action="." method="post">
+  <form action="." method="post">{% csrf_token %}
     <input type="hidden" name="next" value="{{ next }}" id="next" />
     <p class="submit">
     <input type="submit" name="submit" value="{% trans "Remove" %}" /> or <a href="{{ comment.get_absolute_url }}">cancel</a>
Index: django/contrib/comments/templates/comments/form.html
===================================================================
--- django/contrib/comments/templates/comments/form.html	(revision 10131)
+++ django/contrib/comments/templates/comments/form.html	(working copy)
@@ -1,5 +1,5 @@
-{% load comments i18n %}
-<form action="{% comment_form_target %}" method="post">
+{% load comments i18n csrf %}
+<form action="{% comment_form_target %}" method="post">{% csrf_token %}
   {% for field in form %}
     {% if field.is_hidden %}
       {{ field }}
Index: django/contrib/comments/templates/comments/moderation_queue.html
===================================================================
--- django/contrib/comments/templates/comments/moderation_queue.html	(revision 10131)
+++ django/contrib/comments/templates/comments/moderation_queue.html	(working copy)
@@ -1,5 +1,5 @@
 {% extends "admin/change_list.html" %}
-{% load adminmedia i18n %}
+{% load adminmedia i18n csrf %}
 
 {% block title %}{% trans "Comment moderation queue" %}{% endblock %}
 
@@ -44,11 +44,11 @@
       {% for comment in comments %}
         <tr class="{% cycle 'row1' 'row2' %}">
           <td class="actions">
-            <form action="{% url comments-approve comment.pk %}" method="post">
+            <form action="{% url comments-approve comment.pk %}" method="post">{% csrf_token %}
               <input type="hidden" name="next" value="{% url comments-moderation-queue %}" />
               <input class="approve submit" type="submit" name="submit" value="{% trans "Approve" %}" />
             </form>
-            <form action="{% url comments-delete comment.pk %}" method="post">
+            <form action="{% url comments-delete comment.pk %}" method="post">{% csrf_token %}
               <input type="hidden" name="next" value="{% url comments-moderation-queue %}" />
               <input class="remove submit" type="submit" name="submit" value="{% trans "Remove" %}" />
             </form>
Index: django/contrib/comments/templates/comments/flag.html
===================================================================
--- django/contrib/comments/templates/comments/flag.html	(revision 10131)
+++ django/contrib/comments/templates/comments/flag.html	(working copy)
@@ -1,12 +1,12 @@
 {% extends "comments/base.html" %}
-{% load i18n %}
+{% load i18n csrf %}
 
 {% block title %}{% trans "Flag this comment" %}{% endblock %}
 
 {% block content %}
 <h1>{% trans "Really flag this comment?" %}</h1>
   <blockquote>{{ comment|linebreaks }}</blockquote>
-  <form action="." method="post">
+  <form action="." method="post">{% csrf_token %}
     <input type="hidden" name="next" value="{{ next }}" id="next" />
     <p class="submit">
     <input type="submit" name="submit" value="{% trans "Flag" %}" /> or <a href="{{ comment.get_absolute_url }}">cancel</a>
Index: django/contrib/admin/templates/admin/template_validator.html
===================================================================
--- django/contrib/admin/templates/admin/template_validator.html	(revision 10131)
+++ django/contrib/admin/templates/admin/template_validator.html	(working copy)
@@ -1,10 +1,10 @@
 {% extends "admin/base_site.html" %}
-
+{% load csrf %}
 {% block content %}
 
 <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>
Index: django/contrib/admin/templates/admin/change_list.html
===================================================================
--- django/contrib/admin/templates/admin/change_list.html	(revision 10131)
+++ django/contrib/admin/templates/admin/change_list.html	(working copy)
@@ -1,5 +1,5 @@
 {% extends "admin/base_site.html" %}
-{% load adminmedia admin_list i18n %}
+{% load adminmedia admin_list i18n csrf %}
 
 {% block extrastyle %}
   {{ block.super }}
@@ -63,7 +63,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 %}
Index: django/contrib/admin/templates/admin/delete_selected_confirmation.html
===================================================================
--- django/contrib/admin/templates/admin/delete_selected_confirmation.html	(revision 10131)
+++ django/contrib/admin/templates/admin/delete_selected_confirmation.html	(working copy)
@@ -1,5 +1,5 @@
 {% extends "admin/base_site.html" %}
-{% load i18n %}
+{% load i18n csrf %}
 
 {% block breadcrumbs %}
 <div class="breadcrumbs">
@@ -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 }}" />
@@ -34,4 +34,4 @@
     </div>
     </form>
 {% endif %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
Index: django/contrib/admin/templates/admin/auth/user/change_password.html
===================================================================
--- django/contrib/admin/templates/admin/auth/user/change_password.html	(revision 10131)
+++ django/contrib/admin/templates/admin/auth/user/change_password.html	(working copy)
@@ -1,5 +1,5 @@
 {% extends "admin/base_site.html" %}
-{% load i18n admin_modify adminmedia %}
+{% load i18n admin_modify adminmedia csrf %}
 {% block extrahead %}{{ block.super }}
 <script type="text/javascript" src="../../../../jsi18n/"></script>
 {% endblock %}
@@ -14,7 +14,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 %}
Index: django/contrib/admin/templates/admin/login.html
===================================================================
--- django/contrib/admin/templates/admin/login.html	(revision 10131)
+++ django/contrib/admin/templates/admin/login.html	(working copy)
@@ -1,5 +1,5 @@
 {% extends "admin/base_site.html" %}
-{% load i18n %}
+{% load i18n csrf %}
 
 {% block extrastyle %}{% load adminmedia %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/login.css" />{% endblock %}
 
@@ -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>
Index: django/contrib/admin/templates/admin/change_form.html
===================================================================
--- django/contrib/admin/templates/admin/change_form.html	(revision 10131)
+++ django/contrib/admin/templates/admin/change_form.html	(working copy)
@@ -1,5 +1,5 @@
 {% extends "admin/base_site.html" %}
-{% load i18n admin_modify adminmedia %}
+{% load i18n admin_modify adminmedia csrf %}
 
 {% block extrahead %}{{ block.super }}
 <script type="text/javascript" src="../../../jsi18n/"></script>
@@ -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 %}
Index: django/contrib/admin/templates/admin/delete_confirmation.html
===================================================================
--- django/contrib/admin/templates/admin/delete_confirmation.html	(revision 10131)
+++ django/contrib/admin/templates/admin/delete_confirmation.html	(working copy)
@@ -1,5 +1,5 @@
 {% extends "admin/base_site.html" %}
-{% load i18n %}
+{% load i18n csrf %}
 
 {% block breadcrumbs %}
 <div class="breadcrumbs">
@@ -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" %}" />
Index: django/contrib/admin/templates/registration/password_reset_confirm.html
===================================================================
--- django/contrib/admin/templates/registration/password_reset_confirm.html	(revision 10131)
+++ django/contrib/admin/templates/registration/password_reset_confirm.html	(working copy)
@@ -1,5 +1,5 @@
 {% extends "admin/base_site.html" %}
-{% load i18n %}
+{% load i18n csrf %}
 
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset confirmation' %}</div>{% endblock %}
 
@@ -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 }}
Index: django/contrib/admin/templates/registration/password_reset_form.html
===================================================================
--- django/contrib/admin/templates/registration/password_reset_form.html	(revision 10131)
+++ django/contrib/admin/templates/registration/password_reset_form.html	(working copy)
@@ -1,5 +1,5 @@
 {% extends "admin/base_site.html" %}
-{% load i18n %}
+{% load i18n csrf %}
 
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password reset' %}</div>{% endblock %}
 
@@ -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>
Index: django/contrib/admin/templates/registration/password_change_form.html
===================================================================
--- django/contrib/admin/templates/registration/password_change_form.html	(revision 10131)
+++ django/contrib/admin/templates/registration/password_change_form.html	(working copy)
@@ -1,5 +1,5 @@
 {% extends "admin/base_site.html" %}
-{% load i18n %}
+{% load i18n csrf %}
 {% block userlinks %}{% url django-admindocs-docroot as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %} {% trans 'Change password' %} / <a href="../logout/">{% trans 'Log out' %}</a>{% endblock %}
 {% block breadcrumbs %}<div class="breadcrumbs"><a href="../">{% trans 'Home' %}</a> &rsaquo; {% trans 'Password change' %}</div>{% endblock %}
 
@@ -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>
Index: django/contrib/csrf/middleware.py
===================================================================
--- django/contrib/csrf/middleware.py	(revision 10131)
+++ django/contrib/csrf/middleware.py	(working copy)
@@ -13,19 +13,50 @@
     from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
 
 from django.conf import settings
-from django.http import HttpResponseForbidden
+from django.core.urlresolvers import get_callable
 from django.utils.hashcompat import md5_constructor
 from django.utils.safestring import mark_safe
 
-_ERROR_MSG = mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>')
-
 _POST_FORM_RE = \
     re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
 
 _HTML_TYPES = ('text/html', 'application/xhtml+xml')
 
+def _get_failure_view():
+    """
+    Returns the view to be used for CSRF rejections
+    """
+    return get_callable(settings.CSRF_FAILURE_VIEW)
+
+def _get_validated_session_id(request):
+    if hasattr(request, 'session'):
+        # Avoid use of session.session_key, which can trigger creation
+        # of session.
+        if request.session.accessed:
+            # Could be a new session, need to use new session ID
+            return request.session.session_key
+        else:
+            session_id = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
+            if session_id is not None and request.session.exists(session_id):
+                return session_id
+            else:
+                return None # not a valid session
+    return None
+
+def get_token(request):
+    """
+    Returns the the CSRF token required for a session,
+    or None if there is no session active.
+    """
+    session_id = _get_validated_session_id(request)
+    if session_id is None:
+        # No session
+        return None
+    else:
+        return _make_token(session_id)
+
 def _make_token(session_id):
-    return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
+    return md5_constructor(settings.SECRET_KEY + settings.CSRF_SALT + session_id).hexdigest()
 
 class CsrfViewMiddleware(object):
     """
@@ -40,21 +71,19 @@
             if request.is_ajax():
                 return None
 
-            try:
-                session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
-            except KeyError:
+            csrf_token = get_token(request)
+            if csrf_token is None:
                 # No session, no check required
                 return None
 
-            csrf_token = _make_token(session_id)
             # check incoming token
             try:
                 request_csrf_token = request.POST['csrfmiddlewaretoken']
             except KeyError:
-                return HttpResponseForbidden(_ERROR_MSG)
+                return _get_failure_view()(request)
 
             if request_csrf_token != csrf_token:
-                return HttpResponseForbidden(_ERROR_MSG)
+                return _get_failure_view()(request)
 
         return None
 
@@ -80,10 +109,10 @@
             csrf_token = _make_token(cookie.value)
         except KeyError:
             # Normal case - look for existing session cookie
-            try:
-                session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
+            session_id = _get_validated_session_id(request)
+            if session_id is not None:
                 csrf_token = _make_token(session_id)
-            except KeyError:
+            else:
                 # no incoming or outgoing cookie
                 pass
 
Index: django/contrib/csrf/views.py
===================================================================
--- django/contrib/csrf/views.py	(revision 0)
+++ django/contrib/csrf/views.py	(revision 0)
@@ -0,0 +1,10 @@
+from django.http import HttpResponseForbidden
+from django.utils.safestring import mark_safe
+
+_ERROR_MSG = mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>')
+
+def csrf_failure(request):
+    """
+    Default view used when request fails CSRF protection
+    """
+    return HttpResponseForbidden(_ERROR_MSG)

Property changes on: django/contrib/csrf/views.py
___________________________________________________________________
Added: svn:eol-style
   + native

Index: django/contrib/csrf/tests.py
===================================================================
--- django/contrib/csrf/tests.py	(revision 10131)
+++ django/contrib/csrf/tests.py	(working copy)
@@ -3,9 +3,12 @@
 from django.test import TestCase
 from django.http import HttpRequest, HttpResponse, HttpResponseForbidden
 from django.contrib.csrf.middleware import CsrfMiddleware, _make_token, 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
 
-
 def post_form_response():
     resp = HttpResponse(content="""
 <html><body><form method="POST"><input type="text" /></form></body></html>
@@ -15,27 +18,57 @@
 def test_view(request):
     return post_form_response()
 
+def _render_csrf_token_template(req):
+    context = RequestContext(req, processors=[csrf])
+    template = Template("{% load csrf %}{% csrf_token %}")
+    return template.render(context)
+
+def test_view_with_session_access(request):
+    request.session["test value"] = "value"
+    return HttpResponse(_render_csrf_token_template(request))
+
+def test_view_with_late_session_access(request):
+    # This view creates RequestContext and *then* accesses the
+    # session.  This will be problematic unless the csrf context
+    # processor is lazy.
+    context = RequestContext(request, processors=[csrf])
+    request.session["test value"] = "value"
+    template = Template("{% load csrf %}{% csrf_token %}")
+    return HttpResponse(template.render(context))
+
 class CsrfMiddlewareTest(TestCase):
 
-    _session_id = "1"
+    def setUp(self, *args, **kwargs):
+        super(CsrfMiddlewareTest, self).setUp(*args, **kwargs)
+        # We have to create an actual session in the DB as the
+        # middleware validates the session
+        engine = import_module(settings.SESSION_ENGINE)
+        s = engine.SessionStore(None)
+        s.create()
+        self._session_id = s.session_key
 
+    def _apply_request_middleware(self, req):
+        SessionMiddleware().process_request(req)
+        return req
+
     def _get_GET_no_session_request(self):
-        return HttpRequest()
+        return self._apply_request_middleware(HttpRequest())
 
     def _get_GET_session_request(self):
-        req = self._get_GET_no_session_request()
+        req = HttpRequest()
         req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
-        return req
+        return self._apply_request_middleware(req)
 
     def _get_POST_session_request(self):
-        req = self._get_GET_session_request()
+        req = HttpRequest()
+        req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
         req.method = "POST"
-        return req
+        return self._apply_request_middleware(req)
 
     def _get_POST_no_session_request(self):
-        req = self._get_GET_no_session_request()
+        req = HttpRequest()
         req.method = "POST"
-        return req
+        return self._apply_request_middleware(req)
 
     def _get_POST_session_request_with_token(self):
         req = self._get_POST_session_request()
@@ -142,3 +175,42 @@
         req.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
         req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
         self.assertEquals(None, req2)
+
+
+    def test_token_node_no_session(self):
+        """
+        Check that CsrfTokenNode works when no session active
+        """
+        req = self._get_GET_no_session_request()
+        rendered = _render_csrf_token_template(req)
+        self.assertEquals(u"", rendered)
+
+    def test_token_node_with_session(self):
+        """
+        Check that CsrfTokenNode works when a session is active
+        """
+        req = self._get_GET_session_request()
+        rendered = _render_csrf_token_template(req)
+        self.assertNotEqual(u"", rendered)
+        expected = ("name='csrfmiddlewaretoken' value='%s'" % _make_token(self._session_id))
+        self.failUnless(expected in rendered)
+
+    def test_token_node_with_new_session(self):
+        """
+        Check that CsrfTokenNode works when a session is created by
+        the view
+        """
+        req = self._get_GET_no_session_request()
+        resp = test_view_with_session_access(req)
+        expected = ("name='csrfmiddlewaretoken' value='%s'" % _make_token(req.session.session_key))
+        self.assertContains(resp, expected)
+
+    def test_token_node_with_late_session_access(self):
+        """
+        Check that CsrfTokenNode works when a session is created by
+        the view *after* the RequestContext has been created
+        """
+        req = self._get_GET_no_session_request()
+        resp = test_view_with_late_session_access(req)
+        expected = ("name='csrfmiddlewaretoken' value='%s'" % _make_token(req.session.session_key))
+        self.assertContains(resp, expected)
Index: django/contrib/csrf/templatetags/csrf.py
===================================================================
--- django/contrib/csrf/templatetags/csrf.py	(revision 0)
+++ django/contrib/csrf/templatetags/csrf.py	(revision 0)
@@ -0,0 +1,25 @@
+from django import template
+from django.utils.safestring import mark_safe
+
+register = template.Library()
+
+class CsrfTokenNode(template.Node):
+    def render(self, context):
+        csrf_token = context.get('csrf_token', None)
+        if csrf_token:
+            if csrf_token == 'NOTNEEDED':
+                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 having 'django.contrib.csrf.context_processors.csrf' in TEMPLATE_CONTEXT_PROCESSORS.")
+            return u''
+
+def csrf_token(parser, token):
+    return CsrfTokenNode()
+register.tag(csrf_token)

Property changes on: django/contrib/csrf/templatetags/csrf.py
___________________________________________________________________
Added: svn:eol-style
   + native

Index: django/contrib/csrf/templatetags/__init__.py
===================================================================

Property changes on: django/contrib/csrf/templatetags/__init__.py
___________________________________________________________________
Added: svn:eol-style
   + native

Index: django/contrib/csrf/context_processors.py
===================================================================
--- django/contrib/csrf/context_processors.py	(revision 0)
+++ django/contrib/csrf/context_processors.py	(revision 0)
@@ -0,0 +1,20 @@
+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
+    'NOTNEEDED' if it is not needed
+    """
+    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 an
+        # empty dict.
+        return {'csrf_token':'NOTNEEDED'}
+    else:
+        return {'csrf_token': token}
+
+# We need to generate the value as late as possible, in case the user
+# creates RequestContext early in the view.
+csrf = lazy(csrf, dict)

Property changes on: django/contrib/csrf/context_processors.py
___________________________________________________________________
Added: svn:eol-style
   + native

Index: tests/regressiontests/admin_views/tests.py
===================================================================
--- tests/regressiontests/admin_views/tests.py	(revision 10131)
+++ tests/regressiontests/admin_views/tests.py	(working copy)
@@ -747,8 +747,9 @@
         # 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 hidden 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)
 
Index: tests/runtests.py
===================================================================
--- tests/runtests.py	(revision 10131)
+++ tests/runtests.py	(working copy)
@@ -30,6 +30,7 @@
     'django.contrib.sessions',
     'django.contrib.comments',
     'django.contrib.admin',
+    'django.contrib.csrf',
 ]
 
 def get_test_models():
Index: docs/topics/http/middleware.txt
===================================================================
--- docs/topics/http/middleware.txt	(revision 10131)
+++ docs/topics/http/middleware.txt	(working copy)
@@ -29,6 +29,7 @@
     MIDDLEWARE_CLASSES = (
         'django.middleware.common.CommonMiddleware',
         'django.contrib.sessions.middleware.SessionMiddleware',
+        'django.contrib.csrf.middleware.CsrfViewMiddleware',
         'django.contrib.auth.middleware.AuthenticationMiddleware',
     )
 
Index: docs/ref/contrib/csrf.txt
===================================================================
--- docs/ref/contrib/csrf.txt	(revision 10131)
+++ docs/ref/contrib/csrf.txt	(working copy)
@@ -7,46 +7,119 @@
 .. module:: django.contrib.csrf
    :synopsis: Protects against Cross Site Request Forgeries
 
-The CsrfMiddleware class provides easy-to-use protection against
-`Cross Site Request Forgeries`_.  This type of attack occurs when a malicious
-Web site creates a link or form button that is intended to perform some action
-on your Web site, using the credentials of a logged-in user who is tricked
-into clicking on the link in their browser.
+The CSRF middleware and template tag provides easy-to-use protection
+against `Cross Site Request Forgeries`_.  This type of attack occurs
+when a malicious Web site creates a link or form button that is
+intended to perform some action on your Web site, using the
+credentials of a logged-in user who is tricked into clicking on the
+link in their browser.
 
 The first defense against CSRF attacks is to ensure that GET requests
-are side-effect free.  POST requests can then be protected by adding this
-middleware into your list of installed middleware.
+are side-effect free.  POST requests can then be protected by adding
+these middleware into your list of installed middleware following the
+steps below.
 
+.. versionadded:: 1.1
+    The functionality described here is depended on by the 'contrib'
+    apps, including the admin, so usage steps 1, 2 and 4.1.1 below are
+    **required** for these apps to function.
+
 .. _Cross Site Request Forgeries: http://www.squarefree.com/securitytips/web-developers.html#CSRF
 
 How to use it
 =============
+.. versionchanged:: 1.1
+    The template tag functionality (the recommended way to use this)
+    was added in version 1.1. The previous method (still available) is
+    described under 'Legacy method'.
 
-Add the middleware ``'django.contrib.csrf.middleware.CsrfMiddleware'`` to
-your list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It needs to process
-the response after the SessionMiddleware, so must come before it in the
-list. It also must process the response before things like compression
-happen to the response, so it must come after GZipMiddleware in the
-list.
+Follow these steps:
 
-The ``CsrfMiddleware`` class is actually composed of two middleware:
-``CsrfViewMiddleware`` which performs the checks on incoming requests,
-and ``CsrfResponseMiddleware`` which performs post-processing of the
-result.  This allows the individual components to be used and/or
-replaced instead of using ``CsrfMiddleware``.
+    1. Add the middleware
+       ``'django.contrib.csrf.middleware.CsrfViewMiddleware'`` to your
+       list of middleware classes, :setting:`MIDDLEWARE_CLASSES`. It
+       must come after ``SessionMiddleware`` in the list.
 
-.. versionchanged:: 1.1
-    (previous versions of Django did not provide these two components
-    of ``CsrfMiddleware`` as described above)
+    2. Add ``'django.contrib.csrf'`` to your :setting:`INSTALLED_APPS`.
 
+    3. In any template that uses a POST form, first load the 'csrf'
+       template tag library::
+
+           {% load csrf %}
+
+       Then use the ``csrf_token`` tag inside the ``<form>`` element, e.g.::
+
+           <form action="" method="POST">{% csrf_token %}
+
+    4. In the corresponding view functions, ensure that the
+       ``'django.contrib.csrf.context_processors.csrf'`` is being
+       used. Usually, this can be done in one of two ways:
+
+       1. Using RequestContext:
+
+          1. Ensure ``'django.contrib.csrf.context_processors.csrf'``
+             is present in your :setting:`TEMPLATE_CONTEXT_PROCESSORS`
+             setting. It is present by default.
+
+          2. Use ``RequestContext`` as the context instance in the
+             relevant views. If you are using generic views or contrib
+             apps, you are covered already.
+
+       2. Manually import and use the processor to generate the CSRF
+          token and add it to the template context. e.g.::
+
+              from django.contrib.csrf.context_processors import csrf
+              from django.template import Context
+              from django.shortcuts import render_to_response
+              def my_view(request):
+                  c = Context()
+                  c.update(csrf(request))
+                  return render_to_response("a_template.html", context_instance=c)
+
+    5. Ensure that all views render their templates **after**
+       accessing the session.  Note that sessions can be created
+       implicitly simply by setting a value or accessing the contents.
+       If this is done after the template is rendered, the CSRF token
+       it contains will be incorrect, and the following form
+       submission will be rejected.
+
+Legacy method
+-------------
+
+In Django 1.0, the template tag did not exist.  Instead, a
+post-processing middleware that re-wrote POST forms to include the
+CRSF token was used.  This 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.contrib.csrf.middleware.CsrfResponseMiddleware'``
+       to your :setting:`MIDDLEWARE_CLASSES` setting.
+
+       ``CsrfResponseMiddleware`` needs to process the response after
+       the ``SessionMiddleware``, so must come before it in the list.
+       It also must process the response before things like
+       compression happen to the response, so it must come after
+       ``GZipMiddleware`` in the list.
+
+Use of the ``CsrfResponseMiddleware`` is not recommended, but it can
+be used as an interim measure until applications have been updated to use
+the ``{% crsf_token %}`` tag.
+
+Django 1.0 provided a single ``CsrfMiddleware`` class.  This is also
+still available for backwards compatibility.  It combines the
+functions of the two new middleware.
+
+
 Exceptions
 ----------
 
 .. versionadded:: 1.1
 
-To manually exclude a view function from being handled by the
-CsrfMiddleware, you can use the ``csrf_exempt`` decorator, found in
-the ``django.contrib.csrf.middleware`` module. For example::
+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.contrib.csrf.middleware`` module. For example::
 
     from django.contrib.csrf.middleware import csrf_exempt
 
@@ -54,34 +127,52 @@
         return HttpResponse('Hello world')
     my_view = csrf_exempt(my_view)
 
-Like the middleware itself, the ``csrf_exempt`` decorator is composed
-of two parts: a ``csrf_view_exempt`` decorator and a
-``csrf_response_exempt`` decorator, found in the same module.  These
-disable the view protection mechanism (``CsrfViewMiddleware``) and the
-response post-processing (``CsrfResponseMiddleware``) respectively.
-They can be used individually if required.
+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.
 
 You don't have to worry about doing this for most AJAX views. Any
 request sent with "X-Requested-With: XMLHttpRequest" is automatically
 exempt. (See the next section.)
 
+Rejected requests
+=================
+
+By default, a '403 Forbidden' response is sent to the user if an
+incoming request fails the checks performed by ``CsrfViewMiddleware``.
+This should usually only be seen when there is a genuine Cross Site
+Request Forgery, or when, due to a programming error, the CSRF token
+has not been included with a POST form.
+
+No logging is done, and the error message is not very friendly, so you
+may want to provide your own page for handling this condition.  To do
+this, simply set the :setting:`CSRF_FAILURE_VIEW` setting to a dotted
+path to your own view function.
+
 How it works
 ============
 
-CsrfMiddleware does two things:
+The CSRF protection requires two things:
 
-1. It modifies outgoing requests by adding a hidden form field to all
-   'POST' forms, with the name 'csrfmiddlewaretoken' and a value which is
-   a hash of the session ID plus a secret. If there is no session ID set,
-   this modification of the response isn't done, so there is very little
-   performance penalty for those requests that don't have a session.
-   (This is done by ``CsrfResponseMiddleware``).
+1. A hidden form field with the name 'csrfmiddlewaretoken' must be
+   added to all outgoing POST forms.  The value of this field is a
+   hash of the session ID plus a secret. If there is no session ID
+   set, this modification of the response isn't done, so there is very
+   little performance penalty for those requests that don't have a
+   session.
 
-2. On all incoming POST requests that have the session cookie set, it
-   checks that the 'csrfmiddlewaretoken' is present and correct. If it
-   isn't, the user will get a 403 error. (This is done by
-   ``CsrfViewMiddleware``)
+   This part is done by the template tag (and with the
+   legacy method, it is done by ``CsrfResponseMiddleware``).
 
+2. On all incoming POST requests that have the session cookie set, the
+   'csrfmiddlewaretoken' must be present and correct. If it isn't, the
+   user will get a 403 error.
+
+   This check is done by ``CsrfViewMiddleware``.
+
 This ensures that only forms that have originated from your Web site
 can be used to POST data back.
 
@@ -112,13 +203,14 @@
 Limitations
 ===========
 
-CsrfMiddleware requires Django's session framework to work. If you have
-a custom authentication system that manually sets cookies and the like,
-it won't help you.
+These middleware require Django's session framework to work. If you
+have a custom authentication system that manually sets cookies and the
+like, it won't help you.
 
-If 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.  It may still be possible
-to use the middleware, provided you can find some way to get the
-CSRF token and ensure that is included when your form is submitted.
+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.  It may still be possible to use the middleware,
+provided you can find some way to get the CSRF token and ensure that
+is included when your form is submitted.
Index: docs/ref/settings.txt
===================================================================
--- docs/ref/settings.txt	(revision 10131)
+++ docs/ref/settings.txt	(working copy)
@@ -146,6 +146,21 @@
 
 .. setting:: DATABASE_ENGINE
 
+CSRF_FAILURE_VIEW
+-----------------
+
+Default: ``'django.contrib.csrf.views.csrf_failure'``
+
+The name of the view function to be used when an incoming request
+is rejected by the CSRF protection. See :ref:`ref-contrib-csrf`.
+
+CSRF_SALT
+---------
+
+Some additional 'salt' used by the CSRF middleware when hashing, to
+avoid a potential security problem.  Does not need to be changed.  See
+source for more details.
+
 DATABASE_ENGINE
 ---------------
 
@@ -762,6 +777,7 @@
 
     ('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`.
