--- a/app/django/contrib/sessions/backends/base.py Tue Oct 14 12:36:55 2008 +0000
+++ b/app/django/contrib/sessions/backends/base.py Tue Oct 14 16:00:59 2008 +0000
@@ -1,17 +1,32 @@
import base64
-import md5
import os
import random
import sys
import time
-from django.conf import settings
-from django.core.exceptions import SuspiciousOperation
-
+from datetime import datetime, timedelta
try:
import cPickle as pickle
except ImportError:
import pickle
+from django.conf import settings
+from django.core.exceptions import SuspiciousOperation
+from django.utils.hashcompat import md5_constructor
+
+# Use the system (hardware-based) random number generator if it exists.
+if hasattr(random, 'SystemRandom'):
+ randrange = random.SystemRandom().randrange
+else:
+ randrange = random.randrange
+MAX_SESSION_KEY = 18446744073709551616L # 2 << 63
+
+class CreateError(Exception):
+ """
+ Used internally as a consistent exception type to catch from save (see the
+ docstring for SessionBase.save() for details).
+ """
+ pass
+
class SessionBase(object):
"""
Base class for all Session classes.
@@ -71,13 +86,13 @@
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()
+ pickled_md5 = md5_constructor(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:
+ if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
raise SuspiciousOperation("User tampered with session cookie.")
try:
return pickle.loads(pickled)
@@ -86,6 +101,33 @@
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 clear(self):
+ # To avoid unnecessary persistent storage accesses, we set up the
+ # internals directly (loading data wastes time, since we are going to
+ # set it to an empty dict anyway).
+ self._session_cache = {}
+ self.accessed = True
+ self.modified = True
+
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.
@@ -96,8 +138,9 @@
# 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()
+ session_key = md5_constructor("%s%s%s%s"
+ % (randrange(0, MAX_SESSION_KEY), pid, time.time(),
+ settings.SECRET_KEY)).hexdigest()
if not self.exists(session_key):
break
return session_key
@@ -114,13 +157,16 @@
session_key = property(_get_session_key, _set_session_key)
- def _get_session(self):
- # Lazily loads session from storage.
+ def _get_session(self, no_load=False):
+ """
+ Lazily loads session from storage (unless "no_load" is True, when only
+ an empty dict is stored) and stores it in the current instance.
+ """
self.accessed = True
try:
return self._session_cache
except AttributeError:
- if self._session_key is None:
+ if self._session_key is None or no_load:
self._session_cache = {}
else:
self._session_cache = self.load()
@@ -128,6 +174,81 @@
_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
+
+ def flush(self):
+ """
+ Removes the current session data from the database and regenerates the
+ key.
+ """
+ self.clear()
+ self.delete()
+ self.create()
+
+ def cycle_key(self):
+ """
+ Creates a new session key, whilst retaining the current session data.
+ """
+ data = self._session_cache
+ key = self.session_key
+ self.create()
+ self._session_cache = data
+ self.delete(key)
+
# Methods that child classes must implement.
def exists(self, session_key):
@@ -136,15 +257,26 @@
"""
raise NotImplementedError
- def save(self):
+ def create(self):
"""
- Saves the session data.
+ Creates a new session instance. Guaranteed to create a new object with
+ a unique key and will have saved the result once (with empty data)
+ before the method returns.
"""
raise NotImplementedError
- def delete(self, session_key):
+ def save(self, must_create=False):
+ """
+ Saves the session data. If 'must_create' is True, a new session object
+ is created (otherwise a CreateError exception is raised). Otherwise,
+ save() can update an existing object with the same key.
"""
- Clears out the session data under this key.
+ raise NotImplementedError
+
+ def delete(self, session_key=None):
+ """
+ Deletes the session data under this key. If the key is None, the
+ current session key value is used.
"""
raise NotImplementedError