Permalink
Browse files

Fixed #3304 -- Added support for HTTPOnly cookies. Thanks to arvin fo…

…r the suggestion, and rodolfo for the draft patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14707 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
freakboy3742 committed Nov 26, 2010
1 parent ba21814 commit 78be884ea788835ad98ad433862a82cf192c3d4f
@@ -421,6 +421,7 @@
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
@@ -38,5 +38,6 @@ def process_response(self, request, response):
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
@@ -11,8 +11,10 @@
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 @@ def test_configuration_check(self):
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'])
@@ -2,7 +2,6 @@
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 @@
# 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 @@ def value_encode(self, val):
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 @@ def get(self, header, alternate):
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 @@ def set_cookie(self, key, value='', max_age=None, expires=None, path='/',
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,
@@ -566,7 +566,13 @@ Methods
Returns ``True`` or ``False`` based on a case-insensitive check for a
header with the given name.
.. method:: HttpResponse.set_cookie(key, value='', max_age=None, expires=None, path='/', domain=None, secure=None)
.. method:: HttpResponse.set_cookie(key, value='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=False)
.. versionchanged:: 1.3
The possibility of specifying a ``datetime.datetime`` object in
``expires``, and the auto-calculation of ``max_age`` in such case
was added. The ``httponly`` argument was also added.
Sets a cookie. The parameters are the same as in the `cookie Morsel`_
object in the Python standard library.
@@ -583,14 +589,18 @@ Methods
the domains www.lawrence.com, blogs.lawrence.com and
calendars.lawrence.com. Otherwise, a cookie will only be readable by
the domain that set it.
* Use ``http_only=True`` if you want to prevent client-side
JavaScript from having access to the cookie.
.. _`cookie Morsel`: http://docs.python.org/library/cookie.html#Cookie.Morsel
HTTPOnly_ is a flag included in a Set-Cookie HTTP response
header. It is not part of the RFC2109 standard for cookies,
and it isn't honored consistently by all browsers. However,
when it is honored, it can be a useful way to mitigate the
risk of client side script accessing the protected cookie
data.
.. versionchanged:: 1.3
Both the possibility of specifying a ``datetime.datetime`` object in
``expires`` and the auto-calculation of ``max_age`` in such case were added
in Django 1.3.
.. _`cookie Morsel`: http://docs.python.org/library/cookie.html#Cookie.Morsel
.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly
.. method:: HttpResponse.delete_cookie(key, path='/', domain=None)
@@ -1392,6 +1392,25 @@ The domain to use for session cookies. Set this to a string such as
``".lawrence.com"`` for cross-domain cookies, or use ``None`` for a standard
domain cookie. See the :doc:`/topics/http/sessions`.
.. setting:: SESSION_COOKIE_HTTPONLY
SESSION_COOKIE_HTTPONLY
-----------------------
Default: ``False``
Whether to use HTTPOnly flag on the session cookie. If this is set to
``True``, client-side JavaScript will not to be able to access the
session cookie.
HTTPOnly_ is a flag included in a Set-Cookie HTTP response header. It
is not part of the RFC2109 standard for cookies, and it isn't honored
consistently by all browsers. However, when it is honored, it can be a
useful way to mitigate the risk of client side script accessing the
protected cookie data.
.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly
.. setting:: SESSION_COOKIE_NAME
SESSION_COOKIE_NAME
@@ -161,6 +161,10 @@ requests. These include:
* Support for lookups spanning relations in admin's ``list_filter``.
* Support for _HTTPOnly cookies.
.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly
.. _backwards-incompatible-changes-1.3:
Backwards-incompatible changes in 1.3
@@ -457,6 +457,23 @@ The domain to use for session cookies. Set this to a string such as
``".lawrence.com"`` (note the leading dot!) for cross-domain cookies, or use
``None`` for a standard domain cookie.
SESSION_COOKIE_HTTPONLY
-----------------------
Default: ``False``
Whether to use HTTPOnly flag on the session cookie. If this is set to
``True``, client-side JavaScript will not to be able to access the
session cookie.
HTTPOnly_ is a flag included in a Set-Cookie HTTP response header. It
is not part of the RFC2109 standard for cookies, and it isn't honored
consistently by all browsers. However, when it is honored, it can be a
useful way to mitigate the risk of client side script accessing the
protected cookie data.
.. _HTTPOnly: http://www.owasp.org/index.php/HTTPOnly
SESSION_COOKIE_NAME
-------------------
@@ -89,6 +89,15 @@ def test_max_age_expiration(self):
self.assertEqual(max_age_cookie['max-age'], 10)
self.assertEqual(max_age_cookie['expires'], cookie_date(time.time()+10))
def test_httponly_cookie(self):
response = HttpResponse()
response.set_cookie('example', httponly=True)
example_cookie = response.cookies['example']
# A compat cookie may be in use -- check that it has worked
# both as an output string, and using the cookie attributes
self.assertTrue('; httponly' in str(example_cookie))
self.assertTrue(example_cookie['httponly'])
def test_limited_stream(self):
# Read all of a limited stream
stream = LimitedStream(StringIO('test'), 2)

0 comments on commit 78be884

Please sign in to comment.