6 import os, time, datetime, random, base64
8 from copy import deepcopy
10 import cPickle as pickle
24 'Session', 'SessionExpired',
25 'Store', 'DiskStore', 'DBStore',
28 web.config.session_parameters = utils.storage({
29 'cookie_name': 'webpy_session_id',
30 'cookie_domain': None,
32 'timeout': 86400, #24 * 60 * 60, # 24 hours in seconds
33 'ignore_expiry': True,
34 'ignore_change_ip': True,
35 'secret_key': 'fLjUfxqXtfNoIldA0A0J',
36 'expired_message': 'Session expired',
41 class SessionExpired(web.HTTPError):
42 def __init__(self, message):
43 web.HTTPError.__init__(self, '200 OK', {}, data=message)
45 class Session(object):
46 """Session management for web.py
49 "store", "_initializer", "_last_cleanup_time", "_config", "_data",
50 "__getitem__", "__setitem__", "__delitem__"
53 def __init__(self, app, store, initializer=None):
55 self._initializer = initializer
56 self._last_cleanup_time = 0
57 self._config = utils.storage(web.config.session_parameters)
58 self._data = utils.threadeddict()
60 self.__getitem__ = self._data.__getitem__
61 self.__setitem__ = self._data.__setitem__
62 self.__delitem__ = self._data.__delitem__
65 app.add_processor(self._processor)
67 def __contains__(self, name):
68 return name in self._data
70 def __getattr__(self, name):
71 return getattr(self._data, name)
73 def __setattr__(self, name, value):
74 if name in self.__slots__:
75 object.__setattr__(self, name, value)
77 setattr(self._data, name, value)
79 def __delattr__(self, name):
80 delattr(self._data, name)
82 def _processor(self, handler):
83 """Application processor to setup session for every request"""
93 """Load the session from the store, by the id from cookie"""
94 cookie_name = self._config.cookie_name
95 cookie_domain = self._config.cookie_domain
96 cookie_path = self._config.cookie_path
97 httponly = self._config.httponly
98 self.session_id = web.cookies().get(cookie_name)
100 # protection against session_id tampering
101 if self.session_id and not self._valid_session_id(self.session_id):
102 self.session_id = None
106 d = self.store[self.session_id]
110 if not self.session_id:
111 self.session_id = self._generate_session_id()
113 if self._initializer:
114 if isinstance(self._initializer, dict):
115 self.update(deepcopy(self._initializer))
116 elif hasattr(self._initializer, '__call__'):
121 def _check_expiry(self):
123 if self.session_id and self.session_id not in self.store:
124 if self._config.ignore_expiry:
125 self.session_id = None
127 return self.expired()
129 def _validate_ip(self):
130 # check for change of IP
131 if self.session_id and self.get('ip', None) != web.ctx.ip:
132 if not self._config.ignore_change_ip:
133 return self.expired()
136 if not self.get('_killed'):
137 self._setcookie(self.session_id)
138 self.store[self.session_id] = dict(self._data)
140 self._setcookie(self.session_id, expires=-1)
142 def _setcookie(self, session_id, expires='', **kw):
143 cookie_name = self._config.cookie_name
144 cookie_domain = self._config.cookie_domain
145 cookie_path = self._config.cookie_path
146 httponly = self._config.httponly
147 secure = self._config.secure
148 web.setcookie(cookie_name, session_id, expires=expires, domain=cookie_domain, httponly=httponly, secure=secure, path=cookie_path)
150 def _generate_session_id(self):
151 """Generate a random id for session"""
154 rand = os.urandom(16)
156 secret_key = self._config.secret_key
157 session_id = sha1("%s%s%s%s" %(rand, now, utils.safestr(web.ctx.ip), secret_key))
158 session_id = session_id.hexdigest()
159 if session_id not in self.store:
163 def _valid_session_id(self, session_id):
164 rx = utils.re_compile('^[0-9a-fA-F]+$')
165 return rx.match(session_id)
168 """Cleanup the stored sessions"""
169 current_time = time.time()
170 timeout = self._config.timeout
171 if current_time - self._last_cleanup_time > timeout:
172 self.store.cleanup(timeout)
173 self._last_cleanup_time = current_time
176 """Called when an expired session is atime"""
179 raise SessionExpired(self._config.expired_message)
182 """Kill the session, make it no longer available"""
183 del self.store[self.session_id]
187 """Base class for session stores"""
189 def __contains__(self, key):
190 raise NotImplementedError
192 def __getitem__(self, key):
193 raise NotImplementedError
195 def __setitem__(self, key, value):
196 raise NotImplementedError
198 def cleanup(self, timeout):
199 """removes all the expired sessions"""
200 raise NotImplementedError
202 def encode(self, session_dict):
203 """encodes session dict as a string"""
204 pickled = pickle.dumps(session_dict)
205 return base64.encodestring(pickled)
207 def decode(self, session_data):
208 """decodes the data to get back the session dict """
209 pickled = base64.decodestring(session_data)
210 return pickle.loads(pickled)
212 class DiskStore(Store):
214 Store for saving a session on disk.
217 >>> root = tempfile.mkdtemp()
218 >>> s = DiskStore(root)
225 Traceback (most recent call last):
229 def __init__(self, root):
230 # if the storage root doesn't exists, create it.
231 if not os.path.exists(root):
233 os.path.abspath(root)
237 def _get_path(self, key):
238 if os.path.sep in key:
239 raise ValueError, "Bad key: %s" % repr(key)
240 return os.path.join(self.root, key)
242 def __contains__(self, key):
243 path = self._get_path(key)
244 return os.path.exists(path)
246 def __getitem__(self, key):
247 path = self._get_path(key)
248 if os.path.exists(path):
249 pickled = open(path).read()
250 return self.decode(pickled)
254 def __setitem__(self, key, value):
255 path = self._get_path(key)
256 pickled = self.encode(value)
266 def __delitem__(self, key):
267 path = self._get_path(key)
268 if os.path.exists(path):
271 def cleanup(self, timeout):
273 for f in os.listdir(self.root):
274 path = self._get_path(f)
275 atime = os.stat(path).st_atime
276 if now - atime > timeout :
279 class DBStore(Store):
280 """Store for saving a session in database
281 Needs a table with the following columns:
283 session_id CHAR(128) UNIQUE NOT NULL,
284 atime DATETIME NOT NULL default current_timestamp,
287 def __init__(self, db, table_name):
289 self.table = table_name
291 def __contains__(self, key):
292 data = self.db.select(self.table, where="session_id=$key", vars=locals())
293 return bool(list(data))
295 def __getitem__(self, key):
296 now = datetime.datetime.now()
298 s = self.db.select(self.table, where="session_id=$key", vars=locals())[0]
299 self.db.update(self.table, where="session_id=$key", atime=now, vars=locals())
303 return self.decode(s.data)
305 def __setitem__(self, key, value):
306 pickled = self.encode(value)
307 now = datetime.datetime.now()
309 self.db.update(self.table, where="session_id=$key", data=pickled, vars=locals())
311 self.db.insert(self.table, False, session_id=key, data=pickled )
313 def __delitem__(self, key):
314 self.db.delete(self.table, where="session_id=$key", vars=locals())
316 def cleanup(self, timeout):
317 timeout = datetime.timedelta(timeout/(24.0*60*60)) #timedelta takes numdays as arg
318 last_allowed_time = datetime.datetime.now() - timeout
319 self.db.delete(self.table, where="$last_allowed_time > atime", vars=locals())
322 """Store for saving session using `shelve` module.
325 store = ShelfStore(shelve.open('session.shelf'))
327 XXX: is shelve thread-safe?
329 def __init__(self, shelf):
332 def __contains__(self, key):
333 return key in self.shelf
335 def __getitem__(self, key):
336 atime, v = self.shelf[key]
337 self[key] = v # update atime
340 def __setitem__(self, key, value):
341 self.shelf[key] = time.time(), value
343 def __delitem__(self, key):
349 def cleanup(self, timeout):
351 for k in self.shelf.keys():
352 atime, v = self.shelf[k]
353 if now - atime > timeout :
356 if __name__ == '__main__' :