app/django/contrib/sessions/backends/base.py
changeset 323 ff1a9aa48cfd
parent 54 03e267d67478
--- 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