Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

[1.2.X] Fixed #14565 - No csrf_token on 404 page.

This solution doesn't have the negative side-effects of [14356].

Backport of [14377] from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@14380 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 36dd74446047570bd6bc1a8bdb8e2eaf3eb39a44 1 parent fcc283a
Luke Plant authored
38  django/middleware/csrf.py
@@ -84,18 +84,22 @@ class CsrfViewMiddleware(object):
84 84
     This middleware should be used in conjunction with the csrf_token template
85 85
     tag.
86 86
     """
  87
+    # The _accept and _reject methods currently only exist for the sake of the
  88
+    # requires_csrf_token decorator.
  89
+    def _accept(self, request):
  90
+        # Avoid checking the request twice by adding a custom attribute to
  91
+        # request.  This will be relevant when both decorator and middleware
  92
+        # are used.
  93
+        request.csrf_processing_done = True
  94
+        return None
  95
+
  96
+    def _reject(self, request, reason):
  97
+        return _get_failure_view()(request, reason=reason)
  98
+
87 99
     def process_view(self, request, callback, callback_args, callback_kwargs):
88 100
         if getattr(request, 'csrf_processing_done', False):
89 101
             return None
90 102
 
91  
-        reject = lambda s: _get_failure_view()(request, reason=s)
92  
-        def accept():
93  
-            # Avoid checking the request twice by adding a custom attribute to
94  
-            # request.  This will be relevant when both decorator and middleware
95  
-            # are used.
96  
-            request.csrf_processing_done = True
97  
-            return None
98  
-
99 103
         # If the user doesn't have a CSRF cookie, generate one and store it in the
100 104
         # request, so it's available to the view.  We'll store it in a cookie when
101 105
         # we reach the response.
@@ -124,7 +128,7 @@ def accept():
124 128
                 # the creation of CSRF cookies, so that everything else continues to
125 129
                 # work exactly the same (e.g. cookies are sent etc), but before the
126 130
                 # any branches that call reject()
127  
-                return accept()
  131
+                return self._accept(request)
128 132
 
129 133
             if request.is_ajax():
130 134
                 # .is_ajax() is based on the presence of X-Requested-With.  In
@@ -149,20 +153,20 @@ def accept():
149 153
                 #      allowing the cross-domain POST request.
150 154
                 #
151 155
                 # So in all cases, it is safe to allow these requests through.
152  
-                return accept()
  156
+                return self._accept(request)
153 157
 
154 158
             if request.is_secure():
155 159
                 # Strict referer checking for HTTPS
156 160
                 referer = request.META.get('HTTP_REFERER')
157 161
                 if referer is None:
158  
-                    return reject(REASON_NO_REFERER)
  162
+                    return self._reject(request, REASON_NO_REFERER)
159 163
 
160 164
                 # The following check ensures that the referer is HTTPS,
161 165
                 # the domains match and the ports match.  This might be too strict.
162 166
                 good_referer = 'https://%s/' % request.get_host()
163 167
                 if not referer.startswith(good_referer):
164  
-                    return reject(REASON_BAD_REFERER %
165  
-                                  (referer, good_referer))
  168
+                    return self._reject(request, REASON_BAD_REFERER %
  169
+                                        (referer, good_referer))
166 170
 
167 171
             # If the user didn't already have a CSRF cookie, then fall back to
168 172
             # the Django 1.1 method (hash of session ID), so a request is not
@@ -176,7 +180,7 @@ def accept():
176 180
                     # No CSRF cookie and no session cookie. For POST requests,
177 181
                     # we insist on a CSRF cookie, and in this way we can avoid
178 182
                     # all CSRF attacks, including login CSRF.
179  
-                    return reject(REASON_NO_COOKIE)
  183
+                    return self._reject(request, REASON_NO_COOKIE)
180 184
             else:
181 185
                 csrf_token = request.META["CSRF_COOKIE"]
182 186
 
@@ -185,11 +189,11 @@ def accept():
185 189
             if request_csrf_token != csrf_token:
186 190
                 if cookie_is_new:
187 191
                     # probably a problem setting the CSRF cookie
188  
-                    return reject(REASON_NO_CSRF_COOKIE)
  192
+                    return self._reject(request, REASON_NO_CSRF_COOKIE)
189 193
                 else:
190  
-                    return reject(REASON_BAD_TOKEN)
  194
+                    return self._reject(request, REASON_BAD_TOKEN)
191 195
 
192  
-        return accept()
  196
+        return self._accept(request)
193 197
 
194 198
     def process_response(self, request, response):
195 199
         if getattr(response, 'csrf_processing_done', False):
16  django/views/decorators/csrf.py
@@ -14,6 +14,22 @@
14 14
 using the decorator multiple times, is harmless and efficient.
15 15
 """
16 16
 
  17
+
  18
+class _EnsureCsrfToken(CsrfViewMiddleware):
  19
+    # We need this to behave just like the CsrfViewMiddleware, but not reject
  20
+    # requests.
  21
+    def _reject(self, request, reason):
  22
+        return None
  23
+
  24
+
  25
+requires_csrf_token = decorator_from_middleware(_EnsureCsrfToken)
  26
+requires_csrf_token.__name__ = 'requires_csrf_token'
  27
+csrf_protect.__doc__ = """
  28
+Use this decorator on views that need a correct csrf_token available to
  29
+RequestContext, but without the CSRF protection that csrf_protect
  30
+enforces.
  31
+"""
  32
+
17 33
 def csrf_response_exempt(view_func):
18 34
     """
19 35
     Modifies a view function so that its response is exempt
8  django/views/defaults.py
... ...
@@ -1,6 +1,11 @@
1 1
 from django import http
  2
+from django.views.decorators.csrf import requires_csrf_token
2 3
 from django.template import Context, RequestContext, loader
3 4
 
  5
+
  6
+# This can be called when CsrfViewMiddleware.process_view has not run, therefore
  7
+# need @requires_csrf_token in case the template needs {% csrf_token %}.
  8
+@requires_csrf_token
4 9
 def page_not_found(request, template_name='404.html'):
5 10
     """
6 11
     Default 404 handler.
@@ -13,6 +18,8 @@ def page_not_found(request, template_name='404.html'):
13 18
     t = loader.get_template(template_name) # You need to create a 404.html template.
14 19
     return http.HttpResponseNotFound(t.render(RequestContext(request, {'request_path': request.path})))
15 20
 
  21
+
  22
+@requires_csrf_token
16 23
 def server_error(request, template_name='500.html'):
17 24
     """
18 25
     500 error handler.
@@ -23,6 +30,7 @@ def server_error(request, template_name='500.html'):
23 30
     t = loader.get_template(template_name) # You need to create a 500.html template.
24 31
     return http.HttpResponseServerError(t.render(Context({})))
25 32
 
  33
+
26 34
 def shortcut(request, content_type_id, object_id):
27 35
     # TODO: Remove this in Django 2.0.
28 36
     # This is a legacy view that depends on the contenttypes framework.
10  tests/regressiontests/csrf_tests/tests.py
@@ -3,7 +3,7 @@
3 3
 from django.test import TestCase
4 4
 from django.http import HttpRequest, HttpResponse
5 5
 from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware
6  
-from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt
  6
+from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt, requires_csrf_token
7 7
 from django.core.context_processors import csrf
8 8
 from django.contrib.sessions.middleware import SessionMiddleware
9 9
 from django.utils.importlib import import_module
@@ -322,6 +322,14 @@ def test_get_token_for_exempt_view(self):
322 322
         resp = token_view(req)
323 323
         self._check_token_present(resp)
324 324
 
  325
+    def test_get_token_for_requires_csrf_token_view(self):
  326
+        """
  327
+        Check that get_token works for a view decorated solely with requires_csrf_token
  328
+        """
  329
+        req = self._get_GET_csrf_cookie_request()
  330
+        resp = requires_csrf_token(token_view)(req)
  331
+        self._check_token_present(resp)
  332
+
325 333
     def test_token_node_with_new_csrf_cookie(self):
326 334
         """
327 335
         Check that CsrfTokenNode works when a CSRF cookie is created by
17  tests/regressiontests/views/tests/defaults.py
@@ -9,6 +9,8 @@
9 9
 class DefaultsTests(TestCase):
10 10
     """Test django views in django/views/defaults.py"""
11 11
     fixtures = ['testdata.json']
  12
+    non_existing_urls = ['/views/non_existing_url/', # this is in urls.py
  13
+                         '/views/other_non_existing_url/'] # this NOT in urls.py
12 14
 
13 15
     def test_shortcut_with_absolute_url(self):
14 16
         "Can view a shortcut for an Author object that has a get_absolute_url method"
@@ -49,12 +51,21 @@ def test_bad_content_type(self):
49 51
 
50 52
     def test_page_not_found(self):
51 53
         "A 404 status is returned by the page_not_found view"
52  
-        non_existing_urls = ['/views/non_existing_url/', # this is in urls.py
53  
-                             '/views/other_non_existing_url/'] # this NOT in urls.py
54  
-        for url in non_existing_urls:
  54
+        for url in self.non_existing_urls:
55 55
             response = self.client.get(url)
56 56
             self.assertEquals(response.status_code, 404)
57 57
 
  58
+    def test_csrf_token_in_404(self):
  59
+        """
  60
+        The 404 page should have the csrf_token available in the context
  61
+        """
  62
+        # See ticket #14565
  63
+        for url in self.non_existing_urls:
  64
+            response = self.client.get(url)
  65
+            csrf_token = response.context['csrf_token']
  66
+            self.assertNotEqual(str(csrf_token), 'NOTPROVIDED')
  67
+            self.assertNotEqual(str(csrf_token), '')
  68
+
58 69
     def test_server_error(self):
59 70
         "The server_error view raises a 500 status"
60 71
         response = self.client.get('/views/server_error/')

0 notes on commit 36dd744

Please sign in to comment.
Something went wrong with that request. Please try again.