#14579: built-in sessions middleware can't be used for entirely cookie-based
sessions
------------------------------------+---------------------------------------
Reporter: oran | Owner: nobody
Status: new | Milestone:
Component: Uncategorized | Version: 1.2
Resolution: | Keywords:
Stage: Unreviewed | Has_patch: 0
Needs_docs: 0 | Needs_tests: 0
Needs_better_patch: 0 |
------------------------------------+---------------------------------------
Changes (by gabrielhurley):
* needs_better_patch: => 0
* needs_tests: => 0
* needs_docs: => 0
Old description:
> 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
New description:
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
}}}
Comment:
Reformatted ticket description.
--
Ticket URL: <http://code.djangoproject.com/ticket/14579#comment:1>
Django <http://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/django-updates?hl=en.