Author: lukeplant
Date: 2010-10-28 06:58:51 -0500 (Thu, 28 Oct 2010)
New Revision: 14380

Modified:
   django/branches/releases/1.2.X/django/middleware/csrf.py
   django/branches/releases/1.2.X/django/views/decorators/csrf.py
   django/branches/releases/1.2.X/django/views/defaults.py
   django/branches/releases/1.2.X/tests/regressiontests/csrf_tests/tests.py
   django/branches/releases/1.2.X/tests/regressiontests/views/tests/defaults.py
Log:
[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.

Modified: django/branches/releases/1.2.X/django/middleware/csrf.py
===================================================================
--- django/branches/releases/1.2.X/django/middleware/csrf.py    2010-10-28 
11:58:30 UTC (rev 14379)
+++ django/branches/releases/1.2.X/django/middleware/csrf.py    2010-10-28 
11:58:51 UTC (rev 14380)
@@ -84,18 +84,22 @@
     This middleware should be used in conjunction with the csrf_token template
     tag.
     """
+    # The _accept and _reject methods currently only exist for the sake of the
+    # requires_csrf_token decorator.
+    def _accept(self, request):
+        # Avoid checking the request twice by adding a custom attribute to
+        # request.  This will be relevant when both decorator and middleware
+        # are used.
+        request.csrf_processing_done = True
+        return None
+
+    def _reject(self, request, reason):
+        return _get_failure_view()(request, reason=reason)
+
     def process_view(self, request, callback, callback_args, callback_kwargs):
         if getattr(request, 'csrf_processing_done', False):
             return None
 
-        reject = lambda s: _get_failure_view()(request, reason=s)
-        def accept():
-            # Avoid checking the request twice by adding a custom attribute to
-            # request.  This will be relevant when both decorator and 
middleware
-            # are used.
-            request.csrf_processing_done = True
-            return None
-
         # If the user doesn't have a CSRF cookie, generate one and store it in 
the
         # request, so it's available to the view.  We'll store it in a cookie 
when
         # we reach the response.
@@ -124,7 +128,7 @@
                 # the creation of CSRF cookies, so that everything else 
continues to
                 # work exactly the same (e.g. cookies are sent etc), but 
before the
                 # any branches that call reject()
-                return accept()
+                return self._accept(request)
 
             if request.is_ajax():
                 # .is_ajax() is based on the presence of X-Requested-With.  In
@@ -149,20 +153,20 @@
                 #      allowing the cross-domain POST request.
                 #
                 # So in all cases, it is safe to allow these requests through.
-                return accept()
+                return self._accept(request)
 
             if request.is_secure():
                 # Strict referer checking for HTTPS
                 referer = request.META.get('HTTP_REFERER')
                 if referer is None:
-                    return reject(REASON_NO_REFERER)
+                    return self._reject(request, REASON_NO_REFERER)
 
                 # The following check ensures that the referer is HTTPS,
                 # the domains match and the ports match.  This might be too 
strict.
                 good_referer = 'https://%s/' % request.get_host()
                 if not referer.startswith(good_referer):
-                    return reject(REASON_BAD_REFERER %
-                                  (referer, good_referer))
+                    return self._reject(request, REASON_BAD_REFERER %
+                                        (referer, good_referer))
 
             # If the user didn't already have a CSRF cookie, then fall back to
             # the Django 1.1 method (hash of session ID), so a request is not
@@ -176,7 +180,7 @@
                     # No CSRF cookie and no session cookie. For POST requests,
                     # we insist on a CSRF cookie, and in this way we can avoid
                     # all CSRF attacks, including login CSRF.
-                    return reject(REASON_NO_COOKIE)
+                    return self._reject(request, REASON_NO_COOKIE)
             else:
                 csrf_token = request.META["CSRF_COOKIE"]
 
@@ -185,11 +189,11 @@
             if request_csrf_token != csrf_token:
                 if cookie_is_new:
                     # probably a problem setting the CSRF cookie
-                    return reject(REASON_NO_CSRF_COOKIE)
+                    return self._reject(request, REASON_NO_CSRF_COOKIE)
                 else:
-                    return reject(REASON_BAD_TOKEN)
+                    return self._reject(request, REASON_BAD_TOKEN)
 
-        return accept()
+        return self._accept(request)
 
     def process_response(self, request, response):
         if getattr(response, 'csrf_processing_done', False):

Modified: django/branches/releases/1.2.X/django/views/decorators/csrf.py
===================================================================
--- django/branches/releases/1.2.X/django/views/decorators/csrf.py      
2010-10-28 11:58:30 UTC (rev 14379)
+++ django/branches/releases/1.2.X/django/views/decorators/csrf.py      
2010-10-28 11:58:51 UTC (rev 14380)
@@ -14,6 +14,22 @@
 using the decorator multiple times, is harmless and efficient.
 """
 
+
+class _EnsureCsrfToken(CsrfViewMiddleware):
+    # We need this to behave just like the CsrfViewMiddleware, but not reject
+    # requests.
+    def _reject(self, request, reason):
+        return None
+
+
+requires_csrf_token = decorator_from_middleware(_EnsureCsrfToken)
+requires_csrf_token.__name__ = 'requires_csrf_token'
+csrf_protect.__doc__ = """
+Use this decorator on views that need a correct csrf_token available to
+RequestContext, but without the CSRF protection that csrf_protect
+enforces.
+"""
+
 def csrf_response_exempt(view_func):
     """
     Modifies a view function so that its response is exempt

Modified: django/branches/releases/1.2.X/django/views/defaults.py
===================================================================
--- django/branches/releases/1.2.X/django/views/defaults.py     2010-10-28 
11:58:30 UTC (rev 14379)
+++ django/branches/releases/1.2.X/django/views/defaults.py     2010-10-28 
11:58:51 UTC (rev 14380)
@@ -1,6 +1,11 @@
 from django import http
+from django.views.decorators.csrf import requires_csrf_token
 from django.template import Context, RequestContext, loader
 
+
+# This can be called when CsrfViewMiddleware.process_view has not run, 
therefore
+# need @requires_csrf_token in case the template needs {% csrf_token %}.
+...@requires_csrf_token
 def page_not_found(request, template_name='404.html'):
     """
     Default 404 handler.
@@ -13,6 +18,8 @@
     t = loader.get_template(template_name) # You need to create a 404.html 
template.
     return http.HttpResponseNotFound(t.render(RequestContext(request, 
{'request_path': request.path})))
 
+
+...@requires_csrf_token
 def server_error(request, template_name='500.html'):
     """
     500 error handler.
@@ -23,6 +30,7 @@
     t = loader.get_template(template_name) # You need to create a 500.html 
template.
     return http.HttpResponseServerError(t.render(Context({})))
 
+
 def shortcut(request, content_type_id, object_id):
     # TODO: Remove this in Django 2.0.
     # This is a legacy view that depends on the contenttypes framework.

Modified: 
django/branches/releases/1.2.X/tests/regressiontests/csrf_tests/tests.py
===================================================================
--- django/branches/releases/1.2.X/tests/regressiontests/csrf_tests/tests.py    
2010-10-28 11:58:30 UTC (rev 14379)
+++ django/branches/releases/1.2.X/tests/regressiontests/csrf_tests/tests.py    
2010-10-28 11:58:51 UTC (rev 14380)
@@ -3,7 +3,7 @@
 from django.test import TestCase
 from django.http import HttpRequest, HttpResponse
 from django.middleware.csrf import CsrfMiddleware, CsrfViewMiddleware
-from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt
+from django.views.decorators.csrf import csrf_exempt, csrf_view_exempt, 
requires_csrf_token
 from django.core.context_processors import csrf
 from django.contrib.sessions.middleware import SessionMiddleware
 from django.utils.importlib import import_module
@@ -322,6 +322,14 @@
         resp = token_view(req)
         self._check_token_present(resp)
 
+    def test_get_token_for_requires_csrf_token_view(self):
+        """
+        Check that get_token works for a view decorated solely with 
requires_csrf_token
+        """
+        req = self._get_GET_csrf_cookie_request()
+        resp = requires_csrf_token(token_view)(req)
+        self._check_token_present(resp)
+
     def test_token_node_with_new_csrf_cookie(self):
         """
         Check that CsrfTokenNode works when a CSRF cookie is created by

Modified: 
django/branches/releases/1.2.X/tests/regressiontests/views/tests/defaults.py
===================================================================
--- 
django/branches/releases/1.2.X/tests/regressiontests/views/tests/defaults.py    
    2010-10-28 11:58:30 UTC (rev 14379)
+++ 
django/branches/releases/1.2.X/tests/regressiontests/views/tests/defaults.py    
    2010-10-28 11:58:51 UTC (rev 14380)
@@ -9,6 +9,8 @@
 class DefaultsTests(TestCase):
     """Test django views in django/views/defaults.py"""
     fixtures = ['testdata.json']
+    non_existing_urls = ['/views/non_existing_url/', # this is in urls.py
+                         '/views/other_non_existing_url/'] # this NOT in 
urls.py
 
     def test_shortcut_with_absolute_url(self):
         "Can view a shortcut for an Author object that has a get_absolute_url 
method"
@@ -49,12 +51,21 @@
 
     def test_page_not_found(self):
         "A 404 status is returned by the page_not_found view"
-        non_existing_urls = ['/views/non_existing_url/', # this is in urls.py
-                             '/views/other_non_existing_url/'] # this NOT in 
urls.py
-        for url in non_existing_urls:
+        for url in self.non_existing_urls:
             response = self.client.get(url)
             self.assertEquals(response.status_code, 404)
 
+    def test_csrf_token_in_404(self):
+        """
+        The 404 page should have the csrf_token available in the context
+        """
+        # See ticket #14565
+        for url in self.non_existing_urls:
+            response = self.client.get(url)
+            csrf_token = response.context['csrf_token']
+            self.assertNotEqual(str(csrf_token), 'NOTPROVIDED')
+            self.assertNotEqual(str(csrf_token), '')
+
     def test_server_error(self):
         "The server_error view raises a 500 status"
         response = self.client.get('/views/server_error/')

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to