""" A simple session management plugin for pyblosxom. Inspired by the mod_python Session module. This plugin tries to set a session cookie. As an alternative you can get the session id manually and store it in a hidden form field or the QS. See below for examples. If you make any changes to this plugin, please send a patch to so I can incorporate them. Thanks! To install: 1) Put session.py in your plugin directory. 2) In config.py add session to py['load_plugins'] 3) Add the following optional variable to config.py: py['session_dbmfile'] = '/tmp/pb_session.dbm' # this is the default Note: check the dependency listing below. Usage with cookies: Store variables in the session: def cb_whatever(args): request = args['request'] session = request.getSession() session['hello'] = "world" session.save() ... Retreive variables from the session: def cb_whatever(args): request = args['request'] session = request.getSession() msg = session['hello'] ... Usage without cookies: Create a session: def cb_whatever(args): request = args['request'] session = request.getSession() session['hello'] = "world" session.save() sessId = session.id() # store sessId in a hidden form field or add it to the QS or whatever. Retreive a session: def cb_whatever(args): request = args['request'] form = request.getForm() sessId = form['pbsid'].value session = request.getSession(sessId) msg = session['hello'] ... Todo: write doc strings ... :( Dependecies: - My compatibility plugin if you're not using pyblosxom 1.2+. Revisions: $Log: session.py,v $ Revision 1.2 2005/02/10 15:47:44 sar removed debugging statements Revision 1.1 2005/02/08 17:51:21 sar cvs server update Revision 1.8 2005/01/18 02:14:29 sar moved cgi stuff to the compatibility plugin Revision 1.7 2005/01/16 14:08:00 sar removed unnecessary local variables Revision 1.6 2004/12/17 02:51:18 sar fixed bugs in _init. indentation error caused that ._accessed was not updated and .cleanup() was only called if session was new. Revision 1.5 2004/12/16 03:09:54 sar fixed a bug in the CGI version of _setCookie, thanks Will. added config variable 'session_dbmfile'. Revision 1.4 2004/12/14 20:17:31 sar fixed bugs in _lock, _unlock and _setCookie Revision 1.3 2004/12/11 19:12:43 sar removed dependencies on my WSGI wrapper Revision 1.2 2004/12/05 12:07:19 sar changed callback from cb_renderer to cb_start, added switch to handle mod_python session or fall back to the handmade Session. Revision 1.1 2004/11/27 23:54:58 sar created $Id: session.py,v 1.2 2005/02/10 15:47:44 sar Exp $ """ __author__ = "Steven Armstrong " __version__ = "$Revision: 1.2 $ $Date: 2005/02/10 15:47:44 $" __url__ = "http://www.c-area.ch/code/" __description__ = "Simple session management" __license__ = "GPL 2+" # Python imports import sys import os import time import random import md5 import anydbm import tempfile from Cookie import SimpleCookie as Cookie try: import cPickle as pickle except ImportError: import pickle # Pyblosxom imports from Pyblosxom import tools # parameters COOKIE_NAME = "pbsid" # mod_python uses 'pysid'. don't use the same. DFT_TIMEOUT = 30*60 # 30 min CLEANUP_CHANCE = 1000 # cleanups have 1 in CLEANUP_CHANCE chance DBM_NAME = "pb_session.dbm" DBM_FILE = os.path.join(tempfile.gettempdir(), DBM_NAME) # the mod_python session seems to be a bit buggy when running with a WSGI wrapper. # looks like issues with the Session object directly accessing the output-stream. # so here's a flag to use the handmade session even if running under mod_python. USE_MP_SESSION = False def verify_installation(request): config = request.getConfiguration() retval = 1 try: import compatibility except ImportError: print "If you're not running the WSGI version of Pyblosxom" print "you'll need the 'compatibility.py' plugin." if not 'session_dbmfile' in config: print "Missing optional property: 'session_dbmfile'" print "Using the default of '%s'." % DBM_FILE return retval class Session(dict): def __init__(self, request, sid=None, dbmfile=None, timeout=None, lock=1): dict.__init__(self) self._request = request self._http = self._request.getHttp() self._sid = sid self._use_lock = lock self._new = 1 self._created = 0 self._accessed = 0 self._timeout = 0 self._locked = 0 self._invalid = 0 if dbmfile: self._dbmfile = dbmfile else: self._dbmfile = self._request.getConfiguration().get('session_dbmfile', DBM_FILE) self._init(timeout) def __del__(self): self._unlock() def _init(self, timeout): if not self._sid: # check to see if cookie exists cookie = self._getCookie() if cookie: self._sid = cookie.value if self._sid: # attempt to load ourselves if self.load(): self._new = 0 if self._new: # make a new session self._sid = self._generateSID() self._setCookie(self._makeCookie()) self._created = time.time() if timeout: self._timeout = timeout else: self._timeout = DFT_TIMEOUT self._accessed = time.time() # need cleanup? if random.randint(1, CLEANUP_CHANCE) == 1: self.cleanup() def _generateSID(self): """ Generates a new session ID. Uses different random sources to be difficult to guess. """ t = long(time.time()*10000) pid = os.getpid() rnd1 = random.randrange(1,999999999,1) rnd2 = random.randrange(1,999999999,1) ip = self._request.getHttp()['REMOTE_ADDR'] return md5.new("%d%d%d%d%s" % (t, pid, rnd1, rnd2, ip)).hexdigest() ### cookie related methods def _makeCookie(self): m = Cookie() m.load("%s=%s" % (COOKIE_NAME, self._sid)) c = m[COOKIE_NAME] #c['path'] = "/" c['path'] = self._http['SCRIPT_NAME'] return c def _getCookie(self): # check to see if cookie exists cookies = Cookie() if 'HTTP_COOKIE' in self._http: cookies.load(self._http['HTTP_COOKIE']) if cookies.has_key(COOKIE_NAME): return cookies[COOKIE_NAME] return None def _setCookie(self, c): k, v = c.output().split(":", 1) self._request.getResponse().addHeader(k, v.strip()) ### dbm related methods def _lock(self): # ???: does this work as expected? if self._use_lock: self._dbmfile_lock = open(self._dbmfile, "r+") tools.lock(self._dbmfile_lock, tools.LOCK_EX) self._locked = 1 def _unlock(self): # ???: does this work as expected? if self._use_lock and self._locked: tools.unlock(self._dbmfile_lock) self._dbmfile_lock.close() self._locked = 0 def _getDBM(self): result = anydbm.open(self._dbmfile, 'c') return result def _do_load(self): dbm = self._getDBM() self._lock() try: if dbm.has_key(self._sid): return pickle.loads(dbm[self._sid]) else: return None finally: dbm.close() self._unlock() def _do_save(self, dict): dbm = self._getDBM() self._lock() try: dbm[self._sid] = pickle.dumps(dict) finally: dbm.close() self._unlock() def _do_delete(self): dbm = self._getDBM() self._lock() try: try: del dbm[self._sid] except KeyError: pass finally: dbm.close() self._unlock() def _do_cleanup(self): dbm = self._getDBM() self._lock() try: old = [] try: s = dbm.first() while 1: key, val = s dict = pickle.loads(val) try: if (time.time() - dict["_accessed"]) > dict["_timeout"]: old.append(key) except KeyError: old.append(key) try: s = dbm.next() except KeyError: break except DBNotFoundError: pass for key in old: try: del dbm[key] except: pass finally: dbm.close() self._unlock() ### public methods def invalidate(self): c = self._makeCookie() c['expires'] = 0 self._setCookie(c) self.delete() self._invalid = 1 def load(self): dict = self._do_load() if dict == None: return 0 if (time.time() - dict["_accessed"]) > dict["_timeout"]: return 0 self._created = dict["_created"] self._accessed = dict["_accessed"] self._timeout = dict["_timeout"] self.update(dict["_data"]) return 1 def save(self): if not self._invalid: dict = {"_data" : self.copy(), "_created" : self._created, "_accessed": self._accessed, "_timeout" : self._timeout} self._do_save(dict) def delete(self): self._do_delete() self.clear() def is_new(self): return not not self._new def id(self): return self._sid def created(self): return self._created def last_accessed(self): return self._accessed def timeout(self): return self._timeout def set_timeout(self, secs): self._timeout = secs def cleanup(self): self._do_cleanup() #****************************** # Callbacks #****************************** def cb_start(args): request = args['request'] http = request.getHttp() if not request.getSession: def _getSession(sessId=None): if request._session == None: # running on mod_python and using WSGI wrapper if 'mod_python.request' in http and USE_MP_SESSION: from mod_python.Session import Session as MPSession request._session = MPSession(http['mod_python.request'], sid=sessId) # CGI version else: request._session = Session(request, sid=sessId) return request._session request.getSession = _getSession