Opened 14 years ago

Last modified 13 years ago

#14579 closed

built-in sessions middleware can't be used for entirely cookie-based sessions — at Version 1

Reported by: oran Owned by: nobody
Component: contrib.sessions Version: 1.2
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Gabriel Hurley)

My goal was to configure django to work with sessions that use nothing but cookies for session management.
Built-in session backends all use cookies just for the session key, but store session data elsewhere.
Since my session data is very small, and I don't / can't have them written to the disk or cache, I want to store it entirely in a cookie.

It turns out that not only does Django not provide such a sessions backend - it's also impossible to write one without also customizing the sessions middleware. This is because cookies can only be read from the request and written to the response, but these objects are not provided to the session by the middleware!

I ended up replacing the default sessions middleware with a patched version that adds the needed objects to the session, if the session object has the attributes that accept them. See patch + simple code for the backend below.

You might want to build this (or something similar) into the framework - others may also find this useful.

=================================================================
Middleware patch
=================================================================

8c8
< class SessionMiddleware(django.contrib.sessions.middleware.SessionMiddleware):
---
> class SessionMiddleware(object):
10d9
<         '''Similar to original constructor, but also keeps the request in the session'''
14,15d12
<         if hasattr(request.session, 'request'):
<             request.session.request = request
22,23d18
<         if hasattr(request.session, 'response'):
<             request.session.response = response
47,48d41
<                 if hasattr(request.session, 'put_in_cookie'):
<                     request.session.put_in_cookie()



=================================================================
Simple implementation for cookie-only sessions backend:
=================================================================

from Crypto.Cipher import AES
from django.conf import settings
from django.contrib.sessions.backends.base import SessionBase
from django.utils.http import cookie_date
import base64
import django.conf
import hashlib
import re
import time


BLOCK_SIZE = 32
PADDING = '{'
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING


class SessionStore(SessionBase):
    """
    Implements a cookie based session store.
    """
    def __init__(self, session_key=None):
        self.cookie_name = getattr(settings, "SESSION_COOKIE_DATA_NAME", "SessionData")
        m = hashlib.md5()
        m.update("cookies!")
        m.update(getattr(settings, "SECRET_KEY"))
        m.update("cookies!")
        self.secret = m.hexdigest()
        self.request = None
        self.response = None
        super(SessionStore, self).__init__(session_key)
        self.saved_session_data = None

    def _encrypt(self, plain):
        c = AES.new(self.secret)
        padded = pad(plain)
        encrypted = c.encrypt(padded)
        wrapped = base64.b64encode(encrypted) + '-%d'%len(plain)
        return wrapped
    
    def _decrypt(self, cipher):
        lstr = re.findall(r'-\d+$', cipher)[0]
        l = int(lstr[1:])
        c = AES.new(self.secret)
        encrypted = base64.b64decode(cipher[:-len(lstr)])
        padded = c.decrypt(encrypted)
        return padded[:l]
        
    def create(self):
        self._session_key = self._get_new_session_key()
        self.save(must_create=True)
        self.modified = True
        return

    def load(self):
        cookie_data = self.request.COOKIES.get(self.cookie_name, None)
        if cookie_data == None:
            return None
        decrypted = self._decrypt(cookie_data)
        decoded = self.decode(decrypted)
        return decoded

    def save(self, must_create=False):
        encoded = self.encode(self._get_session(no_load=must_create))
        encrypted = self._encrypt(encoded)
        session_data = encrypted
        self.saved_session_data = session_data
    
    def put_in_cookie(self):
        max_age = self.get_expiry_age()
        expires_time = time.time() + max_age
        expires = cookie_date(expires_time)
        if self.response==None:
            pass
        self.response.set_cookie(self.cookie_name,
                self.saved_session_data, max_age=max_age,
                        expires=expires, domain=django.conf.settings.SESSION_COOKIE_DOMAIN,
                        path=django.conf.settings.SESSION_COOKIE_PATH,
                        secure=django.conf.settings.SESSION_COOKIE_SECURE or None)

    def exists(self, session_key):
        return False     # we can't possibly have session cookie ID conflicts...

    def delete(self, session_key=None):
        if self.response:
            self.response.delete_cookie(
                    self.cookie_name, domain=django.conf.settings.SESSION_COOKIE_DOMAIN,
                    path=django.conf.settings.SESSION_COOKIE_PATH)

    def clean(self):
        pass

Change History (2)

by oran <oran@…>, 14 years ago

Attachment: patch.2.diff added

the patch

comment:1 by Gabriel Hurley, 14 years ago

Description: modified (diff)

Reformatted ticket description.

Note: See TracTickets for help on using tickets.
Back to Top