[Python-modules-commits] [beaker] 01/03: Import beaker_1.8.1.orig.tar.gz

Piotr Ożarowski piotr at moszumanska.debian.org
Sat Nov 5 19:07:25 UTC 2016


This is an automated email from the git hooks/post-receive script.

piotr pushed a commit to branch master
in repository beaker.

commit 3f7edc65277801d8d4f41c0031ef07db613bc1f9
Author: Piotr Ożarowski <piotr at debian.org>
Date:   Sat Nov 5 20:02:26 2016 +0100

    Import beaker_1.8.1.orig.tar.gz
---
 Beaker.egg-info/PKG-INFO |   4 +-
 PKG-INFO                 |   4 +-
 beaker/__init__.py       |   2 +-
 beaker/cache.py          |   4 +-
 beaker/middleware.py     |   5 +-
 beaker/session.py        | 228 +++++++++++++++++++++++++++++++----------------
 beaker/util.py           |  32 ++++++-
 setup.py                 |   2 +-
 8 files changed, 190 insertions(+), 91 deletions(-)

diff --git a/Beaker.egg-info/PKG-INFO b/Beaker.egg-info/PKG-INFO
index 680c2bf..a12725a 100644
--- a/Beaker.egg-info/PKG-INFO
+++ b/Beaker.egg-info/PKG-INFO
@@ -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 at groovie.org, pjenvey at groovie.org, amol at turbogears.org
 License: BSD
diff --git a/PKG-INFO b/PKG-INFO
index 680c2bf..a12725a 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -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 at groovie.org, pjenvey at groovie.org, amol at turbogears.org
 License: BSD
diff --git a/beaker/__init__.py b/beaker/__init__.py
index b280975..e8b6b09 100644
--- a/beaker/__init__.py
+++ b/beaker/__init__.py
@@ -1 +1 @@
-__version__ = '1.8.0'
+__version__ = '1.8.1'
diff --git a/beaker/cache.py b/beaker/cache.py
index cbb071d..dd8645f 100644
--- a/beaker/cache.py
+++ b/beaker/cache.py
@@ -99,9 +99,9 @@ class _backends(object):
                     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 --git a/beaker/middleware.py b/beaker/middleware.py
index ecdd567..319f2b7 100644
--- a/beaker/middleware.py
+++ b/beaker/middleware.py
@@ -106,8 +106,9 @@ class SessionMiddleware(object):
 
         # 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 --git a/beaker/session.py b/beaker/session.py
index cbd7694..86c9d70 100644
--- a/beaker/session.py
+++ b/beaker/session.py
@@ -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.cache import clsmap
 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 @@ class SignedCookie(SimpleCookie):
 
     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 @@ class Session(dict):
     :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 @@ class Session(dict):
     """
     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 @@ class Session(dict):
         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 @@ class Session(dict):
             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 @@ class Session(dict):
             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 @@ class Session(dict):
                 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 @@ class Session(dict):
 
     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 @@ class Session(dict):
             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 @@ class Session(dict):
         """
         # 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 @@ class CookieSession(Session):
                     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 @@ class CookieSession(Session):
         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 @@ class CookieSession(Session):
         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 @@ class CookieSession(Session):
         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 @@ class CookieSession(Session):
             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 @@ class CookieSession(Session):
 
     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 @@ class SessionObject(object):
             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 @@ class SessionObject(object):
     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 --git a/beaker/util.py b/beaker/util.py
index 35f0441..4e26594 100644
--- a/beaker/util.py
+++ b/beaker/util.py
@@ -304,6 +304,8 @@ def coerce_session_params(params):
         ('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 @@ def coerce_session_params(params):
     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 @@ def func_namespace(func):
         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 --git a/setup.py b/setup.py
index af01ea7..f5d7080 100644
--- a/setup.py
+++ b/setup.py
@@ -61,7 +61,7 @@ setup(name='Beaker',
       keywords='wsgi myghty session web cache middleware',
       author='Ben Bangert, Mike Bayer, Philip Jenvey, Alessandro Molina',
       author_email='ben at groovie.org, pjenvey at groovie.org, amol at 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,

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/beaker.git



More information about the Python-modules-commits mailing list