PageRenderTime 43ms CodeModel.GetById 17ms app.highlight 21ms RepoModel.GetById 1ms app.codeStats 0ms

/django/contrib/sessions/backends/base.py

https://code.google.com/p/mango-py/
Python | 310 lines | 255 code | 33 blank | 22 comment | 14 complexity | eddef3dc6ea055e0b27516e0e6a0fdd8 MD5 | raw file
  1import base64
  2import os
  3import random
  4import sys
  5import time
  6from datetime import datetime, timedelta
  7try:
  8    import cPickle as pickle
  9except ImportError:
 10    import pickle
 11
 12from django.conf import settings
 13from django.core.exceptions import SuspiciousOperation
 14from django.utils.hashcompat import md5_constructor
 15from django.utils.crypto import constant_time_compare, salted_hmac
 16
 17# Use the system (hardware-based) random number generator if it exists.
 18if hasattr(random, 'SystemRandom'):
 19    randrange = random.SystemRandom().randrange
 20else:
 21    randrange = random.randrange
 22MAX_SESSION_KEY = 18446744073709551616L     # 2 << 63
 23
 24class CreateError(Exception):
 25    """
 26    Used internally as a consistent exception type to catch from save (see the
 27    docstring for SessionBase.save() for details).
 28    """
 29    pass
 30
 31class SessionBase(object):
 32    """
 33    Base class for all Session classes.
 34    """
 35    TEST_COOKIE_NAME = 'testcookie'
 36    TEST_COOKIE_VALUE = 'worked'
 37
 38    def __init__(self, session_key=None):
 39        self._session_key = session_key
 40        self.accessed = False
 41        self.modified = False
 42
 43    def __contains__(self, key):
 44        return key in self._session
 45
 46    def __getitem__(self, key):
 47        return self._session[key]
 48
 49    def __setitem__(self, key, value):
 50        self._session[key] = value
 51        self.modified = True
 52
 53    def __delitem__(self, key):
 54        del self._session[key]
 55        self.modified = True
 56
 57    def keys(self):
 58        return self._session.keys()
 59
 60    def items(self):
 61        return self._session.items()
 62
 63    def get(self, key, default=None):
 64        return self._session.get(key, default)
 65
 66    def pop(self, key, *args):
 67        self.modified = self.modified or key in self._session
 68        return self._session.pop(key, *args)
 69
 70    def setdefault(self, key, value):
 71        if key in self._session:
 72            return self._session[key]
 73        else:
 74            self.modified = True
 75            self._session[key] = value
 76            return value
 77
 78    def set_test_cookie(self):
 79        self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE
 80
 81    def test_cookie_worked(self):
 82        return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE
 83
 84    def delete_test_cookie(self):
 85        del self[self.TEST_COOKIE_NAME]
 86
 87    def _hash(self, value):
 88        key_salt = "django.contrib.sessions" + self.__class__.__name__
 89        return salted_hmac(key_salt, value).hexdigest()
 90
 91    def encode(self, session_dict):
 92        "Returns the given session dictionary pickled and encoded as a string."
 93        pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL)
 94        hash = self._hash(pickled)
 95        return base64.encodestring(hash + ":" + pickled)
 96
 97    def decode(self, session_data):
 98        encoded_data = base64.decodestring(session_data)
 99        try:
100            # could produce ValueError if there is no ':'
101            hash, pickled = encoded_data.split(':', 1)
102            expected_hash = self._hash(pickled)
103            if not constant_time_compare(hash, expected_hash):
104                raise SuspiciousOperation("Session data corrupted")
105            else:
106                return pickle.loads(pickled)
107        except Exception:
108            # ValueError, SuspiciousOperation, unpickling exceptions
109            # Fall back to Django 1.2 method
110            # PendingDeprecationWarning <- here to remind us to
111            # remove this fallback in Django 1.5
112            try:
113                return self._decode_old(session_data)
114            except Exception:
115                # Unpickling can cause a variety of exceptions. If something happens,
116                # just return an empty dictionary (an empty session).
117                return {}
118
119    def _decode_old(self, session_data):
120        encoded_data = base64.decodestring(session_data)
121        pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
122        if not constant_time_compare(md5_constructor(pickled + settings.SECRET_KEY).hexdigest(),
123                                     tamper_check):
124            raise SuspiciousOperation("User tampered with session cookie.")
125        return pickle.loads(pickled)
126
127    def update(self, dict_):
128        self._session.update(dict_)
129        self.modified = True
130
131    def has_key(self, key):
132        return self._session.has_key(key)
133
134    def values(self):
135        return self._session.values()
136
137    def iterkeys(self):
138        return self._session.iterkeys()
139
140    def itervalues(self):
141        return self._session.itervalues()
142
143    def iteritems(self):
144        return self._session.iteritems()
145
146    def clear(self):
147        # To avoid unnecessary persistent storage accesses, we set up the
148        # internals directly (loading data wastes time, since we are going to
149        # set it to an empty dict anyway).
150        self._session_cache = {}
151        self.accessed = True
152        self.modified = True
153
154    def _get_new_session_key(self):
155        "Returns session key that isn't being used."
156        # The random module is seeded when this Apache child is created.
157        # Use settings.SECRET_KEY as added salt.
158        try:
159            pid = os.getpid()
160        except AttributeError:
161            # No getpid() in Jython, for example
162            pid = 1
163        while 1:
164            session_key = md5_constructor("%s%s%s%s"
165                    % (randrange(0, MAX_SESSION_KEY), pid, time.time(),
166                       settings.SECRET_KEY)).hexdigest()
167            if not self.exists(session_key):
168                break
169        return session_key
170
171    def _get_session_key(self):
172        if self._session_key:
173            return self._session_key
174        else:
175            self._session_key = self._get_new_session_key()
176            return self._session_key
177
178    def _set_session_key(self, session_key):
179        self._session_key = session_key
180
181    session_key = property(_get_session_key, _set_session_key)
182
183    def _get_session(self, no_load=False):
184        """
185        Lazily loads session from storage (unless "no_load" is True, when only
186        an empty dict is stored) and stores it in the current instance.
187        """
188        self.accessed = True
189        try:
190            return self._session_cache
191        except AttributeError:
192            if self._session_key is None or no_load:
193                self._session_cache = {}
194            else:
195                self._session_cache = self.load()
196        return self._session_cache
197
198    _session = property(_get_session)
199
200    def get_expiry_age(self):
201        """Get the number of seconds until the session expires."""
202        expiry = self.get('_session_expiry')
203        if not expiry:   # Checks both None and 0 cases
204            return settings.SESSION_COOKIE_AGE
205        if not isinstance(expiry, datetime):
206            return expiry
207        delta = expiry - datetime.now()
208        return delta.days * 86400 + delta.seconds
209
210    def get_expiry_date(self):
211        """Get session the expiry date (as a datetime object)."""
212        expiry = self.get('_session_expiry')
213        if isinstance(expiry, datetime):
214            return expiry
215        if not expiry:   # Checks both None and 0 cases
216            expiry = settings.SESSION_COOKIE_AGE
217        return datetime.now() + timedelta(seconds=expiry)
218
219    def set_expiry(self, value):
220        """
221        Sets a custom expiration for the session. ``value`` can be an integer,
222        a Python ``datetime`` or ``timedelta`` object or ``None``.
223
224        If ``value`` is an integer, the session will expire after that many
225        seconds of inactivity. If set to ``0`` then the session will expire on
226        browser close.
227
228        If ``value`` is a ``datetime`` or ``timedelta`` object, the session
229        will expire at that specific future time.
230
231        If ``value`` is ``None``, the session uses the global session expiry
232        policy.
233        """
234        if value is None:
235            # Remove any custom expiration for this session.
236            try:
237                del self['_session_expiry']
238            except KeyError:
239                pass
240            return
241        if isinstance(value, timedelta):
242            value = datetime.now() + value
243        self['_session_expiry'] = value
244
245    def get_expire_at_browser_close(self):
246        """
247        Returns ``True`` if the session is set to expire when the browser
248        closes, and ``False`` if there's an expiry date. Use
249        ``get_expiry_date()`` or ``get_expiry_age()`` to find the actual expiry
250        date/age, if there is one.
251        """
252        if self.get('_session_expiry') is None:
253            return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
254        return self.get('_session_expiry') == 0
255
256    def flush(self):
257        """
258        Removes the current session data from the database and regenerates the
259        key.
260        """
261        self.clear()
262        self.delete()
263        self.create()
264
265    def cycle_key(self):
266        """
267        Creates a new session key, whilst retaining the current session data.
268        """
269        data = self._session_cache
270        key = self.session_key
271        self.create()
272        self._session_cache = data
273        self.delete(key)
274
275    # Methods that child classes must implement.
276
277    def exists(self, session_key):
278        """
279        Returns True if the given session_key already exists.
280        """
281        raise NotImplementedError
282
283    def create(self):
284        """
285        Creates a new session instance. Guaranteed to create a new object with
286        a unique key and will have saved the result once (with empty data)
287        before the method returns.
288        """
289        raise NotImplementedError
290
291    def save(self, must_create=False):
292        """
293        Saves the session data. If 'must_create' is True, a new session object
294        is created (otherwise a CreateError exception is raised). Otherwise,
295        save() can update an existing object with the same key.
296        """
297        raise NotImplementedError
298
299    def delete(self, session_key=None):
300        """
301        Deletes the session data under this key. If the key is None, the
302        current session key value is used.
303        """
304        raise NotImplementedError
305
306    def load(self):
307        """
308        Loads the session data and returns a dictionary.
309        """
310        raise NotImplementedError