# $Id: session.py 2041 2006-02-05 19:02:14Z zzzeek $ # session.py - session management for Myghty # Copyright (C) 2004, 2005 Michael Bayer mike_mp@zzzcomputing.com # # This module is part of Myghty and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php # # import Cookie import hmac, md5, time, random, os, re, UserDict, datetime from myghty.container import * from myghty.util import * __all__ = ['SignedCookie', 'Session', 'MyghtySessionArgs'] class SignedCookie(Cookie.BaseCookie): "extends python cookie to give digital signature support" def __init__(self, secret, input=None): self.secret = secret Cookie.BaseCookie.__init__(self, input) def value_decode(self, val): sig = val[0:32] value = val[32:] if hmac.new(self.secret, value).hexdigest() != sig: return None, val return val[32:], val def value_encode(self, val): return val, ("%s%s" % (hmac.new(self.secret, val).hexdigest(), val)) class Session(UserDict.DictMixin): "session object that uses container package for storage" def __init__(self, request, id = None, invalidate_corrupt = False, use_cookies = True, type = None, data_dir = None, key = 'myghty_session_id', timeout = None, cookie_expires=True, secret = None, log_file = None, namespace_class = None, **params): if type is None: if data_dir is None: self.type = 'memory' else: self.type = 'file' else: self.type = type if namespace_class is None: self.namespace_class = container_registry(self.type, 'NamespaceManager') else: self.namespace_class = namespace_class self.params = params self.request = request self.data_dir = data_dir self.key = key self.timeout = timeout self.use_cookies = use_cookies self.cookie_expires = cookie_expires self.log_file = log_file self.was_invalidated = False self.secret = secret self.id = id if self.use_cookies: try: cookieheader = request.headers_in['cookie'] except KeyError: cookieheader = '' if secret is not None: try: self.cookie = SignedCookie(secret, input = cookieheader) except Cookie.CookieError: self.cookie = SignedCookie(secret, input = None) else: self.cookie = Cookie.SimpleCookie(input = cookieheader) if self.id is None and self.cookie.has_key(self.key): self.id = self.cookie[self.key].value if self.id is None: self._create_id() else: self.is_new = False try: self.load() except: if invalidate_corrupt: self.invalidate() else: raise def _create_id(self): self.id = md5.new( md5.new("%f%s%f%d" % (time.time(), id({}), random.random(), os.getpid()) ).hexdigest(), ).hexdigest() self.is_new = True if self.use_cookies: self.cookie[self.key] = self.id self.cookie[self.key]['path'] = '/' if self.cookie_expires is not True: if self.cookie_expires is False: expires = datetime.datetime.fromtimestamp( 0x7FFFFFFF ) elif isinstance(self.cookie_expires, datetime.timedelta): expires = datetime.datetime.today() + self.cookie_expires elif isinstance(self.cookie_expires, datetime.datetime): expires = self.cookie_expires else: raise ValueError("Invalid argument for cookie_expires: %s" % repr(self.cookie_expires)) self.cookie[self.key]['expires'] = expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" ) self.request.headers_out.add('set-cookie', self.cookie[self.key].output(header='')) created = property(lambda self: self.dict['_creation_time']) def delete(self): """deletes the persistent storage for this session, but remains valid. """ self.namespace.acquire_write_lock() try: for k in self.namespace.keys(): if not re.match(r'_creation_time|_accessed_time', k): del self.namespace[k] self.namespace['_accessed_time'] = time.time() finally: self.namespace.release_write_lock() def __getitem__(self, key): return self.dict.__getitem__(key) def __setitem__(self, key, value): self.dict.__setitem__(key, value) def __delitem__(self, key): del self.dict[key] def keys(self): return self.dict.keys() def __contains__(self, key): return self.dict.has_key(key) def has_key(self, key): return self.dict.has_key(key) def __iter__(self): return iter(self.dict.keys()) def iteritems(self): return self.dict.iteritems() def invalidate(self): "invalidates this session, creates a new session id, returns to the is_new state" namespace = self.namespace namespace.acquire_write_lock() try: namespace.remove() finally: namespace.release_write_lock() self.was_invalidated = True self._create_id() self.load() def load(self): "loads the data from this session from persistent storage" self.namespace = self.namespace_class(NamespaceContext(log_file = self.log_file), self.id, data_dir = self.data_dir, digest_filenames = False, **self.params) namespace = self.namespace namespace.acquire_write_lock() try: self.debug("session loading keys") self.dict = {} now = time.time() if not namespace.has_key('_creation_time'): namespace['_creation_time'] = now try: self.accessed = namespace['_accessed_time'] namespace['_accessed_time'] = now except KeyError: namespace['_accessed_time'] = self.accessed = now if self.timeout is not None and now - self.accessed > self.timeout: self.invalidate() else: for k in namespace.keys(): self.dict[k] = namespace[k] finally: namespace.release_write_lock() def save(self): "saves the data for this session to persistent storage" self.namespace.acquire_write_lock() try: self.debug("session saving keys") todel = [] for k in self.namespace.keys(): if not self.dict.has_key(k): todel.append(k) for k in todel: del self.namespace[k] for k in self.dict.keys(): self.namespace[k] = self.dict[k] self.namespace['_accessed_time'] = time.time() finally: self.namespace.release_write_lock() def lock(self): """locks this session against other processes/threads. this is automatic when load/save is called. ***use with caution*** and always with a corresponding 'unlock' inside a "finally:" block, as a stray lock typically cannot be unlocked without shutting down the whole application. """ self.namespace.acquire_write_lock() def unlock(self): """unlocks this session against other processes/threads. this is automatic when load/save is called. ***use with caution*** and always within a "finally:" block, as a stray lock typically cannot be unlocked without shutting down the whole application. """ self.namespace.release_write_lock() def debug(self, message): if self.log_file is not None: self.log_file.write(message) class MyghtySessionArgs(PrefixArgs): def __init__(self, data_dir = None, **params): PrefixArgs.__init__(self, 'session_') self.set_prefix_params(**params) if not self.params.has_key('data_dir') and data_dir is not None: self.params['data_dir'] = os.path.join(data_dir, 'sessions') def get_session(self, request, **params): return Session(request, **self.get_params(**params)) def clone(self, **params): p = self.get_params(**params) arg = MyghtySessionArgs() arg.params = p return arg