def _makeOne(self, wrapped): from pyramid.session import manage_accessed return manage_accessed(wrapped)
class SessionCookie(dict): _cookie_name = 'session' _reissue_time = 0 # dirty flag _dirty = False def __init__(self, request): # log.debug('SessionCookie::__init__()') self._request = request # flags new = True # assume that this is a new session # time base flags now = time.time() created = renewed = accessed = now cookie_value = self._request.cookies.get(self._cookie_name) state = {} value = None if cookie_value is not None: try: jwt_key = self._request.registry.settings['jwt.secret'] value = jwt.decode(cookie_value, key=jwt_key, algorithms=['HS256'], verify=True) except ValueError as e: log.error(e) value = None if value is not None: try: created = value['iat'] if 'iat' in value else now renewed = now accessed = value[ 'updated_at'] if 'updated_at' in value else now new = False state = value except (TypeError, ValueError) as e: log.error(e) state = {} # check if session timed out if now - renewed > timeout: # session has timed out, expire the session log.debug('session expired') state = {} self.created = created self.accessed = accessed self.renewed = renewed self.new = new dict.__init__(self, state) # actually set the cookie def _set_cookie(self, response): # do not set cookie on exception if self._request.exception is not None: if not isinstance(self._request.exception, exception.HTTPFound): return False jwt_key = self._request.registry.settings['jwt.secret'] copy = dict(self) copy['iat'] = self.created copy['updated_at'] = self.renewed cookie_value = jwt.encode(copy, key=jwt_key, algorithm='HS256') if len(cookie_value) > 4064: raise ValueError('Cookie value is too long to store (%s bytes)' ) % len(cookie_value) response.set_cookie( self._cookie_name, value=cookie_value, max_age=max_age, path='/', # domain = secure=False, # set to true when using https httponly=True, # ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite # samesite = 'Strict' samesite='Lax') return True # ISession methods def changed(self): if not self._dirty: self._dirty = True def set_cookie_callback(request, response): self._set_cookie(response) self._request = None # explicitly break cycle for gc self._request.add_response_callback(set_cookie_callback) def invalidate(self): self.clear() def set_cookie_clear_callback(request, response): response.set_cookie( self._cookie_name, value='', max_age=0, path='/', secure=False, # set to true when using https httponly=True, # ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite # samesite = 'Strict' samesite='Lax') self._request = None return True self._request.add_response_callback(set_cookie_clear_callback) # non-modifying dictionary methods get = manage_accessed(dict.get) __getitem__ = manage_accessed(dict.__getitem__) items = manage_accessed(dict.items) values = manage_accessed(dict.values) keys = manage_accessed(dict.keys) __contains__ = manage_accessed(dict.__contains__) __len__ = manage_accessed(dict.__len__) __iter__ = manage_accessed(dict.__iter__) # modifying dictionary methods clear = manage_changed(dict.clear) update = manage_changed(dict.update) setdefault = manage_changed(dict.setdefault) pop = manage_changed(dict.pop) popitem = manage_changed(dict.popitem) __setitem__ = manage_changed(dict.__setitem__) __delitem__ = manage_changed(dict.__delitem__)
class MongoSession(dict): """ Dictionary-like session object, based on CookieSession """ # configuration parameters _collection = collection _to_pickle = to_pickle _cookie_name = cookie_name _cookie_max_age = max_age _cookie_path = path _cookie_domain = domain _cookie_secure = secure _cookie_httponly = httponly _cookie_on_exception = set_on_exception _timeout = timeout _reissue_time = reissue_time # dirty flag _dirty = False def __init__(self, request): self.request = request now = time.time() created = renewed = now new = True value = None state = {} cookieval = self._get_cookie() if cookieval: value = self._collection.find_one({'_id': cookieval}) if value is not None: try: renewed = float(value.get('accessed')) created = float(value.get('created')) sval = value.get('value') pickled = value.get('pickled') state = sval if pickled: if not PY3: sval = sval.encode('utf-8') # dammit state = pickle.loads(sval) new = False except (TypeError, ValueError, pickle.PickleError): # value failed to unpack properly or renewed was not # a numeric type so we'll fail deserialization here state = {} if self._timeout is not None: if now - renewed > self._timeout: # expire the session because it was not renewed # before the timeout threshold state = {} self.created = created self.accessed = renewed self.renewed = renewed self.new = new dict.__init__(self, state) # ISession methods def changed(self): if not self._dirty: self._dirty = True def set_cookie_callback(request, response): self._set_cookie(response) self.request = None # explicitly break cycle for gc self.request.add_response_callback(set_cookie_callback) def invalidate(self): cookieval = self._get_cookie() self._collection.delete_one({'_id': cookieval}) self.clear() # XXX probably needs to unset cookie. But... # non-modifying dictionary methods get = manage_accessed(dict.get) __getitem__ = manage_accessed(dict.__getitem__) items = manage_accessed(dict.items) values = manage_accessed(dict.values) keys = manage_accessed(dict.keys) __contains__ = manage_accessed(dict.__contains__) __len__ = manage_accessed(dict.__len__) __iter__ = manage_accessed(dict.__iter__) if not PY3: iteritems = manage_accessed(dict.iteritems) itervalues = manage_accessed(dict.itervalues) iterkeys = manage_accessed(dict.iterkeys) has_key = manage_accessed(dict.has_key) # modifying dictionary methods clear = manage_changed(dict.clear) update = manage_changed(dict.update) setdefault = manage_changed(dict.setdefault) pop = manage_changed(dict.pop) popitem = manage_changed(dict.popitem) __setitem__ = manage_changed(dict.__setitem__) __delitem__ = manage_changed(dict.__delitem__) # flash API methods @manage_changed def flash(self, msg, queue='', allow_duplicate=True): storage = self.setdefault('_f_' + queue, []) if allow_duplicate or (msg not in storage): storage.append(msg) @manage_changed def pop_flash(self, queue=''): storage = self.pop('_f_' + queue, []) return storage @manage_accessed def peek_flash(self, queue=''): storage = self.get('_f_' + queue, []) return storage # CSRF API methods @manage_changed def new_csrf_token(self): token = get_random() self['_csrft_'] = token return token @manage_accessed def get_csrf_token(self): token = self.get('_csrft_', None) if token is None: token = self.new_csrf_token() return token # non-API methods def _get_cookie(self): # cookie value, not value itself value = self.request.cookies.get(self._cookie_name, '') value = re.sub('[^a-f0-9]', '', value) return value def _set_cookie(self, response): if not self._cookie_on_exception: exception = getattr(self.request, 'exception', None) if exception is not None: # dont set a cookie during exceptions return False cookieval = self.new and get_random() or self._get_cookie() if not cookieval: return False value = self._to_pickle and pickle.dumps(dict(self)) or dict(self) data = dict(accessed=self.accessed, created=self.created, value=value, pickled=self._to_pickle, _id=cookieval) self._collection.replace_one({'_id': cookieval}, data, upsert=True) response.set_cookie(self._cookie_name, value=cookieval, max_age=self._cookie_max_age, path=self._cookie_path, domain=self._cookie_domain, secure=self._cookie_secure, httponly=self._cookie_httponly) return True
class PluggableSession(dict): """ Dictionary-like session object """ # configuration parameters _cookie_on_exception = set_on_exception _timeout = timeout _reissue_time = reissue_time # dirty flag _dirty = False def __init__(self, request): self._cookie = CookieHelper( secret, salt, cookie_name, secure=secure, max_age=max_age, httponly=httponly, path=path, domains=domain, hashalg=hashalg, ) self._session_id = None self.request = request reg = request.registry plug = reg.queryUtility(IPlugSession) if plug is None: raise RuntimeError( 'Unable to find any registered IPlugSession') now = time.time() created = renewed = now new = True value = None state = {} # Get the session_id self._session_id = self._cookie.bind(request).get_value() if self._session_id is not None: try: sess_val = plug.loads(self, request) value = serializer.loads(bytes_(sess_val)) except ValueError: value = None # Cleanup the session, since it failed to deserialize plug.clear(self, request) self._session_id = None if value is not None: try: rval, cval, sval = value renewed = float(rval) created = float(cval) state = sval new = False except (TypeError, ValueError): # value failed to unpack properly or renewed was not # a numeric type so we'll fail deserialization here state = {} # Clean up the session since it failed to unpack plug.clear(self, request) self._session_id = None if self._timeout is not None: if now - renewed > self._timeout: # expire the session because it was not renewed # before the timeout threshold state = {} # Session expired, cleanup this session plug.clear(self, request) self._session_id = None # Generate a new session id if self._session_id is None: self._generate_new_id() self.created = created self.accessed = renewed self.renewed = renewed self.new = new self._plug = plug dict.__init__(self, state) # ISession methods def changed(self): if not self._dirty: self._dirty = True def save_session_callback(request, response): self._save_session(response) self.request = None # explicitly break cycle for gc self.request.add_response_callback(save_session_callback) def invalidate(self): self._plug.clear(self, self.request) self._generate_new_id() now = time.time() self.created = self.renewed = now self.new = True self.clear() # non-modifying dictionary methods get = manage_accessed(dict.get) __getitem__ = manage_accessed(dict.__getitem__) items = manage_accessed(dict.items) values = manage_accessed(dict.values) keys = manage_accessed(dict.keys) __contains__ = manage_accessed(dict.__contains__) __len__ = manage_accessed(dict.__len__) __iter__ = manage_accessed(dict.__iter__) if not PY3: iteritems = manage_accessed(dict.iteritems) itervalues = manage_accessed(dict.itervalues) iterkeys = manage_accessed(dict.iterkeys) has_key = manage_accessed(dict.has_key) # modifying dictionary methods clear = manage_changed(dict.clear) update = manage_changed(dict.update) setdefault = manage_changed(dict.setdefault) pop = manage_changed(dict.pop) popitem = manage_changed(dict.popitem) __setitem__ = manage_changed(dict.__setitem__) __delitem__ = manage_changed(dict.__delitem__) # flash API methods @manage_changed def flash(self, msg, queue='', allow_duplicate=True): storage = self.setdefault('_f_' + queue, []) if allow_duplicate or (msg not in storage): storage.append(msg) @manage_changed def pop_flash(self, queue=''): storage = self.pop('_f_' + queue, []) return storage @manage_accessed def peek_flash(self, queue=''): storage = self.get('_f_' + queue, []) return storage # CSRF API methods @manage_changed def new_csrf_token(self): token = text_(binascii.hexlify(os.urandom(20))) self['_csrft_'] = token return token @manage_accessed def get_csrf_token(self): token = self.get('_csrft_', None) if token is None: token = self.new_csrf_token() return token # non-API methods def _save_session(self, response): if not self._cookie_on_exception: exception = getattr(self.request, 'exception', None) if exception is not None: # dont set a cookie during exceptions return False sess_val = native_( serializer.dumps((self.accessed, self.created, dict(self)))) self._plug.dumps(self, self.request, sess_val) self._cookie.set_cookies(response, self._session_id) return True def _generate_new_id(self): self._session_id = text_(binascii.hexlify(os.urandom(20)))