diff options
| author | Russell Keith-Magee <russell@keith-magee.com> | 2010-11-26 13:30:50 +0000 |
|---|---|---|
| committer | Russell Keith-Magee <russell@keith-magee.com> | 2010-11-26 13:30:50 +0000 |
| commit | 78be884ea788835ad98ad433862a82cf192c3d4f (patch) | |
| tree | 847a8e79b97f45de19f0c288e485a969237b3699 /django | |
| parent | ba21814583e5e3a4fafc4f5f34a26b6acdfb7590 (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.py | 1 | ||||
| -rw-r--r-- | django/contrib/sessions/middleware.py | 3 | ||||
| -rw-r--r-- | django/contrib/sessions/tests.py | 44 | ||||
| -rw-r--r-- | django/http/__init__.py | 42 |
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, |
