Ticket #16199: 16199.6.diff

File 16199.6.diff, 12.0 KB (added by Jannis Leidel, 13 years ago)

release notes

  • new file django/contrib/sessions/backends/cookies.py

    diff --git a/django/contrib/sessions/backends/cookies.py b/django/contrib/sessions/backends/cookies.py
    new file mode 100644
    index 0000000..bff221b
    - +  
     1import zlib
     2try:
     3    import cPickle as pickle
     4except ImportError:
     5    import pickle
     6
     7from django.conf import settings
     8from django.core import signing
     9from django.utils.encoding import smart_str
     10
     11from django.contrib.sessions.backends.base import SessionBase
     12
     13
     14class SessionStore(SessionBase):
     15    salt = 'django.contrib.sessions.backends.cookies'
     16
     17    def load(self):
     18        """
     19        We load the data from the key itself instead of fetching from
     20        some external data store. Opposite of _get_session_key(),
     21        raises BadSignature if signature fails.
     22        """
     23        signer = signing.TimestampSigner(salt=self.salt)
     24        try:
     25            base64d = signer.unsign(
     26                self._session_key, max_age=settings.SESSION_COOKIE_AGE)
     27            pickled = signing.b64_decode(smart_str(base64d))
     28            return pickle.loads(zlib.decompress(pickled))
     29        except (signing.BadSignature, ValueError):
     30            self.create()
     31            return {}
     32
     33    def create(self):
     34        """
     35        To create a new key, we simply make sure that the modified flag is set
     36        so that the cookie is set on the client for the current request.
     37        """
     38        self.modified = True
     39
     40    def save(self):
     41        """
     42        To save, we get the session key as a securely signed string and then
     43        set the modified flag so that the cookie is set on the client for the
     44        current request.
     45        """
     46        self._session_key = self._get_session_key()
     47        self.modified = True
     48
     49    def exists(self, session_key=None):
     50        """
     51        This method makes sense when you're talking to a shared resource, but
     52        it doesn't matter when you're storing the information in the client's
     53        cookie.
     54        """
     55        return False
     56
     57    def delete(self, session_key=None):
     58        """
     59        To delete, we clear the session key and the underlying data structure
     60        and set the modified flag so that the cookie is set on the client for
     61        the current request.
     62        """
     63        self._session_key = ''
     64        self._session_cache = {}
     65        self.modified = True
     66
     67    def cycle_key(self):
     68        """
     69        Keeps the same data but with a new key.  To do this, we just have to
     70        call ``save()`` and it will automatically save a cookie with a new key
     71        at the end of the request.
     72        """
     73        self.save()
     74
     75    def _get_session_key(self):
     76        """
     77        Most session backends don't need to override this method, but we do,
     78        because instead of generating a random string, we want to actually
     79        generate a secure url-safe Base64-encoded string of data as our
     80        session key.
     81        """
     82        payload = getattr(self, '_session_cache', {})
     83        pickled = pickle.dumps(payload, pickle.HIGHEST_PROTOCOL)
     84        base64d = signing.b64_encode(zlib.compress(pickled))
     85        return signing.TimestampSigner(salt=self.salt).sign(base64d)
     86
     87    def _set_session_key(self, session_key):
     88        self._session_key = session_key
     89
     90    session_key = property(_get_session_key, _set_session_key)
  • django/contrib/sessions/tests.py

    diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py
    index 2eb43f3..0c34101 100644
    a b from django.contrib.sessions.backends.db import SessionStore as DatabaseSession  
    77from django.contrib.sessions.backends.cache import SessionStore as CacheSession
    88from django.contrib.sessions.backends.cached_db import SessionStore as CacheDBSession
    99from django.contrib.sessions.backends.file import SessionStore as FileSession
     10from django.contrib.sessions.backends.cookies import SessionStore as CookieSession
    1011from django.contrib.sessions.models import Session
    1112from django.contrib.sessions.middleware import SessionMiddleware
    1213from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
    1314from django.http import HttpResponse
    1415from django.test import TestCase, RequestFactory
     16from django.test.utils import override_settings
    1517from django.utils import unittest
    1618
    1719
    class SessionTestsMixin(object):  
    214216        # Tests get_expire_at_browser_close with different settings and different
    215217        # set_expiry calls
    216218        try:
    217             try:
    218                 original_expire_at_browser_close = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
    219                 settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = False
    220 
     219            with override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE=False):
    221220                self.session.set_expiry(10)
    222221                self.assertFalse(self.session.get_expire_at_browser_close())
    223222
    class SessionTestsMixin(object):  
    227226                self.session.set_expiry(None)
    228227                self.assertFalse(self.session.get_expire_at_browser_close())
    229228
    230                 settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = True
    231 
     229            with override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE=True):
    232230                self.session.set_expiry(10)
    233231                self.assertFalse(self.session.get_expire_at_browser_close())
    234232
    class SessionTestsMixin(object):  
    237235
    238236                self.session.set_expiry(None)
    239237                self.assertTrue(self.session.get_expire_at_browser_close())
    240 
    241             except:
    242                 raise
    243         finally:
    244             settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = original_expire_at_browser_close
     238        except:
     239            raise
    245240
    246241    def test_decode(self):
    247242        # Ensure we can decode what we encode
    class FileSessionTests(SessionTestsMixin, unittest.TestCase):  
    302297        shutil.rmtree(self.temp_session_store)
    303298        super(FileSessionTests, self).tearDown()
    304299
     300    @override_settings(
     301        SESSION_FILE_PATH="/if/this/directory/exists/you/have/a/weird/computer")
    305302    def test_configuration_check(self):
    306303        # Make sure the file backend checks for a good storage dir
    307         settings.SESSION_FILE_PATH = "/if/this/directory/exists/you/have/a/weird/computer"
    308304        self.assertRaises(ImproperlyConfigured, self.backend)
    309305
    310306    def test_invalid_key_backslash(self):
    class CacheSessionTests(SessionTestsMixin, unittest.TestCase):  
    324320
    325321
    326322class SessionMiddlewareTests(unittest.TestCase):
    327     def setUp(self):
    328         self.old_SESSION_COOKIE_SECURE = settings.SESSION_COOKIE_SECURE
    329         self.old_SESSION_COOKIE_HTTPONLY = settings.SESSION_COOKIE_HTTPONLY
    330 
    331     def tearDown(self):
    332         settings.SESSION_COOKIE_SECURE = self.old_SESSION_COOKIE_SECURE
    333         settings.SESSION_COOKIE_HTTPONLY = self.old_SESSION_COOKIE_HTTPONLY
    334323
     324    @override_settings(SESSION_COOKIE_SECURE=True)
    335325    def test_secure_session_cookie(self):
    336         settings.SESSION_COOKIE_SECURE = True
    337 
    338326        request = RequestFactory().get('/')
    339327        response = HttpResponse('Session test')
    340328        middleware = SessionMiddleware()
    class SessionMiddlewareTests(unittest.TestCase):  
    347335        response = middleware.process_response(request, response)
    348336        self.assertTrue(response.cookies[settings.SESSION_COOKIE_NAME]['secure'])
    349337
     338    @override_settings(SESSION_COOKIE_HTTPONLY=True)
    350339    def test_httponly_session_cookie(self):
    351         settings.SESSION_COOKIE_HTTPONLY = True
    352 
    353340        request = RequestFactory().get('/')
    354341        response = HttpResponse('Session test')
    355342        middleware = SessionMiddleware()
    class SessionMiddlewareTests(unittest.TestCase):  
    361348        # Handle the response through the middleware
    362349        response = middleware.process_response(request, response)
    363350        self.assertTrue(response.cookies[settings.SESSION_COOKIE_NAME]['httponly'])
     351
     352
     353class CookieSessionTests(SessionTestsMixin, TestCase):
     354
     355    backend = CookieSession
     356
     357    def test_save(self):
     358        """
     359        This test tested exists() in the other session backends, but that
     360        doesn't make sense for us.
     361        """
     362        pass
  • django/core/signing.py

    diff --git a/django/core/signing.py b/django/core/signing.py
    index 3165cf8..224d942 100644
    a b class TimestampSigner(Signer):  
    161161    def __init__(self, *args, **kwargs):
    162162        self.time_func = kwargs.pop('time', time.time)
    163163        super(TimestampSigner, self).__init__(*args, **kwargs)
    164    
     164
    165165    def timestamp(self):
    166166        return baseconv.base62.encode(int(self.time_func() * 10000))
    167167
  • docs/releases/1.4.txt

    diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
    index e7e8f45..10b8b8a 100644
    a b signing in Web applications.  
    8989
    9090See :doc:`cryptographic signing </topics/signing>` docs for more information.
    9191
     92Cookie-based session backend
     93~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     94
     95Django 1.4 introduces a new cookie based backend for the session framework
     96which uses the tools for :doc:`cryptographic signing </topics/signing>` to
     97store the session data in the client's browser.
     98
     99See the :ref:`cookie-based backend <cookie-session-backend>` docs for
     100more information.
     101
    92102New form wizard
    93103~~~~~~~~~~~~~~~
    94104
  • docs/topics/http/sessions.txt

    diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt
    index 8529f53..68f806a 100644
    a b How to use sessions  
    55.. module:: django.contrib.sessions
    66   :synopsis: Provides session management for Django projects.
    77
    8 Django provides full support for anonymous sessions. The session framework lets
    9 you store and retrieve arbitrary data on a per-site-visitor basis. It stores
    10 data on the server side and abstracts the sending and receiving of cookies.
    11 Cookies contain a session ID -- not the data itself.
     8Django provides full support for anonymous sessions. The session framework
     9lets you store and retrieve arbitrary data on a per-site-visitor basis. It
     10stores data on the server side and abstracts the sending and receiving of
     11cookies. Cookies contain a session ID -- not the data itself (unless you're
     12using the :ref:`cookie based backend<cookie-session-backend>`).
    1213
    1314Enabling sessions
    1415=================
    defaults to output from ``tempfile.gettempdir()``, most likely ``/tmp``) to  
    9596control where Django stores session files. Be sure to check that your Web
    9697server has permissions to read and write to this location.
    9798
     99.. _cookie-session-backend:
     100
     101Using cookie-based sessions
     102---------------------------
     103
     104.. versionadded:: 1.4
     105
     106To use cookies-based sessions, set the :setting:`SESSION_ENGINE` setting to
     107``"django.contrib.sessions.backends.cookies"``. The session data will be
     108stored using Django's tools for :doc:`cryptographic signing </topics/signing>`
     109and the :setting:`SECRET_KEY` setting. It's recommended to set the
     110:setting:`SESSION_COOKIE_HTTPONLY` to ``True`` to prevent tampering from
     111JavaScript.
     112
     113.. warning::
     114
     115    The session data is **signed but not encrypted**!
     116
     117    When using the cookies backend the session data can be read out
     118    and will be invalidated when being tampered with. The same invalidation
     119    happens if the client storing the cookie (e.g. your user's browser)
     120    can't store all of the session cookie and drops data. Even though
     121    Django compresses the data before it's still entirely possible to
     122    exceed the `common limit of 4096 bytes`_ per cookie.
     123
     124    Also, the size of a cookie can have an impact on the `speed of your site`_.
     125
     126.. _`common limit of 4096 bytes`: http://tools.ietf.org/html/rfc2965#section-5.3
     127.. _`speed of your site`: http://yuiblog.com/blog/2007/03/01/performance-research-part-3/
    98128
    99129Using sessions in views
    100130=======================
    Controls where Django stores session data. Valid values are:  
    420450    * ``'django.contrib.sessions.backends.file'``
    421451    * ``'django.contrib.sessions.backends.cache'``
    422452    * ``'django.contrib.sessions.backends.cached_db'``
     453    * ``'django.contrib.sessions.backends.cookies'``
    423454
    424455See `configuring the session engine`_ for more details.
    425456
Back to Top