Hello community, here is the log from the commit of package python3-Beaker for openSUSE:Factory checked in at 2016-11-02 12:45:55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python3-Beaker (Old) and /work/SRC/openSUSE:Factory/.python3-Beaker.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python3-Beaker" Changes: -------- --- /work/SRC/openSUSE:Factory/python3-Beaker/python3-Beaker.changes 2016-05-25 21:23:00.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python3-Beaker.new/python3-Beaker.changes 2016-11-02 12:45:57.000000000 +0100 @@ -1,0 +2,21 @@ +Tue Oct 25 17:13:44 UTC 2016 - arun@gmx.de + +- update to version 1.8.1: + * Sessions have a new option save_accessed_time which defaults to + true for backwards compatibility. Set to false to tell beaker not + to update _accessed_time if the session hasn’t been changed, for + non-cookie sessions stores. This lets you avoid needless datastore + writes. _accessed_time will always be updated when the session is + intentionally saved. + * data_serializer parameter in Session accepts a custom object with + dumps and loads methods. + * Fixed a TypeError in exception reporting when failing to load a + NamespaceManager + * Allow to change Cookie Expiration from a value back to None, + previously it had no effect. + * Allow SessionMiddleware to setup a custom Session class through + the session_class argument. + * Added invalidate_corrupt option to CookieSessions too for valid + cookies containing invalid data. + +------------------------------------------------------------------- @@ -7 +27,0 @@ - Old: ---- Beaker-1.8.0.tar.gz New: ---- Beaker-1.8.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python3-Beaker.spec ++++++ --- /var/tmp/diff_new_pack.PJVayz/_old 2016-11-02 12:45:59.000000000 +0100 +++ /var/tmp/diff_new_pack.PJVayz/_new 2016-11-02 12:45:59.000000000 +0100 @@ -17,7 +17,7 @@ Name: python3-Beaker -Version: 1.8.0 +Version: 1.8.1 Release: 0 Url: http://beaker.rtfd.org/ Summary: A Session and Caching library with WSGI Middleware ++++++ Beaker-1.8.0.tar.gz -> Beaker-1.8.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.8.0/Beaker.egg-info/PKG-INFO new/Beaker-1.8.1/Beaker.egg-info/PKG-INFO --- old/Beaker-1.8.0/Beaker.egg-info/PKG-INFO 2016-01-27 12:40:12.000000000 +0100 +++ new/Beaker-1.8.1/Beaker.egg-info/PKG-INFO 2016-10-24 01:40:20.000000000 +0200 @@ -1,8 +1,8 @@ Metadata-Version: 1.1 Name: Beaker -Version: 1.8.0 +Version: 1.8.1 Summary: A Session and Caching library with WSGI Middleware -Home-page: http://beaker.rtfd.org/ +Home-page: https://beaker.readthedocs.io/ Author: Ben Bangert, Mike Bayer, Philip Jenvey, Alessandro Molina Author-email: ben@groovie.org, pjenvey@groovie.org, amol@turbogears.org License: BSD diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.8.0/PKG-INFO new/Beaker-1.8.1/PKG-INFO --- old/Beaker-1.8.0/PKG-INFO 2016-01-27 12:40:12.000000000 +0100 +++ new/Beaker-1.8.1/PKG-INFO 2016-10-24 01:40:20.000000000 +0200 @@ -1,8 +1,8 @@ Metadata-Version: 1.1 Name: Beaker -Version: 1.8.0 +Version: 1.8.1 Summary: A Session and Caching library with WSGI Middleware -Home-page: http://beaker.rtfd.org/ +Home-page: https://beaker.readthedocs.io/ Author: Ben Bangert, Mike Bayer, Philip Jenvey, Alessandro Molina Author-email: ben@groovie.org, pjenvey@groovie.org, amol@turbogears.org License: BSD diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.8.0/beaker/__init__.py new/Beaker-1.8.1/beaker/__init__.py --- old/Beaker-1.8.0/beaker/__init__.py 2016-01-26 00:31:01.000000000 +0100 +++ new/Beaker-1.8.1/beaker/__init__.py 2016-10-24 01:25:44.000000000 +0200 @@ -1 +1 @@ -__version__ = '1.8.0' +__version__ = '1.8.1' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.8.0/beaker/cache.py new/Beaker-1.8.1/beaker/cache.py --- old/Beaker-1.8.0/beaker/cache.py 2015-11-13 22:21:53.000000000 +0100 +++ new/Beaker-1.8.1/beaker/cache.py 2016-09-17 17:01:47.000000000 +0200 @@ -99,9 +99,9 @@ if not isinstance(sys.exc_info()[1], DistributionNotFound): import traceback try: - from io import StringIO + from StringIO import StringIO # Python2 except ImportError: - from StringIO import StringIO + from io import StringIO # Python3 tb = StringIO() traceback.print_exc(file=tb) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.8.0/beaker/middleware.py new/Beaker-1.8.1/beaker/middleware.py --- old/Beaker-1.8.0/beaker/middleware.py 2015-11-21 00:31:03.000000000 +0100 +++ new/Beaker-1.8.1/beaker/middleware.py 2016-09-17 17:01:47.000000000 +0200 @@ -106,8 +106,9 @@ # Load up the default params self.options = dict(invalidate_corrupt=True, type=None, - data_dir=None, key='beaker.session.id', - timeout=None, secret=None, log_file=None) + data_dir=None, key='beaker.session.id', + timeout=None, save_accessed_time=True, secret=None, + log_file=None) # Pull out any config args meant for beaker session. if there are any for dct in [config, kwargs]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.8.0/beaker/session.py new/Beaker-1.8.1/beaker/session.py --- old/Beaker-1.8.0/beaker/session.py 2016-01-25 23:36:28.000000000 +0100 +++ new/Beaker-1.8.1/beaker/session.py 2016-10-04 22:56:46.000000000 +0200 @@ -1,4 +1,4 @@ -from ._compat import PY2, pickle, http_cookies, unicode_text, b64encode, b64decode +from ._compat import PY2, pickle, http_cookies, unicode_text, b64encode, b64decode, string_type import os import time @@ -9,7 +9,19 @@ from beaker.exceptions import BeakerException, InvalidCryptoBackendError from beaker.cookie import SimpleCookie -__all__ = ['SignedCookie', 'Session'] +__all__ = ['SignedCookie', 'Session', 'InvalidSignature'] + + +class _InvalidSignatureType(object): + """Returned from SignedCookie when the value's signature was invalid.""" + def __nonzero__(self): + return False + + def __bool__(self): + return False + + +InvalidSignature = _InvalidSignatureType() try: @@ -50,19 +62,22 @@ def value_decode(self, val): val = val.strip('"') + if not val: + return None, val + 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 + return InvalidSignature, val for a, b in zip(sig, input_sig): invalid_bits += a != b if invalid_bits: - return None, val + return InvalidSignature, val else: return val[40:], val @@ -88,13 +103,19 @@ :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 + whether session data is still valid. Can be set to None to + disable session time out. + :type timeout: int or None + :param save_accessed_time: Whether beaker should save the session's access + time (True) or only modification time (False). + Defaults to True. :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 data_serializer: If ``"json"`` or ``"pickle"`` should be used - to serialize data. By default ``pickle`` is used. + to serialize data. Can also be an object with + ``loads` and ``dumps`` methods. By default + ``"pickle"`` is used. :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. @@ -108,8 +129,9 @@ """ 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='/', data_serializer='pickle', secret=None, + key='beaker.session.id', timeout=None, save_accessed_time=True, + cookie_expires=True, cookie_domain=None, cookie_path='/', + data_serializer='pickle', secret=None, secure=False, namespace_class=None, httponly=False, encrypt_key=None, validate_key=None, encrypt_nonce_bits=DEFAULT_NONCE_BITS, **namespace_args): @@ -129,10 +151,14 @@ self.data_dir = data_dir self.key = key + if timeout and not save_accessed_time: + raise BeakerException("timeout requires save_accessed_time") self.timeout = timeout + self.save_atime = save_accessed_time self.use_cookies = use_cookies self.cookie_expires = cookie_expires - self.data_serializer = data_serializer + + self._set_serializer(data_serializer) # Default cookie domain/path self._domain = cookie_domain @@ -152,14 +178,24 @@ cookieheader = request.get('cookie', '') if secret: try: - self.cookie = SignedCookie(secret, input=cookieheader) + self.cookie = SignedCookie( + secret, + input=cookieheader, + ) except http_cookies.CookieError: - self.cookie = SignedCookie(secret, input=None) + self.cookie = SignedCookie( + secret, + input=None, + ) else: self.cookie = SimpleCookie(input=cookieheader) if not self.id and self.key in self.cookie: - self.id = self.cookie[self.key].value + cookie_data = self.cookie[self.key].value + # Should we check invalidate_corrupt here? + if cookie_data is InvalidSignature: + cookie_data = None + self.id = cookie_data self.is_new = self.id is None if self.is_new: @@ -169,7 +205,7 @@ try: self.load() except Exception as e: - if invalidate_corrupt: + if self.invalidate_corrupt: util.warn( "Invalidating corrupt session %s; " "error was: %s. Set invalidate_corrupt=False " @@ -178,6 +214,17 @@ else: raise + def _set_serializer(self, data_serializer): + self.data_serializer = data_serializer + if self.data_serializer == 'json': + self.serializer = util.JsonSerializer() + elif self.data_serializer == 'pickle': + self.serializer = util.PickleSerializer() + elif isinstance(self.data_serializer, string_type): + raise BeakerException('Invalid value for data_serializer: %s' % data_serializer) + else: + self.serializer = data_serializer + def has_key(self, name): return name in self @@ -194,24 +241,25 @@ 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 + expires = self.cookie_expires + if expires is False: + expires_date = datetime.fromtimestamp(0x7FFFFFFF) + elif isinstance(expires, timedelta): + expires_date = datetime.utcnow() + expires + elif isinstance(expires, datetime): + expires_date = expires + elif expires is not True: + raise ValueError("Invalid argument for cookie_expires: %s" + % repr(self.cookie_expires)) + self.cookie_expires = expires + if not self.cookie or self.key not in self.cookie: + self.cookie[self.key] = self.id + if expires is True: + self.cookie[self.key]['expires'] = '' + return True + self.cookie[self.key]['expires'] = \ + expires_date.strftime("%a, %d-%b-%Y %H:%M:%S GMT") + return expires_date def _update_cookie_out(self, set_cookie=True): self.request['cookie_out'] = self.cookie[self.key].output(header='') @@ -269,41 +317,26 @@ nonce = b64encode(os.urandom(nonce_len))[:nonce_b64len] encrypt_key = crypto.generateCryptoKeys(self.encrypt_key, self.validate_key + nonce, 1) - data = util.serialize(session_data, self.data_serializer) + data = self.serializer.dumps(session_data) return nonce + b64encode(crypto.aesEncrypt(data, encrypt_key)) else: - data = util.serialize(session_data, self.data_serializer) + data = self.serializer.dumps(session_data) 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_b64len = self.encrypt_nonce_size - nonce = session_data[:nonce_b64len] - encrypt_key = crypto.generateCryptoKeys(self.encrypt_key, - self.validate_key + nonce, 1) - payload = b64decode(session_data[nonce_b64len:]) - 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 + __, nonce_b64len = self.encrypt_nonce_size + nonce = session_data[:nonce_b64len] + encrypt_key = crypto.generateCryptoKeys(self.encrypt_key, + self.validate_key + nonce, 1) + payload = b64decode(session_data[nonce_b64len:]) + data = crypto.aesDecrypt(payload, encrypt_key) else: data = b64decode(session_data) - try: - return util.deserialize(data, self.data_serializer) - except: - if self.invalidate_corrupt: - return None - else: - raise + return self.serializer.loads(data) def _delete_cookie(self): self.request['set_cookie'] = True @@ -402,7 +435,7 @@ """ # 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: + if accessed_only and (self.is_new or not self.save_atime): return None # this session might not have a namespace yet or the session id @@ -487,24 +520,34 @@ regardless of the cookie being present or not to determine whether session data is still valid. :type timeout: int + :param save_accessed_time: Whether beaker should save the session's access + time (True) or only modification time (False). + Defaults to True. :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 data_serializer: If ``"json"`` or ``"pickle"`` should be used - to serialize data. By default ``pickle`` is used. + to serialize data. Can also be an object with + ``loads` and ``dumps`` methods. By default + ``"pickle"`` is used. :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 - + :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 """ 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, + save_accessed_time=True, cookie_expires=True, cookie_domain=None, + cookie_path='/', encrypt_key=None, validate_key=None, secure=False, httponly=False, data_serializer='pickle', - encrypt_nonce_bits=DEFAULT_NONCE_BITS, **kwargs): + encrypt_nonce_bits=DEFAULT_NONCE_BITS, invalidate_corrupt=False, + **kwargs): if not crypto.has_aes and encrypt_key: raise InvalidCryptoBackendError("No AES library is installed, can't generate " @@ -513,6 +556,7 @@ self.request = request self.key = key self.timeout = timeout + self.save_atime = save_accessed_time self.cookie_expires = cookie_expires self.encrypt_key = encrypt_key self.validate_key = validate_key @@ -522,7 +566,8 @@ self.httponly = httponly self._domain = cookie_domain self._path = cookie_path - self.data_serializer = data_serializer + self.invalidate_corrupt = invalidate_corrupt + self._set_serializer(data_serializer) try: cookieheader = request['cookie'] @@ -532,11 +577,19 @@ if validate_key is None: raise BeakerException("No validate_key specified for Cookie only " "Session.") + if timeout and not save_accessed_time: + raise BeakerException("timeout requires save_accessed_time") try: - self.cookie = SignedCookie(validate_key, input=cookieheader) + self.cookie = SignedCookie( + validate_key, + input=cookieheader, + ) except http_cookies.CookieError: - self.cookie = SignedCookie(validate_key, input=None) + self.cookie = SignedCookie( + validate_key, + input=None, + ) self['_id'] = _session_id() self.is_new = True @@ -546,10 +599,19 @@ self.is_new = False try: cookie_data = self.cookie[self.key].value + if cookie_data is InvalidSignature: + raise BeakerException("Invalid signature") self.update(self._decrypt_data(cookie_data)) self._path = self.get('_path', '/') - except: - pass + except Exception as e: + if self.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 if self.timeout is not None: now = time.time() @@ -587,7 +649,7 @@ def save(self, accessed_only=False): """Saves the data for this session to persistent storage""" - if accessed_only and self.is_new: + if accessed_only and (self.is_new or not self.save_atime): return if accessed_only: self.clear() @@ -667,10 +729,16 @@ 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) + session_cls = params.get('session_class', None) + if session_cls is None: + if params.get('type') == 'cookie': + session_cls = CookieSession + else: + session_cls = Session else: - self.__dict__['_sess'] = Session(req, **params) + assert issubclass(session_cls, Session),\ + "Not a Session: " + session_cls + self.__dict__['_sess'] = session_cls(req, **params) return self.__dict__['_sess'] def __getattr__(self, attr): @@ -722,21 +790,27 @@ 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. + Always saves the whole session if save() or delete() have been called. + If they haven't: + - If autosave is set to true, saves the the entire session regardless. + - If save_accessed_time is set to true or unset, only saves the updated + access time. + - If save_accessed_time is set to false, doesn't save anything. """ if self.__dict__['_params'].get('auto'): self._session().save() - else: - if self.__dict__.get('_dirty'): + elif self.__dict__['_params'].get('save_accessed_time', True): + if self.dirty(): self._session().save() else: self._session().save(accessed_only=True) + else: # save_accessed_time is false + if self.dirty(): + self._session().save() def dirty(self): + """Returns True if save() or delete() have been called""" return self.__dict__.get('_dirty', False) def accessed(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.8.0/beaker/util.py new/Beaker-1.8.1/beaker/util.py --- old/Beaker-1.8.0/beaker/util.py 2016-01-26 00:40:54.000000000 +0100 +++ new/Beaker-1.8.1/beaker/util.py 2016-09-17 17:07:29.000000000 +0200 @@ -304,6 +304,8 @@ ('secure', (bool, NoneType), "Session secure must be a boolean."), ('httponly', (bool, NoneType), "Session httponly must be a boolean."), ('timeout', (int, NoneType), "Session timeout must be an integer."), + ('save_accessed_time', (bool, NoneType), + "Session save_accessed_time must be a boolean (defaults to true)."), ('auto', (bool, NoneType), "Session is created if accessed."), ('webtest_varname', (str, NoneType), "Session varname must be a string."), ('data_serializer', (str,), "data_serializer must be a string.") @@ -313,6 +315,10 @@ if cookie_expires and isinstance(cookie_expires, int) and \ not isinstance(cookie_expires, bool): opts['cookie_expires'] = timedelta(seconds=cookie_expires) + + if opts.get('timeout') is not None and not opts.get('save_accessed_time', True): + raise Exception("save_accessed_time must be true to use timeout") + return opts @@ -442,15 +448,33 @@ return '%s|%s' % (inspect.getsourcefile(func), func.__name__) +class PickleSerializer(object): + def loads(self, data_string): + return pickle.loads(data_string) + + def dumps(self, data): + return pickle.dumps(data, 2) + + +class JsonSerializer(object): + def loads(self, data_string): + return json.loads(zlib.decompress(data_string).decode('utf-8')) + + def dumps(self, data): + return zlib.compress(json.dumps(data).encode('utf-8')) + + def serialize(data, method): if method == 'json': - return zlib.compress(json.dumps(data).encode('utf-8')) + serializer = JsonSerializer() else: - return pickle.dumps(data, 2) + serializer = PickleSerializer() + return serializer.dumps(data) def deserialize(data_string, method): if method == 'json': - return json.loads(zlib.decompress(data_string).decode('utf-8')) + serializer = JsonSerializer() else: - return pickle.loads(data_string) + serializer = PickleSerializer() + return serializer.loads(data_string) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Beaker-1.8.0/setup.py new/Beaker-1.8.1/setup.py --- old/Beaker-1.8.0/setup.py 2016-01-26 00:08:52.000000000 +0100 +++ new/Beaker-1.8.1/setup.py 2016-09-17 17:01:47.000000000 +0200 @@ -61,7 +61,7 @@ keywords='wsgi myghty session web cache middleware', author='Ben Bangert, Mike Bayer, Philip Jenvey, Alessandro Molina', author_email='ben@groovie.org, pjenvey@groovie.org, amol@turbogears.org', - url='http://beaker.rtfd.org/', + url='https://beaker.readthedocs.io/', license='BSD', packages=find_packages(exclude=['ez_setup', 'examples', 'tests', 'tests.*']), zip_safe=False,