Opened 19 years ago

Closed 18 years ago

Last modified 18 years ago

#395 closed defect (fixed)

[patch] New session middleware for browser-session length cookies

Reported by: rob@… Owned by: Adrian Holovaty
Component: Core (Other) Version:
Severity: blocker Keywords:
Cc: Triage Stage: Unreviewed
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I noticed that the [source:/django/trunk/django/middleware/sessions.py SessionsMiddleware] class was setting a cookie with an explicit expiration date. So I created this modified middleware for my application that allows SESSION_COOKIE_AGE to be set to None (which then creates a session cookie with no expiration date, meaning the cookie will be deleted when the browser session ends).

Currently the middleware sets the session expiration date in the database to 1 hour from when the session cookie is set, but this could be changed with a configuration variable (SESSION_EXPIRATION_AGE, perhaps?).

from django.middleware import sessions as sessionsMiddleware
from django.conf.settings import SESSION_COOKIE_NAME, SESSION_COOKIE_AGE, SESSION_COOKIE_DOMAIN
from django.models.core import sessions
import datetime

class SingleSessionMiddleware(sessionsMiddleware.SessionMiddleware):
  def process_response(self, request, response):
    try:
      modified = request.session.modified
    except AttributeError:
      modified = False
    if modified:
      session_key = request.session.session_key or sessions.get_new_session_key()
      if SESSION_COOKIE_AGE != None:
        new_session = sessions.save(session_key, request.session._session,
          datetime.datetime.now() + datetime.timedelta(seconds=SESSION_COOKIE_AGE))
      else:
        # right now I'm just making sessions last for an hour ... should
        # probably make a configuration directive to specify the seconds
        # till the session expires
        new_session = sessions.save(session_key, request.session._session, 
          datetime.datetime.now() + datetime.timedelta(hours=1))
    
      # TODO: Accept variable session length and domain.
      response.set_cookie(SESSION_COOKIE_NAME, session_key,
        max_age=SESSION_COOKIE_AGE, domain=SESSION_COOKIE_DOMAIN)
    return response

Change History (5)

comment:1 by rob@…, 19 years ago

I just noticed one downside to this code (assuming you set SESSION_COOKIE_AGE=None in settings/main.py): the admin still uses the basic django session code which doesn't like None...

Traceback (most recent call last):

  File "/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages/django/core/servers/basehttp.py", line 272, in run
    self.result = application(self.environ, self.start_response)

  File "/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages/django/core/servers/basehttp.py", line 615, in __call__
    return self.application(environ, start_response)

  File "/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages/django/core/handlers/wsgi.py", line 121, in __call__
    response = middleware_method(request, response)

  File "/Library/Frameworks/Python.framework/Versions/2.4/lib/python2.4/site-packages/django/middleware/sessions.py", line 68, in process_response
    datetime.datetime.now() + datetime.timedelta(seconds=SESSION_COOKIE_AGE))

TypeError: unsupported type for timedelta seconds component: NoneType

Placing SESSION_COOKIE_AGE=60 * 60 * 24 * 7 * 2 in the settings/admin.py fixes the problem, but I thought this was an appropriate issue to add to the ticket.

comment:2 by Adrian Holovaty, 19 years ago

Status: newassigned

comment:3 by b dot nuttall at vanderbilt.edu, 19 years ago

So I got this working with the lastest Django SVN. Here's the middleware class I used:

from django.conf.settings import SESSION_COOKIE_NAME, SESSION_COOKIE_AGE, SESSION_COOKIE_DOMAIN, SESSION_SAVE_EVERY_REQUEST
from django.models.core import sessions
from django.utils.cache import patch_vary_headers
from django.middleware import sessions as sessionsMiddleware

import datetime

class SingleSessionMiddleware(sessionsMiddleware.SessionMiddleware):
    def process_response(self, request, response):
        # If request.session was modified, or if response.session was set, save
        # those changes and set a session cookie.
        patch_vary_headers(response, ('Cookie',))
        try:
            modified = request.session.modified
        except AttributeError:
            pass
        else:
            if modified or SESSION_SAVE_EVERY_REQUEST:
                session_key = request.session.session_key or sessions.get_new_session_key()
                if SESSION_COOKIE_AGE != None:
                    new_session = sessions.save(session_key, request.session._session, datetime.datetime.now() + datetime.timedelta(seconds=SESSION_COOKIE_AGE))
                    expires = datetime.datetime.strftime(datetime.datetime.utcnow() + datetime.timedelta(seconds=SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
                    response.set_cookie(SESSION_COOKIE_NAME, session_key, max_age=SESSION_COOKIE_AGE, expires=expires, domain=SESSION_COOKIE_DOMAIN)
                else:
                    new_session = sessions.save(session_key, request.session._session, datetime.datetime.now() + datetime.timedelta(hours=1))
                    response.set_cookie(SESSION_COOKIE_NAME, session_key, max_age=SESSION_COOKIE_AGE, domain=SESSION_COOKIE_DOMAIN)  
        return response

comment:4 by Adrian Holovaty, 18 years ago

Resolution: fixed
Status: assignedclosed

(In [3049]) Fixed #395 -- Added SESSION_EXPIRE_AT_BROWSER_CLOSE setting, which regulates whether session framework should use browser-session-length cookies.

comment:5 by anonymous, 18 years ago

priority: normalhighest
Severity: normalblocker
Type: enhancementdefect
Note: See TracTickets for help on using tickets.
Back to Top