From 84db1ae3c1643fb92a69b985657f3f1dcc1afec5 Mon Sep 17 00:00:00 2001 From: Jody McIntyre Date: Tue, 5 Jun 2012 17:42:41 -0400 Subject: [PATCH 1/7] Fix test broken by Django 1.4. --- session_csrf/tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/session_csrf/tests.py b/session_csrf/tests.py index 7c11a4e..70a45b6 100644 --- a/session_csrf/tests.py +++ b/session_csrf/tests.py @@ -2,6 +2,7 @@ from django import http from django.conf.urls.defaults import patterns from django.contrib.auth import logout +from django.contrib.auth.middleware import AuthenticationMiddleware from django.contrib.auth.models import User from django.contrib.sessions.models import Session from django.core import signals @@ -92,6 +93,8 @@ def test_anon_token_from_cookie(self): } # Hack to set up request middleware. ClientHandler()(self.rf._base_environ(**r)) + auth_mw = AuthenticationMiddleware() + auth_mw.process_request(request) self.mw.process_request(request) self.assertEqual(request.csrf_token, 'woo') From 65c4e307c538c367babfdc79d683dd880f4a50e4 Mon Sep 17 00:00:00 2001 From: Jody McIntyre Date: Tue, 5 Jun 2012 17:46:28 -0400 Subject: [PATCH 2/7] Fix test failures when CSRF_FAILURE_VIEW is set to something that redirects. --- session_csrf/tests.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/session_csrf/tests.py b/session_csrf/tests.py index 70a45b6..1c748fb 100644 --- a/session_csrf/tests.py +++ b/session_csrf/tests.py @@ -1,5 +1,6 @@ import django.test from django import http +from django.conf import settings from django.conf.urls.defaults import patterns from django.contrib.auth import logout from django.contrib.auth.middleware import AuthenticationMiddleware @@ -78,6 +79,11 @@ def setUp(self): self.token = 'a' * 32 self.rf = django.test.RequestFactory() self.mw = CsrfMiddleware() + self.save_CSRF_FAILURE_VIEW = settings.CSRF_FAILURE_VIEW + settings.CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure' + + def tearDown(self): + settings.CSRF_FAILURE_VIEW = self.save_CSRF_FAILURE_VIEW def process_view(self, request, view=None): return self.mw.process_view(request, view, None, None) @@ -178,9 +184,12 @@ def setUp(self): self.client.handler = ClientHandler(enforce_csrf_checks=True) self.save_ANON_ALWAYS = session_csrf.ANON_ALWAYS session_csrf.ANON_ALWAYS = False + self.save_CSRF_FAILURE_VIEW = settings.CSRF_FAILURE_VIEW + settings.CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure' def tearDown(self): session_csrf.ANON_ALWAYS = self.save_ANON_ALWAYS + settings.CSRF_FAILURE_VIEW = self.save_CSRF_FAILURE_VIEW def login(self): assert self.client.login(username='jbalogh', password='password') @@ -275,9 +284,12 @@ def setUp(self): self.client.handler = ClientHandler(enforce_csrf_checks=True) self.save_ANON_ALWAYS = session_csrf.ANON_ALWAYS session_csrf.ANON_ALWAYS = True + self.save_CSRF_FAILURE_VIEW = settings.CSRF_FAILURE_VIEW + settings.CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure' def tearDown(self): session_csrf.ANON_ALWAYS = self.save_ANON_ALWAYS + settings.CSRF_FAILURE_VIEW = self.save_CSRF_FAILURE_VIEW def login(self): assert self.client.login(username='jbalogh', password='password') From c23330a84660aae218864b66e84699fab4b923ab Mon Sep 17 00:00:00 2001 From: Jody McIntyre Date: Wed, 6 Jun 2012 10:24:51 -0400 Subject: [PATCH 3/7] Re-add urls attribute to TestCsrfToken tests. This is needed to ensure that / is a simple view that doesn't have side effects that break the tests. session-csrf's tests should be independent of the Django project it's included in. --- session_csrf/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/session_csrf/tests.py b/session_csrf/tests.py index 1c748fb..8579027 100644 --- a/session_csrf/tests.py +++ b/session_csrf/tests.py @@ -28,6 +28,7 @@ class TestCsrfToken(django.test.TestCase): + urls = 'session_csrf.tests' def setUp(self): self.client.handler = ClientHandler() From 8b636bc8701587022acd3ce871c87850819c3436 Mon Sep 17 00:00:00 2001 From: Jody McIntyre Date: Fri, 8 Jun 2012 14:57:15 -0400 Subject: [PATCH 4/7] Use session cookie for anonymous CSRF cookie. This fixes an issue with IE 7 and 8 not accepting the cookie if the client's date is set wrong. Session cookies (cookies that have no set expiry date and therefore expire at the end of the browser session) are already used by Django for storing the SESSION_COOKIE (sessionid by default). --- session_csrf/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/session_csrf/__init__.py b/session_csrf/__init__.py index 6b7aa53..651fa2a 100644 --- a/session_csrf/__init__.py +++ b/session_csrf/__init__.py @@ -104,8 +104,7 @@ def process_response(self, request, response): if hasattr(request, '_anon_csrf_key'): # Set or reset the cache and cookie timeouts. response.set_cookie(ANON_COOKIE, request._anon_csrf_key, - max_age=ANON_TIMEOUT, httponly=True, - secure=request.is_secure()) + httponly=True, secure=request.is_secure()) patch_vary_headers(response, ['Cookie']) return response @@ -127,8 +126,8 @@ def wrapper(request, *args, **kw): response = f(request, *args, **kw) if use_anon_cookie: # Set or reset the cache and cookie timeouts. - response.set_cookie(ANON_COOKIE, key, max_age=ANON_TIMEOUT, - httponly=True, secure=request.is_secure()) + response.set_cookie(ANON_COOKIE, key, httponly=True, + secure=request.is_secure()) patch_vary_headers(response, ['Cookie']) return response return wrapper From 07458282b2e40d05d7cc248f9e16af489c4510cc Mon Sep 17 00:00:00 2001 From: Jody McIntyre Date: Fri, 8 Jun 2012 19:02:49 -0400 Subject: [PATCH 5/7] Merge @anonymous_csrf and ANON_ALWAYS functionality This requires moving the common code into process_view so we can tell if the view has been decorated. --- session_csrf/__init__.py | 52 +++++++++++++++------------------------- session_csrf/tests.py | 2 +- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/session_csrf/__init__.py b/session_csrf/__init__.py index 651fa2a..7a8c1a3 100644 --- a/session_csrf/__init__.py +++ b/session_csrf/__init__.py @@ -46,19 +46,7 @@ def process_request(self, request): else: request.csrf_token = request.session['csrf_token'] else: - key = None - token = '' - if ANON_COOKIE in request.COOKIES: - key = request.COOKIES[ANON_COOKIE] - token = cache.get(PREFIX + key, '') - if ANON_ALWAYS: - if not key: - key = django_csrf._get_new_csrf_key() - if not token: - token = django_csrf._get_new_csrf_key() - request._anon_csrf_key = key - cache.set(PREFIX + key, token, ANON_TIMEOUT) - request.csrf_token = token + request.csrf_token = '' # to be filled in later if applicable def process_view(self, request, view_func, args, kwargs): """Check the CSRF token if this is a POST.""" @@ -73,6 +61,22 @@ def process_view(self, request, view_func, args, kwargs): and not request.user.is_authenticated()): return + if hasattr(request, 'user') and not request.user.is_authenticated(): + if ANON_ALWAYS or getattr(view_func, 'anonymous_csrf', False): + key = None + token = '' + if ANON_COOKIE in request.COOKIES: + key = request.COOKIES[ANON_COOKIE] + token = cache.get(PREFIX + key, '') + if not key: + key = django_csrf._get_new_csrf_key() + if not token: + token = django_csrf._get_new_csrf_key() + + request._anon_csrf_key = key + cache.set(PREFIX + key, token, ANON_TIMEOUT) + request.csrf_token = token + # Bail if this is a safe method. if request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'): return self._accept(request) @@ -111,26 +115,8 @@ def process_response(self, request, response): def anonymous_csrf(f): """Decorator that assigns a CSRF token to an anonymous user.""" - @functools.wraps(f) - def wrapper(request, *args, **kw): - use_anon_cookie = not (request.user.is_authenticated() or ANON_ALWAYS) - if use_anon_cookie: - if ANON_COOKIE in request.COOKIES: - key = request.COOKIES[ANON_COOKIE] - token = cache.get(PREFIX + key) or django_csrf._get_new_csrf_key() - else: - key = django_csrf._get_new_csrf_key() - token = django_csrf._get_new_csrf_key() - cache.set(PREFIX + key, token, ANON_TIMEOUT) - request.csrf_token = token - response = f(request, *args, **kw) - if use_anon_cookie: - # Set or reset the cache and cookie timeouts. - response.set_cookie(ANON_COOKIE, key, httponly=True, - secure=request.is_secure()) - patch_vary_headers(response, ['Cookie']) - return response - return wrapper + f.anonymous_csrf = True + return f def anonymous_csrf_exempt(f): diff --git a/session_csrf/tests.py b/session_csrf/tests.py index 8579027..8603390 100644 --- a/session_csrf/tests.py +++ b/session_csrf/tests.py @@ -102,7 +102,7 @@ def test_anon_token_from_cookie(self): ClientHandler()(self.rf._base_environ(**r)) auth_mw = AuthenticationMiddleware() auth_mw.process_request(request) - self.mw.process_request(request) + self.mw.process_view(request, lambda: None, [], {}) self.assertEqual(request.csrf_token, 'woo') def test_set_csrftoken_once(self): From a02ba0495e7edb25bebaece6fb2531643bd5b2fa Mon Sep 17 00:00:00 2001 From: Jody McIntyre Date: Fri, 29 Jun 2012 16:22:20 -0400 Subject: [PATCH 6/7] Unset ANON_ALWAYS during TestCsrfMiddleware tests. This caused test_anon_token_from_cookie to pass when it shouldn't --- session_csrf/tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/session_csrf/tests.py b/session_csrf/tests.py index 8603390..72e28e8 100644 --- a/session_csrf/tests.py +++ b/session_csrf/tests.py @@ -80,10 +80,13 @@ def setUp(self): self.token = 'a' * 32 self.rf = django.test.RequestFactory() self.mw = CsrfMiddleware() + self.save_ANON_ALWAYS = session_csrf.ANON_ALWAYS + session_csrf.ANON_ALWAYS = False self.save_CSRF_FAILURE_VIEW = settings.CSRF_FAILURE_VIEW settings.CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure' def tearDown(self): + session_csrf.ANON_ALWAYS = self.save_ANON_ALWAYS settings.CSRF_FAILURE_VIEW = self.save_CSRF_FAILURE_VIEW def process_view(self, request, view=None): From 452522ad0e5b35235aab2b82d034280e5f33c0a2 Mon Sep 17 00:00:00 2001 From: Jody McIntyre Date: Fri, 29 Jun 2012 16:24:05 -0400 Subject: [PATCH 7/7] Fix test_anon_token_from_cookie. The view needs to be decorated with anonymous_csrf before calling process_view. --- session_csrf/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/session_csrf/tests.py b/session_csrf/tests.py index 72e28e8..98cfcd4 100644 --- a/session_csrf/tests.py +++ b/session_csrf/tests.py @@ -105,7 +105,7 @@ def test_anon_token_from_cookie(self): ClientHandler()(self.rf._base_environ(**r)) auth_mw = AuthenticationMiddleware() auth_mw.process_request(request) - self.mw.process_view(request, lambda: None, [], {}) + self.mw.process_view(request, anonymous_csrf(lambda: None), [], {}) self.assertEqual(request.csrf_token, 'woo') def test_set_csrftoken_once(self):