Django

Code

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

Revision 8459, 9.0 kB (checked in by mtredinnick, 3 months ago)

When logging in, change the session key whilst preserving any existing
sesssion. This means the user will see their session preserved across a login
boundary, but somebody snooping the anonymous session key won't be able to view
the authenticated session data.

This is the final piece of the session key handling changes.

  • Property svn:eol-style set to native
Line 
1 import base64
2 import os
3 import random
4 import sys
5 import time
6 from datetime import datetime, timedelta
7 try:
8     import cPickle as pickle
9 except ImportError:
10     import pickle
11
12 from django.conf import settings
13 from django.core.exceptions import SuspiciousOperation
14 from django.utils.hashcompat import md5_constructor
15
16 # Use the system (hardware-based) random number generator if it exists.
17 if hasattr(random, 'SystemRandom'):
18     randrange = random.SystemRandom().randrange
19 else:
20     randrange = random.randrange
21 MAX_SESSION_KEY = 18446744073709551616L     # 2 << 63
22
23 class CreateError(Exception):
24     """
25     Used internally as a consistent exception type to catch from save (see the
26     docstring for SessionBase.save() for details).
27     """
28     pass
29
30 class SessionBase(object):
31     """
32     Base class for all Session classes.
33     """
34     TEST_COOKIE_NAME = 'testcookie'
35     TEST_COOKIE_VALUE = 'worked'
36
37     def __init__(self, session_key=None):
38         self._session_key = session_key
39         self.accessed = False
40         self.modified = False
41
42     def __contains__(self, key):
43         return key in self._session
44
45     def __getitem__(self, key):
46         return self._session[key]
47
48     def __setitem__(self, key, value):
49         self._session[key] = value
50         self.modified = True
51
52     def __delitem__(self, key):
53         del self._session[key]
54         self.modified = True
55
56     def keys(self):
57         return self._session.keys()
58
59     def items(self):
60         return self._session.items()
61
62     def get(self, key, default=None):
63         return self._session.get(key, default)
64
65     def pop(self, key, *args):
66         self.modified = self.modified or key in self._session
67         return self._session.pop(key, *args)
68
69     def setdefault(self, key, value):
70         if key in self._session:
71             return self._session[key]
72         else:
73             self.modified = True
74             self._session[key] = value
75             return value
76
77     def set_test_cookie(self):
78         self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE
79
80     def test_cookie_worked(self):
81         return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE
82
83     def delete_test_cookie(self):
84         del self[self.TEST_COOKIE_NAME]
85
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)
91
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.")
97         try:
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).
101         except:
102             return {}
103
104     def update(self, dict_):
105         self._session.update(dict_)
106         self.modified = True
107
108     def has_key(self, key):
109         return self._session.has_key(key)
110
111     def values(self):
112         return self._session.values()
113
114     def iterkeys(self):
115         return self._session.iterkeys()
116
117     def itervalues(self):
118         return self._session.itervalues()
119
120     def iteritems(self):
121         return self._session.iteritems()
122
123     def clear(self):
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 = {}
128         self.accessed = True
129         self.modified = True
130
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.
135         try:
136             pid = os.getpid()
137         except AttributeError:
138             # No getpid() in Jython, for example
139             pid = 1
140         while 1:
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):
145                 break
146         return session_key
147
148     def _get_session_key(self):
149         if self._session_key:
150             return self._session_key
151         else:
152             self._session_key = self._get_new_session_key()
153             return self._session_key
154
155     def _set_session_key(self, session_key):
156         self._session_key = session_key
157
158     session_key = property(_get_session_key, _set_session_key)
159
160     def _get_session(self, no_load=False):
161         """
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.
164         """
165         self.accessed = True
166         try:
167             return self._session_cache
168         except AttributeError:
169             if self._session_key is None or no_load:
170                 self._session_cache = {}
171             else:
172                 self._session_cache = self.load()
173         return self._session_cache
174
175     _session = property(_get_session)
176
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):
183             return expiry
184         delta = expiry - datetime.now()
185         return delta.days * 86400 + delta.seconds
186
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):
191             return expiry
192         if not expiry:   # Checks both None and 0 cases
193             expiry = settings.SESSION_COOKIE_AGE
194         return datetime.now() + timedelta(seconds=expiry)
195
196     def set_expiry(self, value):
197         """
198         Sets a custom expiration for the session. ``value`` can be an integer,
199         a Python ``datetime`` or ``timedelta`` object or ``None``.
200
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
203         browser close.
204
205         If ``value`` is a ``datetime`` or ``timedelta`` object, the session
206         will expire at that specific future time.
207
208         If ``value`` is ``None``, the session uses the global session expiry
209         policy.
210         """
211         if value is None:
212             # Remove any custom expiration for this session.
213             try:
214                 del self['_session_expiry']
215             except KeyError:
216                 pass
217             return
218         if isinstance(value, timedelta):
219             value = datetime.now() + value
220         self['_session_expiry'] = value
221
222     def get_expire_at_browser_close(self):
223         """
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.
228         """
229         if self.get('_session_expiry') is None:
230             return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE
231         return self.get('_session_expiry') == 0
232
233     def flush(self):
234         """
235         Removes the current session data from the database and regenerates the
236         key.
237         """
238         self.clear()
239         self.delete()
240         self.create()
241
242     def cycle_key(self):
243         """
244         Creates a new session key, whilst retaining the current session data.
245         """
246         data = self._session_cache
247         key = self.session_key
248         self.create()
249         self._session_cache = data
250         self.delete(key)
251
252     # Methods that child classes must implement.
253
254     def exists(self, session_key):
255         """
256         Returns True if the given session_key already exists.
257         """
258         raise NotImplementedError
259
260     def create(self):
261         """
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.
265         """
266         raise NotImplementedError
267
268     def save(self, must_create=False):
269         """
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.
273         """
274         raise NotImplementedError
275
276     def delete(self, session_key=None):
277         """
278         Deletes the session data under this key. If the key is None, the
279         current session key value is used.
280         """
281         raise NotImplementedError
282
283     def load(self):
284         """
285         Loads the session data and returns a dictionary.
286         """
287         raise NotImplementedError
Note: See TracBrowser for help on using the browser.