#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.

Reply via email to