diff options
Diffstat (limited to 'module/lib/beaker/session.py')
-rw-r--r-- | module/lib/beaker/session.py | 726 |
1 files changed, 0 insertions, 726 deletions
diff --git a/module/lib/beaker/session.py b/module/lib/beaker/session.py deleted file mode 100644 index d70a670eb..000000000 --- a/module/lib/beaker/session.py +++ /dev/null @@ -1,726 +0,0 @@ -import Cookie -import os -from datetime import datetime, timedelta -import time -from beaker.crypto import hmac as HMAC, hmac_sha1 as SHA1, md5 -from beaker import crypto, util -from beaker.cache import clsmap -from beaker.exceptions import BeakerException, InvalidCryptoBackendError -from base64 import b64encode, b64decode - - -__all__ = ['SignedCookie', 'Session'] - - -try: - import uuid - - def _session_id(): - return uuid.uuid4().hex -except ImportError: - import random - if hasattr(os, 'getpid'): - getpid = os.getpid - else: - def getpid(): - return '' - - def _session_id(): - id_str = "%f%s%f%s" % ( - time.time(), - id({}), - random.random(), - getpid() - ) - if util.py3k: - return md5( - md5( - id_str.encode('ascii') - ).hexdigest().encode('ascii') - ).hexdigest() - else: - return md5(md5(id_str).hexdigest()).hexdigest() - - -class SignedCookie(Cookie.BaseCookie): - """Extends python cookie to give digital signature support""" - def __init__(self, secret, input=None): - self.secret = secret.encode('UTF-8') - Cookie.BaseCookie.__init__(self, input) - - def value_decode(self, val): - val = val.strip('"') - sig = HMAC.new(self.secret, val[40:].encode('UTF-8'), SHA1).hexdigest() - - # Avoid timing attacks - invalid_bits = 0 - input_sig = val[:40] - if len(sig) != len(input_sig): - return None, val - - for a, b in zip(sig, input_sig): - invalid_bits += a != b - - if invalid_bits: - return None, val - else: - return val[40:], val - - def value_encode(self, val): - sig = HMAC.new(self.secret, val.encode('UTF-8'), SHA1).hexdigest() - return str(val), ("%s%s" % (sig, val)) - - -class Session(dict): - """Session object that uses container package for storage. - - :param invalidate_corrupt: How to handle corrupt data when loading. When - set to True, then corrupt data will be silently - invalidated and a new session created, - otherwise invalid data will cause an exception. - :type invalidate_corrupt: bool - :param use_cookies: Whether or not cookies should be created. When set to - False, it is assumed the user will handle storing the - session on their own. - :type use_cookies: bool - :param type: What data backend type should be used to store the underlying - session data - :param key: The name the cookie should be set to. - :param timeout: How long session data is considered valid. This is used - regardless of the cookie being present or not to determine - whether session data is still valid. - :type timeout: int - :param cookie_expires: Expiration date for cookie - :param cookie_domain: Domain to use for the cookie. - :param cookie_path: Path to use for the cookie. - :param secure: Whether or not the cookie should only be sent over SSL. - :param httponly: Whether or not the cookie should only be accessible by - the browser not by JavaScript. - :param encrypt_key: The key to use for the local session encryption, if not - provided the session will not be encrypted. - :param validate_key: The key used to sign the local encrypted session - - """ - def __init__(self, request, id=None, invalidate_corrupt=False, - use_cookies=True, type=None, data_dir=None, - key='beaker.session.id', timeout=None, cookie_expires=True, - cookie_domain=None, cookie_path='/', secret=None, - secure=False, namespace_class=None, httponly=False, - encrypt_key=None, validate_key=None, **namespace_args): - if not type: - if data_dir: - self.type = 'file' - else: - self.type = 'memory' - else: - self.type = type - - self.namespace_class = namespace_class or clsmap[self.type] - - self.namespace_args = namespace_args - - self.request = request - self.data_dir = data_dir - self.key = key - - self.timeout = timeout - self.use_cookies = use_cookies - self.cookie_expires = cookie_expires - - # Default cookie domain/path - self._domain = cookie_domain - self._path = cookie_path - self.was_invalidated = False - self.secret = secret - self.secure = secure - self.httponly = httponly - self.encrypt_key = encrypt_key - self.validate_key = validate_key - self.id = id - self.accessed_dict = {} - self.invalidate_corrupt = invalidate_corrupt - - if self.use_cookies: - cookieheader = request.get('cookie', '') - if secret: - try: - self.cookie = SignedCookie(secret, input=cookieheader) - except Cookie.CookieError: - self.cookie = SignedCookie(secret, input=None) - else: - self.cookie = Cookie.SimpleCookie(input=cookieheader) - - if not self.id and self.key in self.cookie: - self.id = self.cookie[self.key].value - - self.is_new = self.id is None - if self.is_new: - self._create_id() - self['_accessed_time'] = self['_creation_time'] = time.time() - else: - try: - self.load() - except Exception, e: - if invalidate_corrupt: - util.warn( - "Invalidating corrupt session %s; " - "error was: %s. Set invalidate_corrupt=False " - "to propagate this exception." % (self.id, e)) - self.invalidate() - else: - raise - - def has_key(self, name): - return name in self - - def _set_cookie_values(self, expires=None): - self.cookie[self.key] = self.id - if self._domain: - self.cookie[self.key]['domain'] = self._domain - if self.secure: - self.cookie[self.key]['secure'] = True - self._set_cookie_http_only() - self.cookie[self.key]['path'] = self._path - - self._set_cookie_expires(expires) - - def _set_cookie_expires(self, expires): - if expires is None: - if self.cookie_expires is not True: - if self.cookie_expires is False: - expires = datetime.fromtimestamp(0x7FFFFFFF) - elif isinstance(self.cookie_expires, timedelta): - expires = datetime.utcnow() + self.cookie_expires - elif isinstance(self.cookie_expires, datetime): - expires = self.cookie_expires - else: - raise ValueError("Invalid argument for cookie_expires: %s" - % repr(self.cookie_expires)) - else: - expires = None - if expires is not None: - if not self.cookie or self.key not in self.cookie: - self.cookie[self.key] = self.id - self.cookie[self.key]['expires'] = \ - expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT") - return expires - - def _update_cookie_out(self, set_cookie=True): - self.request['cookie_out'] = self.cookie[self.key].output(header='') - self.request['set_cookie'] = set_cookie - - def _set_cookie_http_only(self): - try: - if self.httponly: - self.cookie[self.key]['httponly'] = True - except Cookie.CookieError, e: - if 'Invalid Attribute httponly' not in str(e): - raise - util.warn('Python 2.6+ is required to use httponly') - - def _create_id(self, set_new=True): - self.id = _session_id() - - if set_new: - self.is_new = True - self.last_accessed = None - if self.use_cookies: - self._set_cookie_values() - sc = set_new == False - self._update_cookie_out(set_cookie=sc) - - @property - def created(self): - return self['_creation_time'] - - def _set_domain(self, domain): - self['_domain'] = domain - self.cookie[self.key]['domain'] = domain - self._update_cookie_out() - - def _get_domain(self): - return self._domain - - domain = property(_get_domain, _set_domain) - - def _set_path(self, path): - self['_path'] = self._path = path - self.cookie[self.key]['path'] = path - self._update_cookie_out() - - def _get_path(self): - return self._path - - path = property(_get_path, _set_path) - - def _encrypt_data(self, session_data=None): - """Serialize, encipher, and base64 the session dict""" - session_data = session_data or self.copy() - if self.encrypt_key: - nonce = b64encode(os.urandom(6))[:8] - encrypt_key = crypto.generateCryptoKeys(self.encrypt_key, - self.validate_key + nonce, 1) - data = util.pickle.dumps(session_data, 2) - return nonce + b64encode(crypto.aesEncrypt(data, encrypt_key)) - else: - data = util.pickle.dumps(session_data, 2) - return b64encode(data) - - def _decrypt_data(self, session_data): - """Bas64, decipher, then un-serialize the data for the session - dict""" - if self.encrypt_key: - try: - nonce = session_data[:8] - encrypt_key = crypto.generateCryptoKeys(self.encrypt_key, - self.validate_key + nonce, 1) - payload = b64decode(session_data[8:]) - data = crypto.aesDecrypt(payload, encrypt_key) - except: - # As much as I hate a bare except, we get some insane errors - # here that get tossed when crypto fails, so we raise the - # 'right' exception - if self.invalidate_corrupt: - return None - else: - raise - try: - return util.pickle.loads(data) - except: - if self.invalidate_corrupt: - return None - else: - raise - else: - data = b64decode(session_data) - return util.pickle.loads(data) - - def _delete_cookie(self): - self.request['set_cookie'] = True - expires = datetime.utcnow() - timedelta(365) - self._set_cookie_values(expires) - self._update_cookie_out() - - def delete(self): - """Deletes the session from the persistent storage, and sends - an expired cookie out""" - if self.use_cookies: - self._delete_cookie() - self.clear() - - def invalidate(self): - """Invalidates this session, creates a new session id, returns - to the is_new state""" - self.clear() - 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(self.id, - data_dir=self.data_dir, - digest_filenames=False, - **self.namespace_args) - now = time.time() - if self.use_cookies: - self.request['set_cookie'] = True - - self.namespace.acquire_read_lock() - timed_out = False - try: - self.clear() - try: - session_data = self.namespace['session'] - - if (session_data is not None and self.encrypt_key): - session_data = self._decrypt_data(session_data) - - # Memcached always returns a key, its None when its not - # present - if session_data is None: - session_data = { - '_creation_time': now, - '_accessed_time': now - } - self.is_new = True - except (KeyError, TypeError): - session_data = { - '_creation_time': now, - '_accessed_time': now - } - self.is_new = True - - if session_data is None or len(session_data) == 0: - session_data = { - '_creation_time': now, - '_accessed_time': now - } - self.is_new = True - - if self.timeout is not None and \ - now - session_data['_accessed_time'] > self.timeout: - timed_out = True - else: - # Properly set the last_accessed time, which is different - # than the *currently* _accessed_time - if self.is_new or '_accessed_time' not in session_data: - self.last_accessed = None - else: - self.last_accessed = session_data['_accessed_time'] - - # Update the current _accessed_time - session_data['_accessed_time'] = now - - # Set the path if applicable - if '_path' in session_data: - self._path = session_data['_path'] - self.update(session_data) - self.accessed_dict = session_data.copy() - finally: - self.namespace.release_read_lock() - if timed_out: - self.invalidate() - - def save(self, accessed_only=False): - """Saves the data for this session to persistent storage - - If accessed_only is True, then only the original data loaded - at the beginning of the request will be saved, with the updated - last accessed time. - - """ - # Look to see if its a new session that was only accessed - # Don't save it under that case - if accessed_only and self.is_new: - return None - - # this session might not have a namespace yet or the session id - # might have been regenerated - if not hasattr(self, 'namespace') or self.namespace.namespace != self.id: - self.namespace = self.namespace_class( - self.id, - data_dir=self.data_dir, - digest_filenames=False, - **self.namespace_args) - - self.namespace.acquire_write_lock(replace=True) - try: - if accessed_only: - data = dict(self.accessed_dict.items()) - else: - data = dict(self.items()) - - if self.encrypt_key: - data = self._encrypt_data(data) - - # Save the data - if not data and 'session' in self.namespace: - del self.namespace['session'] - else: - self.namespace['session'] = data - finally: - self.namespace.release_write_lock() - if self.use_cookies and self.is_new: - self.request['set_cookie'] = True - - def revert(self): - """Revert the session to its original state from its first - access in the request""" - self.clear() - self.update(self.accessed_dict) - - def regenerate_id(self): - """ - creates a new session id, retains all session data - - Its a good security practice to regnerate the id after a client - elevates priviliges. - - """ - self._create_id(set_new=False) - - # TODO: I think both these methods should be removed. They're from - # the original mod_python code i was ripping off but they really - # have no use here. - 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() - - -class CookieSession(Session): - """Pure cookie-based session - - Options recognized when using cookie-based sessions are slightly - more restricted than general sessions. - - :param key: The name the cookie should be set to. - :param timeout: How long session data is considered valid. This is used - regardless of the cookie being present or not to determine - whether session data is still valid. - :type timeout: int - :param cookie_expires: Expiration date for cookie - :param cookie_domain: Domain to use for the cookie. - :param cookie_path: Path to use for the cookie. - :param secure: Whether or not the cookie should only be sent over SSL. - :param httponly: Whether or not the cookie should only be accessible by - the browser not by JavaScript. - :param encrypt_key: The key to use for the local session encryption, if not - provided the session will not be encrypted. - :param validate_key: The key used to sign the local encrypted session - - """ - def __init__(self, request, key='beaker.session.id', timeout=None, - cookie_expires=True, cookie_domain=None, cookie_path='/', - encrypt_key=None, validate_key=None, secure=False, - httponly=False, **kwargs): - - if not crypto.has_aes and encrypt_key: - raise InvalidCryptoBackendError("No AES library is installed, can't generate " - "encrypted cookie-only Session.") - - self.request = request - self.key = key - self.timeout = timeout - self.cookie_expires = cookie_expires - self.encrypt_key = encrypt_key - self.validate_key = validate_key - self.request['set_cookie'] = False - self.secure = secure - self.httponly = httponly - self._domain = cookie_domain - self._path = cookie_path - - try: - cookieheader = request['cookie'] - except KeyError: - cookieheader = '' - - if validate_key is None: - raise BeakerException("No validate_key specified for Cookie only " - "Session.") - - try: - self.cookie = SignedCookie(validate_key, input=cookieheader) - except Cookie.CookieError: - self.cookie = SignedCookie(validate_key, input=None) - - self['_id'] = _session_id() - self.is_new = True - - # If we have a cookie, load it - if self.key in self.cookie and self.cookie[self.key].value is not None: - self.is_new = False - try: - cookie_data = self.cookie[self.key].value - self.update(self._decrypt_data(cookie_data)) - self._path = self.get('_path', '/') - except: - pass - if self.timeout is not None and time.time() - \ - self['_accessed_time'] > self.timeout: - self.clear() - self.accessed_dict = self.copy() - self._create_cookie() - - def created(self): - return self['_creation_time'] - created = property(created) - - def id(self): - return self['_id'] - id = property(id) - - def _set_domain(self, domain): - self['_domain'] = domain - self._domain = domain - - def _get_domain(self): - return self._domain - - domain = property(_get_domain, _set_domain) - - def _set_path(self, path): - self['_path'] = self._path = path - - def _get_path(self): - return self._path - - path = property(_get_path, _set_path) - - def save(self, accessed_only=False): - """Saves the data for this session to persistent storage""" - if accessed_only and self.is_new: - return - if accessed_only: - self.clear() - self.update(self.accessed_dict) - self._create_cookie() - - def expire(self): - """Delete the 'expires' attribute on this Session, if any.""" - - self.pop('_expires', None) - - def _create_cookie(self): - if '_creation_time' not in self: - self['_creation_time'] = time.time() - if '_id' not in self: - self['_id'] = _session_id() - self['_accessed_time'] = time.time() - - val = self._encrypt_data() - if len(val) > 4064: - raise BeakerException("Cookie value is too long to store") - - self.cookie[self.key] = val - - if '_expires' in self: - expires = self['_expires'] - else: - expires = None - expires = self._set_cookie_expires(expires) - if expires is not None: - self['_expires'] = expires - - if '_domain' in self: - self.cookie[self.key]['domain'] = self['_domain'] - elif self._domain: - self.cookie[self.key]['domain'] = self._domain - if self.secure: - self.cookie[self.key]['secure'] = True - self._set_cookie_http_only() - - self.cookie[self.key]['path'] = self.get('_path', '/') - - self.request['cookie_out'] = self.cookie[self.key].output(header='') - self.request['set_cookie'] = True - - def delete(self): - """Delete the cookie, and clear the session""" - # Send a delete cookie request - self._delete_cookie() - self.clear() - - def invalidate(self): - """Clear the contents and start a new session""" - self.clear() - self['_id'] = _session_id() - - -class SessionObject(object): - """Session proxy/lazy creator - - This object proxies access to the actual session object, so that in - the case that the session hasn't been used before, it will be - setup. This avoid creating and loading the session from persistent - storage unless its actually used during the request. - - """ - def __init__(self, environ, **params): - self.__dict__['_params'] = params - self.__dict__['_environ'] = environ - self.__dict__['_sess'] = None - self.__dict__['_headers'] = {} - - def _session(self): - """Lazy initial creation of session object""" - if self.__dict__['_sess'] is None: - params = self.__dict__['_params'] - environ = self.__dict__['_environ'] - self.__dict__['_headers'] = req = {'cookie_out': None} - req['cookie'] = environ.get('HTTP_COOKIE') - if params.get('type') == 'cookie': - self.__dict__['_sess'] = CookieSession(req, **params) - else: - self.__dict__['_sess'] = Session(req, use_cookies=True, - **params) - return self.__dict__['_sess'] - - def __getattr__(self, attr): - return getattr(self._session(), attr) - - def __setattr__(self, attr, value): - setattr(self._session(), attr, value) - - def __delattr__(self, name): - self._session().__delattr__(name) - - def __getitem__(self, key): - return self._session()[key] - - def __setitem__(self, key, value): - self._session()[key] = value - - def __delitem__(self, key): - self._session().__delitem__(key) - - def __repr__(self): - return self._session().__repr__() - - def __iter__(self): - """Only works for proxying to a dict""" - return iter(self._session().keys()) - - def __contains__(self, key): - return key in self._session() - - def has_key(self, key): - return key in self._session() - - def get_by_id(self, id): - """Loads a session given a session ID""" - params = self.__dict__['_params'] - session = Session({}, use_cookies=False, id=id, **params) - if session.is_new: - return None - return session - - def save(self): - self.__dict__['_dirty'] = True - - def delete(self): - self.__dict__['_dirty'] = True - self._session().delete() - - def persist(self): - """Persist the session to the storage - - If its set to autosave, then the entire session will be saved - regardless of if save() has been called. Otherwise, just the - accessed time will be updated if save() was not called, or - the session will be saved if save() was called. - - """ - if self.__dict__['_params'].get('auto'): - self._session().save() - else: - if self.__dict__.get('_dirty'): - self._session().save() - else: - self._session().save(accessed_only=True) - - def dirty(self): - return self.__dict__.get('_dirty', False) - - def accessed(self): - """Returns whether or not the session has been accessed""" - return self.__dict__['_sess'] is not None |