Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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

…ncy 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
  • Loading branch information...
commit 8e70cef9b67433edd70935dcc30c621d1e7fc0a0 1 parent d1da261
Luke Plant authored October 26, 2009

Showing 47 changed files with 1,427 additions and 234 deletions. Show diff stats Hide diff stats

  1. 2  AUTHORS
  2. 13  django/conf/global_settings.py
  3. 1  django/conf/project_template/settings.py
  4. 9  django/contrib/admin/options.py
  5. 5  django/contrib/admin/sites.py
  6. 2  django/contrib/admin/templates/admin/auth/user/change_password.html
  7. 2  django/contrib/admin/templates/admin/change_form.html
  8. 2  django/contrib/admin/templates/admin/change_list.html
  9. 2  django/contrib/admin/templates/admin/delete_confirmation.html
  10. 2  django/contrib/admin/templates/admin/delete_selected_confirmation.html
  11. 2  django/contrib/admin/templates/admin/login.html
  12. 2  django/contrib/admin/templates/admin/template_validator.html
  13. 2  django/contrib/admin/templates/registration/password_change_form.html
  14. 2  django/contrib/admin/templates/registration/password_reset_confirm.html
  15. 2  django/contrib/admin/templates/registration/password_reset_form.html
  16. 9  django/contrib/auth/views.py
  17. 2  django/contrib/comments/templates/comments/approve.html
  18. 2  django/contrib/comments/templates/comments/delete.html
  19. 2  django/contrib/comments/templates/comments/flag.html
  20. 2  django/contrib/comments/templates/comments/form.html
  21. 2  django/contrib/comments/templates/comments/preview.html
  22. 5  django/contrib/comments/views/comments.py
  23. 4  django/contrib/comments/views/moderation.py
  24. 20  django/contrib/csrf/context_processors.py
  25. 10  django/contrib/csrf/decorators.py
  26. 256  django/contrib/csrf/middleware.py
  27. 311  django/contrib/csrf/tests.py
  28. 62  django/contrib/csrf/views.py
  29. 2  django/contrib/formtools/templates/formtools/form.html
  30. 4  django/contrib/formtools/templates/formtools/preview.html
  31. 9  django/contrib/formtools/tests.py
  32. 2  django/contrib/formtools/wizard.py
  33. 10  django/template/context.py
  34. 21  django/template/defaulttags.py
  35. 5  django/test/client.py
  36. 6  docs/internals/deprecation.txt
  37. 26  docs/intro/tutorial04.txt
  38. 383  docs/ref/contrib/csrf.txt
  39. 2  docs/ref/contrib/formtools/form-wizard.txt
  40. 39  docs/ref/settings.txt
  41. 15  docs/ref/templates/api.txt
  42. 7  docs/ref/templates/builtins.txt
  43. 16  docs/releases/1.2-alpha.txt
  44. 2  docs/topics/auth.txt
  45. 1  docs/topics/http/middleware.txt
  46. 369  extras/csrf_migration_helper.py
  47. 5  tests/regressiontests/admin_views/tests.py
2  AUTHORS
@@ -470,6 +470,8 @@ answer newbie questions, and generally made Django that much better:
470 470
     Gasper Zejn <zejn@kiberpipa.org>
471 471
     Jarek Zgoda <jarek.zgoda@gmail.com>
472 472
     Cheng Zhang
  473
+    Glenn
  474
+    bthomas
473 475
 
474 476
 A big THANK YOU goes to:
475 477
 
13  django/conf/global_settings.py
@@ -300,6 +300,7 @@
300 300
 MIDDLEWARE_CLASSES = (
301 301
     'django.middleware.common.CommonMiddleware',
302 302
     'django.contrib.sessions.middleware.SessionMiddleware',
  303
+    'django.contrib.csrf.middleware.CsrfViewMiddleware',
303 304
     'django.contrib.auth.middleware.AuthenticationMiddleware',
304 305
 #     'django.middleware.http.ConditionalGetMiddleware',
305 306
 #     'django.middleware.gzip.GZipMiddleware',
@@ -374,6 +375,18 @@
374 375
 # The number of days a password reset link is valid for
375 376
 PASSWORD_RESET_TIMEOUT_DAYS = 3
376 377
 
  378
+########
  379
+# CSRF #
  380
+########
  381
+
  382
+# Dotted path to callable to be used as view when a request is
  383
+# rejected by the CSRF middleware.
  384
+CSRF_FAILURE_VIEW = 'django.contrib.csrf.views.csrf_failure'
  385
+
  386
+# Name and domain for CSRF cookie.
  387
+CSRF_COOKIE_NAME = 'csrftoken'
  388
+CSRF_COOKIE_DOMAIN = None
  389
+
377 390
 ###########
378 391
 # TESTING #
379 392
 ###########
1  django/conf/project_template/settings.py
@@ -60,6 +60,7 @@
60 60
 MIDDLEWARE_CLASSES = (
61 61
     'django.middleware.common.CommonMiddleware',
62 62
     'django.contrib.sessions.middleware.SessionMiddleware',
  63
+    'django.contrib.csrf.middleware.CsrfViewMiddleware',
63 64
     'django.contrib.auth.middleware.AuthenticationMiddleware',
64 65
 )
65 66
 
9  django/contrib/admin/options.py
@@ -6,6 +6,7 @@
6 6
 from django.contrib.admin import widgets
7 7
 from django.contrib.admin import helpers
8 8
 from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict
  9
+from django.contrib.csrf.decorators import csrf_protect
9 10
 from django.core.exceptions import PermissionDenied
10 11
 from django.db import models, transaction
11 12
 from django.db.models.fields import BLANK_CHOICE_DASH
@@ -701,6 +702,8 @@ def response_action(self, request, queryset):
701 702
             else:
702 703
                 return HttpResponseRedirect(".")
703 704
 
  705
+    @csrf_protect
  706
+    @transaction.commit_on_success
704 707
     def add_view(self, request, form_url='', extra_context=None):
705 708
         "The 'add' admin view for this model."
706 709
         model = self.model
@@ -782,8 +785,9 @@ def add_view(self, request, form_url='', extra_context=None):
782 785
         }
783 786
         context.update(extra_context or {})
784 787
         return self.render_change_form(request, context, form_url=form_url, add=True)
785  
-    add_view = transaction.commit_on_success(add_view)
786 788
 
  789
+    @csrf_protect
  790
+    @transaction.commit_on_success
787 791
     def change_view(self, request, object_id, extra_context=None):
788 792
         "The 'change' admin view for this model."
789 793
         model = self.model
@@ -871,8 +875,8 @@ def change_view(self, request, object_id, extra_context=None):
871 875
         }
872 876
         context.update(extra_context or {})
873 877
         return self.render_change_form(request, context, change=True, obj=obj)
874  
-    change_view = transaction.commit_on_success(change_view)
875 878
 
  879
+    @csrf_protect
876 880
     def changelist_view(self, request, extra_context=None):
877 881
         "The 'change list' admin view for this model."
878 882
         from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
@@ -985,6 +989,7 @@ def changelist_view(self, request, extra_context=None):
985 989
             'admin/change_list.html'
986 990
         ], context, context_instance=context_instance)
987 991
 
  992
+    @csrf_protect
988 993
     def delete_view(self, request, object_id, extra_context=None):
989 994
         "The 'delete' admin view for this model."
990 995
         opts = self.model._meta
5  django/contrib/admin/sites.py
@@ -3,6 +3,8 @@
3 3
 from django.contrib.admin import ModelAdmin
4 4
 from django.contrib.admin import actions
5 5
 from django.contrib.auth import authenticate, login
  6
+from django.contrib.csrf.middleware import csrf_response_exempt
  7
+from django.contrib.csrf.decorators import csrf_protect
6 8
 from django.db.models.base import ModelBase
7 9
 from django.core.exceptions import ImproperlyConfigured
8 10
 from django.core.urlresolvers import reverse
@@ -186,6 +188,9 @@ def inner(request, *args, **kwargs):
186 188
             return view(request, *args, **kwargs)
187 189
         if not cacheable:
188 190
             inner = never_cache(inner)
  191
+        # We add csrf_protect here so this function can be used as a utility
  192
+        # function for any view, without having to repeat 'csrf_protect'.
  193
+        inner = csrf_response_exempt(csrf_protect(inner))
189 194
         return update_wrapper(inner, view)
190 195
 
191 196
     def get_urls(self):
2  django/contrib/admin/templates/admin/auth/user/change_password.html
@@ -15,7 +15,7 @@
15 15
 </div>
16 16
 {% endif %}{% endblock %}
17 17
 {% block content %}<div id="content-main">
18  
-<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
  18
+<form action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
19 19
 <div>
20 20
 {% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
21 21
 {% if form.errors %}
2  django/contrib/admin/templates/admin/change_form.html
@@ -29,7 +29,7 @@
29 29
   </ul>
30 30
 {% endif %}{% endif %}
31 31
 {% endblock %}
32  
-<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% block form_top %}{% endblock %}
  32
+<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 %}
33 33
 <div>
34 34
 {% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
35 35
 {% if save_on_top %}{% submit_row %}{% endif %}
2  django/contrib/admin/templates/admin/change_list.html
@@ -68,7 +68,7 @@
68 68
         {% endif %}
69 69
       {% endblock %}
70 70
       
71  
-      <form action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
  71
+      <form action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>{% csrf_token %}
72 72
       {% if cl.formset %}
73 73
         {{ cl.formset.management_form }}
74 74
       {% endif %}
2  django/contrib/admin/templates/admin/delete_confirmation.html
@@ -22,7 +22,7 @@
22 22
 {% else %}
23 23
     <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>
24 24
     <ul>{{ deleted_objects|unordered_list }}</ul>
25  
-    <form action="" method="post">
  25
+    <form action="" method="post">{% csrf_token %}
26 26
     <div>
27 27
     <input type="hidden" name="post" value="yes" />
28 28
     <input type="submit" value="{% trans "Yes, I'm sure" %}" />
2  django/contrib/admin/templates/admin/delete_selected_confirmation.html
@@ -23,7 +23,7 @@
23 23
     {% for deleteable_object in deletable_objects %}
24 24
         <ul>{{ deleteable_object|unordered_list }}</ul>
25 25
     {% endfor %}
26  
-    <form action="" method="post">
  26
+    <form action="" method="post">{% csrf_token %}
27 27
     <div>
28 28
     {% for obj in queryset %}
29 29
     <input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}" />
2  django/contrib/admin/templates/admin/login.html
@@ -14,7 +14,7 @@
14 14
 <p class="errornote">{{ error_message }}</p>
15 15
 {% endif %}
16 16
 <div id="content-main">
17  
-<form action="{{ app_path }}" method="post" id="login-form">
  17
+<form action="{{ app_path }}" method="post" id="login-form">{% csrf_token %}
18 18
   <div class="form-row">
19 19
     <label for="id_username">{% trans 'Username:' %}</label> <input type="text" name="username" id="id_username" />
20 20
   </div>
2  django/contrib/admin/templates/admin/template_validator.html
@@ -4,7 +4,7 @@
4 4
 
5 5
 <div id="content-main">
6 6
 
7  
-<form action="" method="post">
  7
+<form action="" method="post">{% csrf_token %}
8 8
 
9 9
 {% if form.errors %}
10 10
 <p class="errornote">Your template had {{ form.errors|length }} error{{ form.errors|pluralize }}:</p>
2  django/contrib/admin/templates/registration/password_change_form.html
@@ -11,7 +11,7 @@
11 11
 
12 12
 <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>
13 13
 
14  
-<form action="" method="post">
  14
+<form action="" method="post">{% csrf_token %}
15 15
 
16 16
 {{ form.old_password.errors }}
17 17
 <p class="aligned wide"><label for="id_old_password">{% trans 'Old password:' %}</label>{{ form.old_password }}</p>
2  django/contrib/admin/templates/registration/password_reset_confirm.html
@@ -13,7 +13,7 @@
13 13
 
14 14
 <p>{% trans "Please enter your new password twice so we can verify you typed it in correctly." %}</p>
15 15
 
16  
-<form action="" method="post">
  16
+<form action="" method="post">{% csrf_token %}
17 17
 {{ form.new_password1.errors }}
18 18
 <p class="aligned wide"><label for="id_new_password1">{% trans 'New password:' %}</label>{{ form.new_password1 }}</p>
19 19
 {{ form.new_password2.errors }}
2  django/contrib/admin/templates/registration/password_reset_form.html
@@ -11,7 +11,7 @@
11 11
 
12 12
 <p>{% trans "Forgotten your password? Enter your e-mail address below, and we'll e-mail instructions for setting a new one." %}</p>
13 13
 
14  
-<form action="" method="post">
  14
+<form action="" method="post">{% csrf_token %}
15 15
 {{ form.email.errors }}
16 16
 <p><label for="id_email">{% trans 'E-mail address:' %}</label> {{ form.email }} <input type="submit" value="{% trans 'Reset my password' %}" /></p>
17 17
 </form>
9  django/contrib/auth/views.py
@@ -4,6 +4,7 @@
4 4
 from django.contrib.auth.forms import AuthenticationForm
5 5
 from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm, PasswordChangeForm
6 6
 from django.contrib.auth.tokens import default_token_generator
  7
+from django.contrib.csrf.decorators import csrf_protect
7 8
 from django.core.urlresolvers import reverse
8 9
 from django.shortcuts import render_to_response, get_object_or_404
9 10
 from django.contrib.sites.models import Site, RequestSite
@@ -14,6 +15,8 @@
14 15
 from django.contrib.auth.models import User
15 16
 from django.views.decorators.cache import never_cache
16 17
 
  18
+@csrf_protect
  19
+@never_cache
17 20
 def login(request, template_name='registration/login.html',
18 21
           redirect_field_name=REDIRECT_FIELD_NAME,
19 22
           authentication_form=AuthenticationForm):
@@ -43,7 +46,6 @@ def login(request, template_name='registration/login.html',
43 46
         'site': current_site,
44 47
         'site_name': current_site.name,
45 48
     }, context_instance=RequestContext(request))
46  
-login = never_cache(login)
47 49
 
48 50
 def logout(request, next_page=None, template_name='registration/logged_out.html', redirect_field_name=REDIRECT_FIELD_NAME):
49 51
     "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
80 82
 #   prompts for a new password
81 83
 # - password_reset_complete shows a success message for the above
82 84
 
  85
+@csrf_protect
83 86
 def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html',
84 87
         email_template_name='registration/password_reset_email.html',
85 88
         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
109 112
 def password_reset_done(request, template_name='registration/password_reset_done.html'):
110 113
     return render_to_response(template_name, context_instance=RequestContext(request))
111 114
 
  115
+# Doesn't need csrf_protect since no-one can guess the URL
112 116
 def password_reset_confirm(request, uidb36=None, token=None, template_name='registration/password_reset_confirm.html',
113 117
                            token_generator=default_token_generator, set_password_form=SetPasswordForm,
114 118
                            post_reset_redirect=None):
@@ -146,6 +150,8 @@ def password_reset_complete(request, template_name='registration/password_reset_
146 150
     return render_to_response(template_name, context_instance=RequestContext(request,
147 151
                                                                              {'login_url': settings.LOGIN_URL}))
148 152
 
  153
+@csrf_protect
  154
+@login_required
149 155
 def password_change(request, template_name='registration/password_change_form.html',
150 156
                     post_change_redirect=None, password_change_form=PasswordChangeForm):
151 157
     if post_change_redirect is None:
@@ -160,7 +166,6 @@ def password_change(request, template_name='registration/password_change_form.ht
160 166
     return render_to_response(template_name, {
161 167
         'form': form,
162 168
     }, context_instance=RequestContext(request))
163  
-password_change = login_required(password_change)
164 169
 
165 170
 def password_change_done(request, template_name='registration/password_change_done.html'):
166 171
     return render_to_response(template_name, context_instance=RequestContext(request))
2  django/contrib/comments/templates/comments/approve.html
@@ -6,7 +6,7 @@
6 6
 {% block content %}
7 7
   <h1>{% trans "Really make this comment public?" %}</h1>
8 8
   <blockquote>{{ comment|linebreaks }}</blockquote>
9  
-  <form action="." method="post">
  9
+  <form action="." method="post">{% csrf_token %}
10 10
     {% if next %}<input type="hidden" name="next" value="{{ next }}" id="next" />{% endif %}
11 11
     <p class="submit">
12 12
       <input type="submit" name="submit" value="{% trans "Approve" %}" /> or <a href="{{ comment.get_absolute_url }}">cancel</a>
2  django/contrib/comments/templates/comments/delete.html
@@ -6,7 +6,7 @@
6 6
 {% block content %}
7 7
 <h1>{% trans "Really remove this comment?" %}</h1>
8 8
   <blockquote>{{ comment|linebreaks }}</blockquote>
9  
-  <form action="." method="post">
  9
+  <form action="." method="post">{% csrf_token %}
10 10
     {% if next %}<input type="hidden" name="next" value="{{ next }}" id="next" />{% endif %}
11 11
     <p class="submit">
12 12
     <input type="submit" name="submit" value="{% trans "Remove" %}" /> or <a href="{{ comment.get_absolute_url }}">cancel</a>
2  django/contrib/comments/templates/comments/flag.html
@@ -6,7 +6,7 @@
6 6
 {% block content %}
7 7
 <h1>{% trans "Really flag this comment?" %}</h1>
8 8
   <blockquote>{{ comment|linebreaks }}</blockquote>
9  
-  <form action="." method="post">
  9
+  <form action="." method="post">{% csrf_token %}
10 10
     {% if next %}<input type="hidden" name="next" value="{{ next }}" id="next" />{% endif %}
11 11
     <p class="submit">
12 12
     <input type="submit" name="submit" value="{% trans "Flag" %}" /> or <a href="{{ comment.get_absolute_url }}">cancel</a>
2  django/contrib/comments/templates/comments/form.html
... ...
@@ -1,5 +1,5 @@
1 1
 {% load comments i18n %}
2  
-<form action="{% comment_form_target %}" method="post">
  2
+<form action="{% comment_form_target %}" method="post">{% csrf_token %}
3 3
   {% if next %}<input type="hidden" name="next" value="{{ next }}" />{% endif %}
4 4
   {% for field in form %}
5 5
     {% if field.is_hidden %}
2  django/contrib/comments/templates/comments/preview.html
@@ -5,7 +5,7 @@
5 5
 
6 6
 {% block content %}
7 7
   {% load comments %}
8  
-  <form action="{% comment_form_target %}" method="post">
  8
+  <form action="{% comment_form_target %}" method="post">{% csrf_token %}
9 9
     {% if next %}<input type="hidden" name="next" value="{{ next }}" />{% endif %}
10 10
     {% if form.errors %}
11 11
     <h1>{% blocktrans count form.errors|length as counter %}Please correct the error below{% plural %}Please correct the errors below{% endblocktrans %}</h1>
5  django/contrib/comments/views/comments.py
@@ -10,6 +10,7 @@
10 10
 from django.views.decorators.http import require_POST
11 11
 from django.contrib import comments
12 12
 from django.contrib.comments import signals
  13
+from django.contrib.csrf.decorators import csrf_protect
13 14
 
14 15
 class CommentPostBadRequest(http.HttpResponseBadRequest):
15 16
     """
@@ -22,6 +23,8 @@ def __init__(self, why):
22 23
         if settings.DEBUG:
23 24
             self.content = render_to_string("comments/400-debug.html", {"why": why})
24 25
 
  26
+@csrf_protect
  27
+@require_POST
25 28
 def post_comment(request, next=None):
26 29
     """
27 30
     Post a comment.
@@ -116,8 +119,6 @@ def post_comment(request, next=None):
116 119
 
117 120
     return next_redirect(data, next, comment_done, c=comment._get_pk_val())
118 121
 
119  
-post_comment = require_POST(post_comment)
120  
-
121 122
 comment_done = confirmation_view(
122 123
     template = "comments/posted.html",
123 124
     doc = """Display a "comment was posted" success page."""
4  django/contrib/comments/views/moderation.py
@@ -5,7 +5,9 @@
5 5
 from utils import next_redirect, confirmation_view
6 6
 from django.contrib import comments
7 7
 from django.contrib.comments import signals
  8
+from django.contrib.csrf.decorators import csrf_protect
8 9
 
  10
+@csrf_protect
9 11
 @login_required
10 12
 def flag(request, comment_id, next=None):
11 13
     """
@@ -30,6 +32,7 @@ def flag(request, comment_id, next=None):
30 32
             template.RequestContext(request)
31 33
         )
32 34
 
  35
+@csrf_protect
33 36
 @permission_required("comments.can_moderate")
34 37
 def delete(request, comment_id, next=None):
35 38
     """
@@ -56,6 +59,7 @@ def delete(request, comment_id, next=None):
56 59
             template.RequestContext(request)
57 60
         )
58 61
 
  62
+@csrf_protect
59 63
 @permission_required("comments.can_moderate")
60 64
 def approve(request, comment_id, next=None):
61 65
     """
20  django/contrib/csrf/context_processors.py
... ...
@@ -0,0 +1,20 @@
  1
+from django.contrib.csrf.middleware import get_token
  2
+from django.utils.functional import lazy
  3
+
  4
+def csrf(request):
  5
+    """
  6
+    Context processor that provides a CSRF token, or the string 'NOTPROVIDED' if
  7
+    it has not been provided by either a view decorator or the middleware
  8
+    """
  9
+    def _get_val():
  10
+        token = get_token(request)
  11
+        if token is None:
  12
+            # In order to be able to provide debugging info in the
  13
+            # case of misconfiguration, we use a sentinel value
  14
+            # instead of returning an empty dict.
  15
+            return 'NOTPROVIDED'
  16
+        else:
  17
+            return token
  18
+    _get_val = lazy(_get_val, str)
  19
+
  20
+    return {'csrf_token': _get_val() }
10  django/contrib/csrf/decorators.py
... ...
@@ -0,0 +1,10 @@
  1
+from django.contrib.csrf.middleware import CsrfViewMiddleware
  2
+from django.utils.decorators import decorator_from_middleware
  3
+
  4
+csrf_protect = decorator_from_middleware(CsrfViewMiddleware)
  5
+csrf_protect.__name__ = "csrf_protect"
  6
+csrf_protect.__doc__ = """
  7
+This decorator adds CSRF protection in exactly the same way as
  8
+CsrfViewMiddleware, but it can be used on a per view basis.  Using both, or
  9
+using the decorator multiple times, is harmless and efficient.
  10
+"""
256  django/contrib/csrf/middleware.py
@@ -5,94 +5,213 @@
5 5
 against request forgeries from other sites.
6 6
 """
7 7
 
8  
-import re
9 8
 import itertools
  9
+import re
  10
+import random
10 11
 try:
11 12
     from functools import wraps
12 13
 except ImportError:
13 14
     from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
14 15
 
15 16
 from django.conf import settings
16  
-from django.http import HttpResponseForbidden
  17
+from django.core.urlresolvers import get_callable
  18
+from django.utils.cache import patch_vary_headers
17 19
 from django.utils.hashcompat import md5_constructor
18 20
 from django.utils.safestring import mark_safe
19 21
 
20  
-_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>')
21  
-
22 22
 _POST_FORM_RE = \
23 23
     re.compile(r'(<form\W[^>]*\bmethod\s*=\s*(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE)
24 24
 
25 25
 _HTML_TYPES = ('text/html', 'application/xhtml+xml')
26 26
 
27  
-def _make_token(session_id):
  27
+# Use the system (hardware-based) random number generator if it exists.
  28
+if hasattr(random, 'SystemRandom'):
  29
+    randrange = random.SystemRandom().randrange
  30
+else:
  31
+    randrange = random.randrange
  32
+_MAX_CSRF_KEY = 18446744073709551616L     # 2 << 63
  33
+
  34
+def _get_failure_view():
  35
+    """
  36
+    Returns the view to be used for CSRF rejections
  37
+    """
  38
+    return get_callable(settings.CSRF_FAILURE_VIEW)
  39
+
  40
+def _get_new_csrf_key():
  41
+    return md5_constructor("%s%s"
  42
+                % (randrange(0, _MAX_CSRF_KEY), settings.SECRET_KEY)).hexdigest()
  43
+
  44
+def _make_legacy_session_token(session_id):
28 45
     return md5_constructor(settings.SECRET_KEY + session_id).hexdigest()
29 46
 
  47
+def get_token(request):
  48
+    """
  49
+    Returns the the CSRF token required for a POST form.
  50
+
  51
+    A side effect of calling this function is to make the the csrf_protect
  52
+    decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie'
  53
+    header to the outgoing response.  For this reason, you may need to use this
  54
+    function lazily, as is done by the csrf context processor.
  55
+    """
  56
+    request.META["CSRF_COOKIE_USED"] = True
  57
+    return request.META.get("CSRF_COOKIE", None)
  58
+
30 59
 class CsrfViewMiddleware(object):
31 60
     """
32 61
     Middleware that requires a present and correct csrfmiddlewaretoken
33  
-    for POST requests that have an active session.
  62
+    for POST requests that have a CSRF cookie, and sets an outgoing
  63
+    CSRF cookie.
  64
+
  65
+    This middleware should be used in conjunction with the csrf_token template
  66
+    tag.
34 67
     """
35 68
     def process_view(self, request, callback, callback_args, callback_kwargs):
  69
+        if getattr(callback, 'csrf_exempt', False):
  70
+            return None
  71
+
  72
+        if getattr(request, 'csrf_processing_done', False):
  73
+            return None
  74
+
  75
+        reject = lambda s: _get_failure_view()(request, reason=s)
  76
+        def accept():
  77
+            # Avoid checking the request twice by adding a custom attribute to
  78
+            # request.  This will be relevant when both decorator and middleware
  79
+            # are used.
  80
+            request.csrf_processing_done = True
  81
+            return None
  82
+
  83
+        # If the user doesn't have a CSRF cookie, generate one and store it in the
  84
+        # request, so it's available to the view.  We'll store it in a cookie when
  85
+        # we reach the response.
  86
+        try:
  87
+            request.META["CSRF_COOKIE"] = request.COOKIES[settings.CSRF_COOKIE_NAME]
  88
+            cookie_is_new = False
  89
+        except KeyError:
  90
+            # No cookie, so create one.
  91
+            request.META["CSRF_COOKIE"] = _get_new_csrf_key()
  92
+            cookie_is_new = True
  93
+
36 94
         if request.method == 'POST':
37  
-            if getattr(callback, 'csrf_exempt', False):
38  
-                return None
  95
+            if getattr(request, '_dont_enforce_csrf_checks', False):
  96
+                # Mechanism to turn off CSRF checks for test suite.  It comes after
  97
+                # the creation of CSRF cookies, so that everything else continues to
  98
+                # work exactly the same (e.g. cookies are sent etc), but before the
  99
+                # any branches that call reject()
  100
+                return accept()
39 101
 
40 102
             if request.is_ajax():
41  
-                return None
  103
+                # .is_ajax() is based on the presence of X-Requested-With.  In
  104
+                # the context of a browser, this can only be sent if using
  105
+                # XmlHttpRequest.  Browsers implement careful policies for
  106
+                # XmlHttpRequest:
  107
+                #
  108
+                #  * Normally, only same-domain requests are allowed.
  109
+                #
  110
+                #  * Some browsers (e.g. Firefox 3.5 and later) relax this
  111
+                #    carefully:
  112
+                #
  113
+                #    * if it is a 'simple' GET or POST request (which can
  114
+                #      include no custom headers), it is allowed to be cross
  115
+                #      domain.  These requests will not be recognized as AJAX.
  116
+                #
  117
+                #    * if a 'preflight' check with the server confirms that the
  118
+                #      server is expecting and allows the request, cross domain
  119
+                #      requests even with custom headers are allowed. These
  120
+                #      requests will be recognized as AJAX, but can only get
  121
+                #      through when the developer has specifically opted in to
  122
+                #      allowing the cross-domain POST request.
  123
+                #
  124
+                # So in all cases, it is safe to allow these requests through.
  125
+                return accept()
42 126
 
43  
-            try:
44  
-                session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
45  
-            except KeyError:
46  
-                # No session, no check required
47  
-                return None
  127
+            if request.is_secure():
  128
+                # Strict referer checking for HTTPS
  129
+                referer = request.META.get('HTTP_REFERER')
  130
+                if referer is None:
  131
+                    return reject("Referer checking failed - no Referer.")
48 132
 
49  
-            csrf_token = _make_token(session_id)
50  
-            # check incoming token
51  
-            try:
52  
-                request_csrf_token = request.POST['csrfmiddlewaretoken']
53  
-            except KeyError:
54  
-                return HttpResponseForbidden(_ERROR_MSG)
  133
+                # The following check ensures that the referer is HTTPS,
  134
+                # the domains match and the ports match.  This might be too strict.
  135
+                good_referer = 'https://%s/' % request.get_host()
  136
+                if not referer.startswith(good_referer):
  137
+                    return reject("Referer checking failed - %s does not match %s." %
  138
+                                  (referer, good_referer))
  139
+
  140
+            # If the user didn't already have a CSRF key, then accept the
  141
+            # session key for the middleware token, so CSRF protection isn't lost
  142
+            # for the period between upgrading to CSRF cookes to the first time
  143
+            # each user comes back to the site to receive one.
  144
+            if cookie_is_new:
  145
+                try:
  146
+                    session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
  147
+                    csrf_token = _make_legacy_session_token(session_id)
  148
+                except KeyError:
  149
+                    # No CSRF cookie and no session cookie. For POST requests,
  150
+                    # we insist on a CSRF cookie, and in this way we can avoid
  151
+                    # all CSRF attacks, including login CSRF.
  152
+                    return reject("No CSRF cookie.")
  153
+            else:
  154
+                csrf_token = request.META["CSRF_COOKIE"]
55 155
 
  156
+            # check incoming token
  157
+            request_csrf_token = request.POST.get('csrfmiddlewaretoken', None)
56 158
             if request_csrf_token != csrf_token:
57  
-                return HttpResponseForbidden(_ERROR_MSG)
  159
+                return reject("CSRF token missing or incorrect.")
  160
+
  161
+        return accept()
  162
+
  163
+    def process_response(self, request, response):
  164
+        if getattr(response, 'csrf_processing_done', False):
  165
+            return response
58 166
 
59  
-        return None
  167
+        # If CSRF_COOKIE is unset, then CsrfViewMiddleware.process_view was
  168
+        # never called, probaby because a request middleware returned a response
  169
+        # (for example, contrib.auth redirecting to a login page).
  170
+        if request.META.get("CSRF_COOKIE") is None:
  171
+            return response
  172
+
  173
+        if not request.META.get("CSRF_COOKIE_USED", False):
  174
+            return response
  175
+
  176
+        # Set the CSRF cookie even if it's already set, so we renew the expiry timer.
  177
+        response.set_cookie(settings.CSRF_COOKIE_NAME,
  178
+                request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52,
  179
+                domain=settings.CSRF_COOKIE_DOMAIN)
  180
+        # Content varies with the CSRF cookie, so set the Vary header.
  181
+        patch_vary_headers(response, ('Cookie',))
  182
+        response.csrf_processing_done = True
  183
+        return response
60 184
 
61 185
 class CsrfResponseMiddleware(object):
62 186
     """
63  
-    Middleware that post-processes a response to add a
64  
-    csrfmiddlewaretoken if the response/request have an active
65  
-    session.
  187
+    DEPRECATED
  188
+    Middleware that post-processes a response to add a csrfmiddlewaretoken.
  189
+
  190
+    This exists for backwards compatibility and as an interim measure until
  191
+    applications are converted to using use the csrf_token template tag
  192
+    instead. It will be removed in Django 1.4.
66 193
     """
  194
+    def __init__(self):
  195
+        import warnings
  196
+        warnings.warn(
  197
+            "CsrfResponseMiddleware and CsrfMiddleware are deprecated; use CsrfViewMiddleware and the template tag instead (see CSRF documentation).",
  198
+            PendingDeprecationWarning
  199
+        )
  200
+
67 201
     def process_response(self, request, response):
68 202
         if getattr(response, 'csrf_exempt', False):
69 203
             return response
70 204
 
71  
-        csrf_token = None
72  
-        try:
73  
-            # This covers a corner case in which the outgoing response
74  
-            # both contains a form and sets a session cookie.  This
75  
-            # really should not be needed, since it is best if views
76  
-            # that create a new session (login pages) also do a
77  
-            # redirect, as is done by all such view functions in
78  
-            # Django.
79  
-            cookie = response.cookies[settings.SESSION_COOKIE_NAME]
80  
-            csrf_token = _make_token(cookie.value)
81  
-        except KeyError:
82  
-            # Normal case - look for existing session cookie
83  
-            try:
84  
-                session_id = request.COOKIES[settings.SESSION_COOKIE_NAME]
85  
-                csrf_token = _make_token(session_id)
86  
-            except KeyError:
87  
-                # no incoming or outgoing cookie
88  
-                pass
89  
-
90  
-        if csrf_token is not None and \
91  
-                response['Content-Type'].split(';')[0] in _HTML_TYPES:
  205
+        if response['Content-Type'].split(';')[0] in _HTML_TYPES:
  206
+            csrf_token = get_token(request)
  207
+            # If csrf_token is None, we have no token for this request, which probably
  208
+            # means that this is a response from a request middleware.
  209
+            if csrf_token is None:
  210
+                return response
92 211
 
93 212
             # ensure we don't add the 'id' attribute twice (HTML validity)
94 213
             idattributes = itertools.chain(("id='csrfmiddlewaretoken'",),
95  
-                                            itertools.repeat(''))
  214
+                                           itertools.repeat(''))
96 215
             def add_csrf_field(match):
97 216
                 """Returns the matched <form> tag plus the added <input> element"""
98 217
                 return mark_safe(match.group() + "<div style='display:none;'>" + \
@@ -103,34 +222,43 @@ def add_csrf_field(match):
103 222
             # Modify any POST forms
104 223
             response.content, n = _POST_FORM_RE.subn(add_csrf_field, response.content)
105 224
             if n > 0:
  225
+                # Content varies with the CSRF cookie, so set the Vary header.
  226
+                patch_vary_headers(response, ('Cookie',))
  227
+
106 228
                 # Since the content has been modified, any Etag will now be
107  
-                # incorrect.  We could recalculate, but only is we assume that
  229
+                # incorrect.  We could recalculate, but only if we assume that              
108 230
                 # the Etag was set by CommonMiddleware. The safest thing is just
109 231
                 # to delete. See bug #9163
110 232
                 del response['ETag']
111 233
         return response
112 234
 
113  
-class CsrfMiddleware(CsrfViewMiddleware, CsrfResponseMiddleware):
114  
-    """Django middleware that adds protection against Cross Site
  235
+class CsrfMiddleware(object):
  236
+    """
  237
+    Django middleware that adds protection against Cross Site
115 238
     Request Forgeries by adding hidden form fields to POST forms and
116 239
     checking requests for the correct value.
117 240
 
118  
-    In the list of middlewares, SessionMiddleware is required, and
119  
-    must come after this middleware.  CsrfMiddleWare must come after
120  
-    compression middleware.
121  
-
122  
-    If a session ID cookie is present, it is hashed with the
123  
-    SECRET_KEY setting to create an authentication token.  This token
124  
-    is added to all outgoing POST forms and is expected on all
125  
-    incoming POST requests that have a session ID cookie.
  241
+    CsrfMiddleware uses two middleware, CsrfViewMiddleware and
  242
+    CsrfResponseMiddleware, which can be used independently.  It is recommended
  243
+    to use only CsrfViewMiddleware and use the csrf_token template tag in
  244
+    templates for inserting the token.
  245
+    """
  246
+    # We can't just inherit from CsrfViewMiddleware and CsrfResponseMiddleware
  247
+    # because both have process_response methods.
  248
+    def __init__(self):
  249
+        self.response_middleware = CsrfResponseMiddleware()
  250
+        self.view_middleware = CsrfViewMiddleware()
126 251
 
127  
-    If you are setting cookies directly, instead of using Django's
128  
-    session framework, this middleware will not work.
  252
+    def process_response(self, request, resp):
  253
+        # We must do the response post-processing first, because that calls
  254
+        # get_token(), which triggers a flag saying that the CSRF cookie needs
  255
+        # to be sent (done in CsrfViewMiddleware.process_response)
  256
+        resp2 = self.response_middleware.process_response(request, resp)
  257
+        return self.view_middleware.process_response(request, resp2)
129 258
 
130  
-    CsrfMiddleWare is composed of two middleware, CsrfViewMiddleware
131  
-    and CsrfResponseMiddleware which can be used independently.
132  
-    """
133  
-    pass
  259
+    def process_view(self, request, callback, callback_args, callback_kwargs):
  260
+        return self.view_middleware.process_view(request, callback, callback_args,
  261
+                                                 callback_kwargs)
134 262
 
135 263
 def csrf_response_exempt(view_func):
136 264
     """
311  django/contrib/csrf/tests.py
... ...
@@ -1,144 +1,323 @@
1 1
 # -*- coding: utf-8 -*-
2 2
 
3 3
 from django.test import TestCase
4  
-from django.http import HttpRequest, HttpResponse, HttpResponseForbidden
5  
-from django.contrib.csrf.middleware import CsrfMiddleware, _make_token, csrf_exempt
  4
+from django.http import HttpRequest, HttpResponse
  5
+from django.contrib.csrf.middleware import CsrfMiddleware, CsrfViewMiddleware, csrf_exempt
  6
+from django.contrib.csrf.context_processors import csrf
  7
+from django.contrib.sessions.middleware import SessionMiddleware
  8
+from django.utils.importlib import import_module
6 9
 from django.conf import settings
  10
+from django.template import RequestContext, Template
7 11
 
8  
-
  12
+# Response/views used for CsrfResponseMiddleware and CsrfViewMiddleware tests
9 13
 def post_form_response():
10 14
     resp = HttpResponse(content="""
11 15
 <html><body><form method="POST"><input type="text" /></form></body></html>
12 16
 """, mimetype="text/html")
13 17
     return resp
14 18
 
15  
-def test_view(request):
  19
+def post_form_response_non_html():
  20
+    resp = post_form_response()
  21
+    resp["Content-Type"] = "application/xml"
  22
+    return resp
  23
+
  24
+def post_form_view(request):
  25
+    """A view that returns a POST form (without a token)"""
16 26
     return post_form_response()
17 27
 
  28
+# Response/views used for template tag tests
  29
+def _token_template():
  30
+    return Template("{% csrf_token %}")
  31
+
  32
+def _render_csrf_token_template(req):
  33
+    context = RequestContext(req, processors=[csrf])
  34
+    template = _token_template()
  35
+    return template.render(context)
  36
+
  37
+def token_view(request):
  38
+    """A view that uses {% csrf_token %}"""
  39
+    return HttpResponse(_render_csrf_token_template(request))
  40
+
  41
+def non_token_view_using_request_processor(request):
  42
+    """
  43
+    A view that doesn't use the token, but does use the csrf view processor.
  44
+    """
  45
+    context = RequestContext(request, processors=[csrf])
  46
+    template = Template("")
  47
+    return HttpResponse(template.render(context))
  48
+
  49
+class TestingHttpRequest(HttpRequest):
  50
+    """
  51
+    A version of HttpRequest that allows us to change some things
  52
+    more easily
  53
+    """
  54
+    def is_secure(self):
  55
+        return getattr(self, '_is_secure', False)
  56
+
18 57
 class CsrfMiddlewareTest(TestCase):
  58
+    _csrf_id = "1"
19 59
 
  60
+    # This is a valid session token for this ID and secret key.  This was generated using
  61
+    # the old code that we're to be backwards-compatible with.  Don't use the CSRF code
  62
+    # to generate this hash, or we're merely testing the code against itself and not
  63
+    # checking backwards-compatibility.  This is also the output of (echo -n test1 | md5sum).
  64
+    _session_token = "5a105e8b9d40e1329780d62ea2265d8a"
20 65
     _session_id = "1"
  66
+    _secret_key_for_session_test= "test"
21 67
 
22  
-    def _get_GET_no_session_request(self):
23  
-        return HttpRequest()
  68
+    def _get_GET_no_csrf_cookie_request(self):
  69
+        return TestingHttpRequest()
24 70
 
25  
-    def _get_GET_session_request(self):
26  
-        req = self._get_GET_no_session_request()
27  
-        req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
  71
+    def _get_GET_csrf_cookie_request(self):
  72
+        req = TestingHttpRequest()
  73
+        req.COOKIES[settings.CSRF_COOKIE_NAME] = self._csrf_id
28 74
         return req
29 75
 
30  
-    def _get_POST_session_request(self):
31  
-        req = self._get_GET_session_request()
  76
+    def _get_POST_csrf_cookie_request(self):
  77
+        req = self._get_GET_csrf_cookie_request()
32 78
         req.method = "POST"
33 79
         return req
34 80
 
35  
-    def _get_POST_no_session_request(self):
36  
-        req = self._get_GET_no_session_request()
  81
+    def _get_POST_no_csrf_cookie_request(self):
  82
+        req = self._get_GET_no_csrf_cookie_request()
37 83
         req.method = "POST"
38 84
         return req
39 85
 
40  
-    def _get_POST_session_request_with_token(self):
41  
-        req = self._get_POST_session_request()
42  
-        req.POST['csrfmiddlewaretoken'] = _make_token(self._session_id)
  86
+    def _get_POST_request_with_token(self):
  87
+        req = self._get_POST_csrf_cookie_request()
  88
+        req.POST['csrfmiddlewaretoken'] = self._csrf_id
43 89
         return req
44 90
 
45  
-    def _get_post_form_response(self):
46  
-        return post_form_response()
47  
-
48  
-    def _get_new_session_response(self):
49  
-        resp = self._get_post_form_response()
50  
-        resp.cookies[settings.SESSION_COOKIE_NAME] = self._session_id
51  
-        return resp
  91
+    def _get_POST_session_request_with_token(self):
  92
+        req = self._get_POST_no_csrf_cookie_request()
  93
+        req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
  94
+        req.POST['csrfmiddlewaretoken'] = self._session_token
  95
+        return req
52 96
 
53  
-    def _check_token_present(self, response):
54  
-        self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % _make_token(self._session_id))
  97
+    def _get_POST_session_request_no_token(self):
  98
+        req = self._get_POST_no_csrf_cookie_request()
  99
+        req.COOKIES[settings.SESSION_COOKIE_NAME] = self._session_id
  100
+        return req
55 101
 
56  
-    def get_view(self):
57  
-        return test_view
  102
+    def _check_token_present(self, response, csrf_id=None):
  103
+        self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % (csrf_id or self._csrf_id))
58 104
 
59  
-    # Check the post processing
60  
-    def test_process_response_no_session(self):
  105
+    # Check the post processing and outgoing cookie
  106
+    def test_process_response_no_csrf_cookie(self):
61 107
         """
62  
-        Check the post-processor does nothing if no session active
  108
+        When no prior CSRF cookie exists, check that the cookie is created and a
  109
+        token is inserted.
63 110
         """
64  
-        req = self._get_GET_no_session_request()
65  
-        resp = self._get_post_form_response()
  111
+        req = self._get_GET_no_csrf_cookie_request()
  112
+        CsrfMiddleware().process_view(req, post_form_view, (), {})
  113
+
  114
+        resp = post_form_response()
66 115
         resp_content = resp.content # needed because process_response modifies resp
67 116
         resp2 = CsrfMiddleware().process_response(req, resp)
68  
-        self.assertEquals(resp_content, resp2.content)
69 117
 
70  
-    def test_process_response_existing_session(self):
  118
+        csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
  119
+        self.assertNotEqual(csrf_cookie, False)
  120
+        self.assertNotEqual(resp_content, resp2.content)
  121
+        self._check_token_present(resp2, csrf_cookie.value)
  122
+        # Check the Vary header got patched correctly
  123
+        self.assert_('Cookie' in resp2.get('Vary',''))
  124
+
  125
+    def test_process_response_no_csrf_cookie_view_only_get_token_used(self):
  126
+        """
  127
+        When no prior CSRF cookie exists, check that the cookie is created, even
  128
+        if only CsrfViewMiddleware is used.
  129
+        """
  130
+        # This is checking that CsrfViewMiddleware has the cookie setting
  131
+        # code. Most of the other tests use CsrfMiddleware.
  132
+        req = self._get_GET_no_csrf_cookie_request()
  133
+        # token_view calls get_token() indirectly
  134
+        CsrfViewMiddleware().process_view(req, token_view, (), {})
  135
+        resp = token_view(req)
  136
+        resp2 = CsrfViewMiddleware().process_response(req, resp)
  137
+
  138
+        csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
  139
+        self.assertNotEqual(csrf_cookie, False)
  140
+
  141
+    def test_process_response_get_token_not_used(self):
71 142
         """
72  
-        Check that the token is inserted if there is an existing session
  143
+        Check that if get_token() is not called, the view middleware does not
  144
+        add a cookie.
73 145
         """
74  
-        req = self._get_GET_session_request()
75  
-        resp = self._get_post_form_response()
  146
+        # This is important to make pages cacheable.  Pages which do call
  147
+        # get_token(), assuming they use the token, are not cacheable because
  148
+        # the token is specific to the user
  149
+        req = self._get_GET_no_csrf_cookie_request()
  150
+        # non_token_view_using_request_processor does not call get_token(), but
  151
+        # does use the csrf request processor.  By using this, we are testing
  152
+        # that the view processor is properly lazy and doesn't call get_token()
  153
+        # until needed.
  154
+        CsrfViewMiddleware().process_view(req, non_token_view_using_request_processor, (), {})
  155
+        resp = non_token_view_using_request_processor(req)
  156
+        resp2 = CsrfViewMiddleware().process_response(req, resp)
  157
+
  158
+        csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
  159
+        self.assertEqual(csrf_cookie, False)
  160
+
  161
+    def test_process_response_existing_csrf_cookie(self):
  162
+        """
  163
+        Check that the token is inserted when a prior CSRF cookie exists
  164
+        """
  165
+        req = self._get_GET_csrf_cookie_request()
  166
+        CsrfMiddleware().process_view(req, post_form_view, (), {})
  167
+
  168
+        resp = post_form_response()
76 169
         resp_content = resp.content # needed because process_response modifies resp
77 170
         resp2 = CsrfMiddleware().process_response(req, resp)
78 171
         self.assertNotEqual(resp_content, resp2.content)
79 172
         self._check_token_present(resp2)
80 173
 
81  
-    def test_process_response_new_session(self):
  174
+    def test_process_response_non_html(self):
82 175
         """
83  
-        Check that the token is inserted if there is a new session being started
  176
+        Check the the post-processor does nothing for content-types not in _HTML_TYPES.
84 177
         """
85  
-        req = self._get_GET_no_session_request() # no session in request
86  
-        resp = self._get_new_session_response() # but new session started
  178
+        req = self._get_GET_no_csrf_cookie_request()
  179
+        CsrfMiddleware().process_view(req, post_form_view, (), {})
  180
+        resp = post_form_response_non_html()
87 181
         resp_content = resp.content # needed because process_response modifies resp
88 182
         resp2 = CsrfMiddleware().process_response(req, resp)
89  
-        self.assertNotEqual(resp_content, resp2.content)
90  
-        self._check_token_present(resp2)
  183
+        self.assertEquals(resp_content, resp2.content)
91 184
 
92 185
     def test_process_response_exempt_view(self):
93 186
         """
94 187
         Check that no post processing is done for an exempt view
95 188
         """
96  
-        req = self._get_POST_session_request()
97  
-        resp = csrf_exempt(self.get_view())(req)
  189
+        req = self._get_POST_csrf_cookie_request()
  190
+        resp = csrf_exempt(post_form_view)(req)
98 191
         resp_content = resp.content
99 192
         resp2 = CsrfMiddleware().process_response(req, resp)
100 193
         self.assertEquals(resp_content, resp2.content)
101 194
 
102 195
     # Check the request processing
103  
-    def test_process_request_no_session(self):
  196
+    def test_process_request_no_session_no_csrf_cookie(self):
104 197
         """
105  
-        Check that if no session is present, the middleware does nothing.
106  
-        to the incoming request.
  198
+        Check that if neither a CSRF cookie nor a session cookie are present,
  199
+        the middleware rejects the incoming request.  This will stop login CSRF.
107 200
         """
108  
-        req = self._get_POST_no_session_request()
109  
-        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
110  
-        self.assertEquals(None, req2)
  201
+        req = self._get_POST_no_csrf_cookie_request()
  202
+        req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
  203
+        self.assertEquals(403, req2.status_code)
111 204
 
112  
-    def test_process_request_session_no_token(self):
  205
+    def test_process_request_csrf_cookie_no_token(self):
113 206
         """
114  
-        Check that if a session is present but no token, we get a 'forbidden'
  207
+        Check that if a CSRF cookie is present but no token, the middleware
  208
+        rejects the incoming request.
115 209
         """
116  
-        req = self._get_POST_session_request()
117  
-        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
118  
-        self.assertEquals(HttpResponseForbidden, req2.__class__)
  210
+        req = self._get_POST_csrf_cookie_request()
  211
+        req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
  212
+        self.assertEquals(403, req2.status_code)
119 213
 
120  
-    def test_process_request_session_and_token(self):
  214
+    def test_process_request_csrf_cookie_and_token(self):
121 215
         """
122  
-        Check that if a session is present and a token, the middleware lets it through
  216
+        Check that if both a cookie and a token is present, the middleware lets it through.
123 217
         """
124  
-        req = self._get_POST_session_request_with_token()
125  
-        req2 = CsrfMiddleware().process_view(req, self.get_view(), (), {})
  218
+        req = self._get_POST_request_with_token()
  219
+        req2 = CsrfMiddleware().process_view(req, post_form_view, (), {})
126 220
         self.assertEquals(None, req2)
127 221