import base64 import md5 import os import random import sys import time from datetime import datetime, timedelta from django.conf import settings from django.core.exceptions import SuspiciousOperation try: import cPickle as pickle except ImportError: import pickle class SessionBase(object): """ Base class for all Session classes. """ TEST_COOKIE_NAME = 'testcookie' TEST_COOKIE_VALUE = 'worked' def __init__(self, session_key=None): self._session_key = session_key self.accessed = False self.modified = False def __contains__(self, key): return key in self._session def __getitem__(self, key): return self._session[key] def __setitem__(self, key, value): self._session[key] = value self.modified = True def __delitem__(self, key): del self._session[key] self.modified = True def keys(self): return self._session.keys() def items(self): return self._session.items() def get(self, key, default=None): return self._session.get(key, default) def pop(self, key, *args): self.modified = self.modified or key in self._session return self._session.pop(key, *args) def setdefault(self, key, value): if key in self._session: return self._session[key] else: self.modified = True self._session[key] = value return value def set_test_cookie(self): self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE def test_cookie_worked(self): return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE def delete_test_cookie(self): del self[self.TEST_COOKIE_NAME] def encode(self, session_dict): "Returns the given session dictionary pickled and encoded as a string." pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL) pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest() return base64.encodestring(pickled + pickled_md5) def decode(self, session_data): encoded_data = base64.decodestring(session_data) pickled, tamper_check = encoded_data[:-32], encoded_data[-32:] if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check: raise SuspiciousOperation("User tampered with session cookie.") try: return pickle.loads(pickled) # Unpickling can cause a variety of exceptions. If something happens, # just return an empty dictionary (an empty session). except: return {} def update(self, dict_): self._session.update(dict_) self.modified = True def has_key(self, key): return self._session.has_key(key) def values(self): return self._session.values() def iterkeys(self): return self._session.iterkeys() def itervalues(self): return self._session.itervalues() def iteritems(self): return self._session.iteritems() def _get_new_session_key(self): "Returns session key that isn't being used." # The random module is seeded when this Apache child is created. # Use settings.SECRET_KEY as added salt. try: pid = os.getpid() except AttributeError: # No getpid() in Jython, for example pid = 1 while 1: session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1), pid, time.time(), settings.SECRET_KEY)).hexdigest() if not self.exists(session_key): break return session_key def _get_session_key(self): if self._session_key: return self._session_key else: self._session_key = self._get_new_session_key() return self._session_key def _set_session_key(self, session_key): self._session_key = session_key session_key = property(_get_session_key, _set_session_key) def _get_session(self): # Lazily loads session from storage. self.accessed = True try: return self._session_cache except AttributeError: if self._session_key is None: self._session_cache = {} else: self._session_cache = self.load() return self._session_cache _session = property(_get_session) def get_expiry_age(self): """Get the number of seconds until the session expires.""" expiry = self.get('_session_expiry') if not expiry: # Checks both None and 0 cases return settings.SESSION_COOKIE_AGE if not isinstance(expiry, datetime): return expiry delta = expiry - datetime.now() return delta.days * 86400 + delta.seconds def get_expiry_date(self): """Get session the expiry date (as a datetime object).""" expiry = self.get('_session_expiry') if isinstance(expiry, datetime): return expiry if not expiry: # Checks both None and 0 cases expiry = settings.SESSION_COOKIE_AGE return datetime.now() + timedelta(seconds=expiry) def set_expiry(self, value): """ Sets a custom expiration for the session. ``value`` can be an integer, a Python ``datetime`` or ``timedelta`` object or ``None``. If ``value`` is an integer, the session will expire after that many seconds of inactivity. If set to ``0`` then the session will expire on browser close. If ``value`` is a ``datetime`` or ``timedelta`` object, the session will expire at that specific future time. If ``value`` is ``None``, the session uses the global session expiry policy. """ if value is None: # Remove any custom expiration for this session. try: del self['_session_expiry'] except KeyError: pass return if isinstance(value, timedelta): value = datetime.now() + value self['_session_expiry'] = value def get_expire_at_browser_close(self): """ Returns ``True`` if the session is set to expire when the browser closes, and ``False`` if there's an expiry date. Use ``get_expiry_date()`` or ``get_expiry_age()`` to find the actual expiry date/age, if there is one. """ if self.get('_session_expiry') is None: return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE return self.get('_session_expiry') == 0 # Methods that child classes must implement. def exists(self, session_key): """ Returns True if the given session_key already exists. """ raise NotImplementedError def save(self): """ Saves the session data. """ raise NotImplementedError def delete(self, session_key): """ Clears out the session data under this key. """ raise NotImplementedError def load(self): """ Loads the session data and returns a dictionary. """ raise NotImplementedError