6
from datetime import datetime, timedelta
8
import cPickle as pickle
12
from django.conf import settings
13
from django.core.exceptions import SuspiciousOperation
14
from django.utils.hashcompat import md5_constructor
16
# Use the system (hardware-based) random number generator if it exists.
17
if hasattr(random, 'SystemRandom'):
18
randrange = random.SystemRandom().randrange
20
randrange = random.randrange
21
MAX_SESSION_KEY = 18446744073709551616L # 2 << 63
23
class CreateError(Exception):
25
Used internally as a consistent exception type to catch from save (see the
26
docstring for SessionBase.save() for details).
30
class SessionBase(object):
32
Base class for all Session classes.
34
TEST_COOKIE_NAME = 'testcookie'
35
TEST_COOKIE_VALUE = 'worked'
37
def __init__(self, session_key=None):
38
self._session_key = session_key
42
def __contains__(self, key):
43
return key in self._session
45
def __getitem__(self, key):
46
return self._session[key]
48
def __setitem__(self, key, value):
49
self._session[key] = value
52
def __delitem__(self, key):
53
del self._session[key]
57
return self._session.keys()
60
return self._session.items()
62
def get(self, key, default=None):
63
return self._session.get(key, default)
65
def pop(self, key, *args):
66
self.modified = self.modified or key in self._session
67
return self._session.pop(key, *args)
69
def setdefault(self, key, value):
70
if key in self._session:
71
return self._session[key]
74
self._session[key] = value
77
def set_test_cookie(self):
78
self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE
80
def test_cookie_worked(self):
81
return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE
83
def delete_test_cookie(self):
84
del self[self.TEST_COOKIE_NAME]
86
def encode(self, session_dict):
87
"Returns the given session dictionary pickled and encoded as a string."
88
pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL)
89
pickled_md5 = md5_constructor(pickled + settings.SECRET_KEY).hexdigest()
90
return base64.encodestring(pickled + pickled_md5)
92
def decode(self, session_data):
93
encoded_data = base64.decodestring(session_data)
94
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
95
if md5_constructor(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
96
raise SuspiciousOperation("User tampered with session cookie.")
98
return pickle.loads(pickled)
99
# Unpickling can cause a variety of exceptions. If something happens,
100
# just return an empty dictionary (an empty session).
104
def update(self, dict_):
105
self._session.update(dict_)
108
def has_key(self, key):
109
return self._session.has_key(key)
112
return self._session.values()
115
return self._session.iterkeys()
117
def itervalues(self):
118
return self._session.itervalues()
121
return self._session.iteritems()
124
# To avoid unnecessary persistent storage accesses, we set up the
125
# internals directly (loading data wastes time, since we are going to
126
# set it to an empty dict anyway).
127
self._session_cache = {}
131
def _get_new_session_key(self):
132
"Returns session key that isn't being used."
133
# The random module is seeded when this Apache child is created.
134
# Use settings.SECRET_KEY as added salt.
137
except AttributeError:
138
# No getpid() in Jython, for example
141
session_key = md5_constructor("%s%s%s%s"
142
% (randrange(0, MAX_SESSION_KEY), pid, time.time(),
143
settings.SECRET_KEY)).hexdigest()
144
if not self.exists(session_key):
148
def _get_session_key(self):
149
if self._session_key:
150
return self._session_key
152
self._session_key = self._get_new_session_key()
153
return self._session_key
155
def _set_session_key(self, session_key):
156
self._session_key = session_key
158
session_key = property(_get_session_key, _set_session_key)
160
def _get_session(self, no_load=False):
162
Lazily loads session from storage (unless "no_load" is True, when only
163
an empty dict is stored) and stores it in the current instance.
167
return self._session_cache
168
except AttributeError:
169
if self._session_key is None or no_load:
170
self._session_cache = {}
172
self._session_cache = self.load()
173
return self._session_cache
175
_session = property(_get_session)
177
def get_expiry_age(self):
178
"""Get the number of seconds until the session expires."""
179
expiry = self.get('_session_expiry')
180
if not expiry: # Checks both None and 0 cases
181
return settings.SESSION_COOKIE_AGE
182
if not isinstance(expiry, datetime):
184
delta = expiry - datetime.now()
185
return delta.days * 86400 + delta.seconds
187
def get_expiry_date(self):
188
"""Get session the expiry date (as a datetime object)."""
189
expiry = self.get('_session_expiry')
190
if isinstance(expiry, datetime):
192
if not expiry: # Checks both None and 0 cases
193
expiry = settings.SESSION_COOKIE_AGE
194
return datetime.now() + timedelta(seconds=expiry)
196
def set_expiry(self, value):
198
Sets a custom expiration for the session. ``value`` can be an integer,
199
a Python ``datetime`` or ``timedelta`` object or ``None``.
201
If ``value`` is an integer, the session will expire after that many
202
seconds of inactivity. If set to ``0`` then the session will expire on
205
If ``value`` is a ``datetime`` or ``timedelta`` object, the session
206
will expire at that specific future time.
208
If ``value`` is ``None``, the session uses the global session expiry
212
# Remove any custom expiration for this session.
214
del self['_session_expiry']
218
if isinstance(value, timedelta):
219
value = datetime.now() + value
220
self['_session_expiry'] = value
222
def get_expire_at_browser_close(self):
224
Returns ``True`` if the session is set to expire when the browser
225
closes, and ``False`` if there's an expiry date. Use
226
``get_expiry_date()`` or ``get_expiry_age()`` to find the actual expiry
227
date/age, if there is one.
229
if self.get('_session_expiry') is None:
230
return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
231
return self.get('_session_expiry') == 0
235
Removes the current session data from the database and regenerates the
244
Creates a new session key, whilst retaining the current session data.
246
data = self._session_cache
247
key = self.session_key
249
self._session_cache = data
252
# Methods that child classes must implement.
254
def exists(self, session_key):
256
Returns True if the given session_key already exists.
258
raise NotImplementedError
262
Creates a new session instance. Guaranteed to create a new object with
263
a unique key and will have saved the result once (with empty data)
264
before the method returns.
266
raise NotImplementedError
268
def save(self, must_create=False):
270
Saves the session data. If 'must_create' is True, a new session object
271
is created (otherwise a CreateError exception is raised). Otherwise,
272
save() can update an existing object with the same key.
274
raise NotImplementedError
276
def delete(self, session_key=None):
278
Deletes the session data under this key. If the key is None, the
279
current session key value is used.
281
raise NotImplementedError
285
Loads the session data and returns a dictionary.
287
raise NotImplementedError