summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorRussell Keith-Magee <russell@keith-magee.com>2010-11-26 13:30:50 +0000
committerRussell Keith-Magee <russell@keith-magee.com>2010-11-26 13:30:50 +0000
commit78be884ea788835ad98ad433862a82cf192c3d4f (patch)
tree847a8e79b97f45de19f0c288e485a969237b3699 /django
parentba21814583e5e3a4fafc4f5f34a26b6acdfb7590 (diff)
Fixed #3304 -- Added support for HTTPOnly cookies. Thanks to arvin for the suggestion, and rodolfo for the draft patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@14707 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django')
-rw-r--r--django/conf/global_settings.py1
-rw-r--r--django/contrib/sessions/middleware.py3
-rw-r--r--django/contrib/sessions/tests.py44
-rw-r--r--django/http/__init__.py42
4 files changed, 84 insertions, 6 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 599200ad0b..f23c55d20d 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -421,6 +421,7 @@ SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seco
SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or None for standard domain cookie.
SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only).
SESSION_COOKIE_PATH = '/' # The path of the session cookie.
+SESSION_COOKIE_HTTPONLY = False # Whether to use the non-RFC standard httpOnly flag (IE, FF3+, others)
SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request.
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether a user's session cookie expires when the Web browser is closed.
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data
diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py
index 57fcb9015a..68cb77f7e1 100644
--- a/django/contrib/sessions/middleware.py
+++ b/django/contrib/sessions/middleware.py
@@ -38,5 +38,6 @@ class SessionMiddleware(object):
request.session.session_key, max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
- secure=settings.SESSION_COOKIE_SECURE or None)
+ secure=settings.SESSION_COOKIE_SECURE or None,
+ httponly=settings.SESSION_COOKIE_HTTPONLY or None)
return response
diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py
index 9000714dc4..e8aad0f05f 100644
--- a/django/contrib/sessions/tests.py
+++ b/django/contrib/sessions/tests.py
@@ -11,8 +11,10 @@ from django.contrib.sessions.backends.cached_db import SessionStore as CacheDBSe
from django.contrib.sessions.backends.file import SessionStore as FileSession
from django.contrib.sessions.backends.base import SessionBase
from django.contrib.sessions.models import Session
+from django.contrib.sessions.middleware import SessionMiddleware
from django.core.exceptions import ImproperlyConfigured
-from django.test import TestCase
+from django.http import HttpResponse
+from django.test import TestCase, RequestFactory
from django.utils import unittest
from django.utils.hashcompat import md5_constructor
@@ -320,3 +322,43 @@ class FileSessionTests(SessionTestsMixin, unittest.TestCase):
class CacheSessionTests(SessionTestsMixin, unittest.TestCase):
backend = CacheSession
+
+
+class SessionMiddlewareTests(unittest.TestCase):
+ def setUp(self):
+ self.old_SESSION_COOKIE_SECURE = settings.SESSION_COOKIE_SECURE
+ self.old_SESSION_COOKIE_HTTPONLY = settings.SESSION_COOKIE_HTTPONLY
+
+ def tearDown(self):
+ settings.SESSION_COOKIE_SECURE = self.old_SESSION_COOKIE_SECURE
+ settings.SESSION_COOKIE_HTTPONLY = self.old_SESSION_COOKIE_HTTPONLY
+
+ def test_secure_session_cookie(self):
+ settings.SESSION_COOKIE_SECURE = True
+
+ request = RequestFactory().get('/')
+ response = HttpResponse('Session test')
+ middleware = SessionMiddleware()
+
+ # Simulate a request the modifies the session
+ middleware.process_request(request)
+ request.session['hello'] = 'world'
+
+ # Handle the response through the middleware
+ response = middleware.process_response(request, response)
+ self.assertTrue(response.cookies[settings.SESSION_COOKIE_NAME]['secure'])
+
+ def test_httponly_session_cookie(self):
+ settings.SESSION_COOKIE_HTTPONLY = True
+
+ request = RequestFactory().get('/')
+ response = HttpResponse('Session test')
+ middleware = SessionMiddleware()
+
+ # Simulate a request the modifies the session
+ middleware.process_request(request)
+ request.session['hello'] = 'world'
+
+ # Handle the response through the middleware
+ response = middleware.process_response(request, response)
+ self.assertTrue(response.cookies[settings.SESSION_COOKIE_NAME]['httponly'])
diff --git a/django/http/__init__.py b/django/http/__init__.py
index b40558544e..42027f0beb 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -2,7 +2,6 @@ import datetime
import os
import re
import time
-from Cookie import BaseCookie, SimpleCookie, CookieError
from pprint import pformat
from urllib import urlencode
from urlparse import urljoin
@@ -22,6 +21,39 @@ except ImportError:
# PendingDeprecationWarning
from cgi import parse_qsl
+# httponly support exists in Python 2.6's Cookie library,
+# but not in Python 2.4 or 2.5.
+import Cookie
+if Cookie.Morsel._reserved.has_key('httponly'):
+ SimpleCookie = Cookie.SimpleCookie
+else:
+ class Morsel(Cookie.Morsel):
+ def __setitem__(self, K, V):
+ K = K.lower()
+ if K == "httponly":
+ if V:
+ # The superclass rejects httponly as a key,
+ # so we jump to the grandparent.
+ super(Cookie.Morsel, self).__setitem__(K, V)
+ else:
+ super(Morsel, self).__setitem__(K, V)
+
+ def OutputString(self, attrs=None):
+ output = super(Morsel, self).OutputString(attrs)
+ if "httponly" in self:
+ output += "; httponly"
+ return output
+
+ class SimpleCookie(Cookie.SimpleCookie):
+ def __set(self, key, real_value, coded_value):
+ M = self.get(key, Morsel())
+ M.set(key, real_value, coded_value)
+ dict.__setitem__(self, key, M)
+
+ def __setitem__(self, key, value):
+ rval, cval = self.value_encode(value)
+ self.__set(key, rval, cval)
+
from django.utils.datastructures import MultiValueDict, ImmutableList
from django.utils.encoding import smart_str, iri_to_uri, force_unicode
from django.utils.http import cookie_date
@@ -369,11 +401,11 @@ class CompatCookie(SimpleCookie):
def parse_cookie(cookie):
if cookie == '':
return {}
- if not isinstance(cookie, BaseCookie):
+ if not isinstance(cookie, Cookie.BaseCookie):
try:
c = CompatCookie()
c.load(cookie)
- except CookieError:
+ except Cookie.CookieError:
# Invalid cookie
return {}
else:
@@ -462,7 +494,7 @@ class HttpResponse(object):
return self._headers.get(header.lower(), (None, alternate))[1]
def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
- domain=None, secure=False):
+ domain=None, secure=False, httponly=False):
"""
Sets a cookie.
@@ -495,6 +527,8 @@ class HttpResponse(object):
self.cookies[key]['domain'] = domain
if secure:
self.cookies[key]['secure'] = True
+ if httponly:
+ self.cookies[key]['httponly'] = True
def delete_cookie(self, key, path='/', domain=None):
self.set_cookie(key, max_age=0, path=path, domain=domain,