From 8cbcf1d3a60a0ba1a6f3ddde9317ac07b67c6c5d Mon Sep 17 00:00:00 2001
From: Luke Plant <L.Plant.98@cantab.net>
Date: Mon, 9 May 2011 23:00:22 +0000
Subject: [PATCH] Fixed #14134 - ability to set cookie 'path' and 'secure'
 attributes of CSRF cookie

Thanks to cfattarsi for the report and initial patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16200 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 django/conf/global_settings.py            |  4 +++-
 django/middleware/csrf.py                 |  8 +++++--
 docs/ref/contrib/csrf.txt                 | 25 +++++++++++++++++++
 docs/ref/settings.txt                     | 29 +++++++++++++++++++++++
 tests/regressiontests/csrf_tests/tests.py | 18 ++++++++++----
 5 files changed, 76 insertions(+), 8 deletions(-)

diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 477f13b675..88aa5a3e70 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -484,9 +484,11 @@ PASSWORD_RESET_TIMEOUT_DAYS = 3
 # rejected by the CSRF middleware.
 CSRF_FAILURE_VIEW = 'django.views.csrf.csrf_failure'
 
-# Name and domain for CSRF cookie.
+# Settings for CSRF cookie.
 CSRF_COOKIE_NAME = 'csrftoken'
 CSRF_COOKIE_DOMAIN = None
+CSRF_COOKIE_PATH = '/'
+CSRF_COOKIE_SECURE = False
 
 ############
 # MESSAGES #
diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py
index 2f36f18e6c..e4cab6c08f 100644
--- a/django/middleware/csrf.py
+++ b/django/middleware/csrf.py
@@ -197,8 +197,12 @@ class CsrfViewMiddleware(object):
 
         # Set the CSRF cookie even if it's already set, so we renew the expiry timer.
         response.set_cookie(settings.CSRF_COOKIE_NAME,
-                request.META["CSRF_COOKIE"], max_age = 60 * 60 * 24 * 7 * 52,
-                domain=settings.CSRF_COOKIE_DOMAIN)
+                            request.META["CSRF_COOKIE"],
+                            max_age = 60 * 60 * 24 * 7 * 52,
+                            domain=settings.CSRF_COOKIE_DOMAIN,
+                            path=settings.CSRF_COOKIE_PATH,
+                            secure=settings.CSRF_COOKIE_SECURE
+                            )
         # Content varies with the CSRF cookie, so set the Vary header.
         patch_vary_headers(response, ('Cookie',))
         response.csrf_processing_done = True
diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt
index 88e9523719..b42dc26fbd 100644
--- a/docs/ref/contrib/csrf.txt
+++ b/docs/ref/contrib/csrf.txt
@@ -423,6 +423,31 @@ Default: ``'csrftoken'``
 The name of the cookie to use for the CSRF authentication token. This can be
 whatever you want.
 
+CSRF_COOKIE_PATH
+----------------
+
+.. versionadded:: 1.4
+
+Default: ``'/'``
+
+The path set on the CSRF cookie. This should either match the URL path of your
+Django installation or be a parent of that path.
+
+This is useful if you have multiple Django instances running under the same
+hostname. They can use different cookie paths, and each instance will only see
+its own CSRF cookie.
+
+CSRF_COOKIE_SECURE
+------------------
+
+.. versionadded:: 1.4
+
+Default: ``False``
+
+Whether to use a secure cookie for the CSRF cookie. If this is set to ``True``,
+the cookie will be marked as "secure," which means browsers may ensure that the
+cookie is only sent under an HTTPS connection.
+
 CSRF_FAILURE_VIEW
 -----------------
 
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index f8a5e0f640..f5f1226f21 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -340,6 +340,35 @@ Default: ``'csrftoken'``
 The name of the cookie to use for the CSRF authentication token. This can be whatever you
 want.  See :doc:`/ref/contrib/csrf`.
 
+.. setting:: CSRF_COOKIE_PATH
+
+CSRF_COOKIE_PATH
+----------------
+
+.. versionadded:: 1.4
+
+Default: ``'/'``
+
+The path set on the CSRF cookie. This should either match the URL path of your
+Django installation or be a parent of that path.
+
+This is useful if you have multiple Django instances running under the same
+hostname. They can use different cookie paths, and each instance will only see
+its own CSRF cookie.
+
+.. setting:: CSRF_COOKIE_SECURE
+
+CSRF_COOKIE_SECURE
+------------------
+
+.. versionadded:: 1.4
+
+Default: ``False``
+
+Whether to use a secure cookie for the CSRF cookie. If this is set to ``True``,
+the cookie will be marked as "secure," which means browsers may ensure that the
+cookie is only sent under an HTTPS connection.
+
 .. setting:: CSRF_FAILURE_VIEW
 
 CSRF_FAILURE_VIEW
diff --git a/tests/regressiontests/csrf_tests/tests.py b/tests/regressiontests/csrf_tests/tests.py
index 1285c1ddac..8dc3fbbc1b 100644
--- a/tests/regressiontests/csrf_tests/tests.py
+++ b/tests/regressiontests/csrf_tests/tests.py
@@ -82,13 +82,21 @@ class CsrfViewMiddlewareTest(TestCase):
         patched.
         """
         req = self._get_GET_no_csrf_cookie_request()
-        # token_view calls get_token() indirectly
-        CsrfViewMiddleware().process_view(req, token_view, (), {})
-        resp = token_view(req)
-        resp2 = CsrfViewMiddleware().process_response(req, resp)
 
-        csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
+        # Put tests for CSRF_COOKIE_* settings here
+        with self.settings(CSRF_COOKIE_NAME='myname',
+                           CSRF_COOKIE_DOMAIN='.example.com',
+                           CSRF_COOKIE_PATH='/test/',
+                           CSRF_COOKIE_SECURE=True):
+            # token_view calls get_token() indirectly
+            CsrfViewMiddleware().process_view(req, token_view, (), {})
+            resp = token_view(req)
+            resp2 = CsrfViewMiddleware().process_response(req, resp)
+        csrf_cookie = resp2.cookies.get('myname', False)
         self.assertNotEqual(csrf_cookie, False)
+        self.assertEqual(csrf_cookie['domain'], '.example.com')
+        self.assertEqual(csrf_cookie['secure'], True)
+        self.assertEqual(csrf_cookie['path'], '/test/')
         self.assertTrue('Cookie' in resp2.get('Vary',''))
 
     def test_process_response_get_token_not_used(self):