Ticket #16199: 16199.4.diff

File 16199.4.diff, 10.0 KB (added by jezdez, 4 years ago)

Updated docs with warning and cleaned up tests slightly

  • 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/topics/http/sessions.txt

    diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt
    index 8529f53..14f1900 100644
    a b defaults to output from ``tempfile.gettempdir()``, most likely ``/tmp``) to 
    9595control where Django stores session files. Be sure to check that your Web
    9696server has permissions to read and write to this location.
    9797
     98Using cookies-based sessions
     99----------------------------
     100
     101.. versionadded:: 1.4
     102
     103To use cookies-based sessions, set the :setting:`SESSION_ENGINE` setting to
     104``"django.contrib.sessions.backends.cookies"``. The session data will be
     105stored using Django's tools for :doc:`cryptographic signing </topics/signing>`
     106and the :setting:`SECRET_KEY` setting. It's recommended to set the
     107:setting:`SESSION_COOKIE_HTTPONLY` to ``True`` to prevent tampering from
     108JavaScript.
     109
     110.. warning::
     111
     112    Before being added to the client-side cookie the session data is
     113    **signed but not encrypted**. In other words, when using the
     114    cookies backend the data stored in the session can be read out but
     115    will be invalidated when being tampered with.
    98116
    99117Using sessions in views
    100118=======================
    Controls where Django stores session data. Valid values are: 
    420438    * ``'django.contrib.sessions.backends.file'``
    421439    * ``'django.contrib.sessions.backends.cache'``
    422440    * ``'django.contrib.sessions.backends.cached_db'``
     441    * ``'django.contrib.sessions.backends.cookies'``
    423442
    424443See `configuring the session engine`_ for more details.
    425444
Back to Top