Hello community,

here is the log from the commit of package python-Beaker for openSUSE:Factory 
checked in at 2017-09-12 19:39:05
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-Beaker (Old)
 and      /work/SRC/openSUSE:Factory/.python-Beaker.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-Beaker"

Tue Sep 12 19:39:05 2017 rev:21 rq:522093 version:1.9.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-Beaker/python-Beaker.changes      
2016-11-25 12:26:20.000000000 +0100
+++ /work/SRC/openSUSE:Factory/.python-Beaker.new/python-Beaker.changes 
2017-09-12 19:39:06.340486850 +0200
@@ -1,0 +2,23 @@
+Thu Sep  7 16:55:30 UTC 2017 - [email protected]
+
+- Update to version 1.9.0
+  * Beaker now provides builtin ``ext:mongodb`` and ``ext:redis``
+    namespace managers. Both come with a Synchronizer implemented
+    on the storage backend instead of relying on file one.
+  * Fixed an issue where cookie options like ``Secure``,
+    ``Domain`` and so on where lost.
+  * Improved support for cache entries expiration.
+    NamespaceManagers that support it will expire their key
+    automatically.
+  * Pycryptodome can be used instead of pycrypto.
+  * An issue with ``Cookie`` module import on case insensitive
+    file systems should have been resolved.
+  * Cryptography module is now as a crypto function provider
+    instead of pycrypto
+
+-------------------------------------------------------------------
+Thu Aug 24 13:33:15 UTC 2017 - [email protected]
+
+- singlespec auto-conversion
+
+-------------------------------------------------------------------
@@ -158,0 +182 @@
+

Old:
----
  Beaker-1.8.1.tar.gz

New:
----
  Beaker-1.9.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-Beaker.spec ++++++
--- /var/tmp/diff_new_pack.wcgvk4/_old  2017-09-12 19:39:06.848415467 +0200
+++ /var/tmp/diff_new_pack.wcgvk4/_new  2017-09-12 19:39:06.852414905 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-Beaker
 #
-# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -16,28 +16,50 @@
 #
 
 
+%{?!python_module:%define python_module() python-%{**} python3-%{**}}
+%define oldpython python
+# Test files not in source archive
+%bcond_with     test
 Name:           python-Beaker
-Version:        1.8.1
+Version:        1.9.0
 Release:        0
-Url:            http://beaker.rtfd.org/
 Summary:        A Session and Caching library with WSGI Middleware
 License:        BSD-3-Clause
 Group:          Development/Languages/Python
+Url:            https://github.com/bbangert/beaker
 Source:         
https://files.pythonhosted.org/packages/source/B/Beaker/Beaker-%{version}.tar.gz
-BuildRoot:      %{_tmppath}/%{name}-%{version}-build
-Requires:       python-pycryptopp >= 0.5.12
+BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
-BuildRequires:  python-pycryptopp >= 0.5.12
-BuildRequires:  python-setuptools
-Provides:       python-beaker = %{version}
-Obsoletes:      python-beaker < %{version}
-%if 0%{?suse_version} && 0%{?suse_version} <= 1110
-BuildRequires:  python-ordereddict
-BuildRequires:  python-unittest2
-%{!?python_sitelib: %global python_sitelib %(python -c "from 
distutils.sysconfig import get_python_lib; print get_python_lib()")}
-%else
+BuildRequires:  python-rpm-macros
+%if %{with test}
+BuildRequires:  %{python_module SQLAlchemy}
+BuildRequires:  %{python_module WebTest}
+BuildRequires:  %{python_module coverage}
+BuildRequires:  %{python_module cryptography}
+BuildRequires:  %{python_module mock}
+BuildRequires:  %{python_module nose}
+BuildRequires:  %{python_module pycryptodome}
+BuildRequires:  %{python_module pylibmc}
+BuildRequires:  %{python_module python-memcached}
+BuildRequires:  %{python_module pymongo}
+BuildRequires:  %{python_module redis}
+BuildRequires:  python-funcsigs
+%endif
+Requires:       python-pylibmc
+Requires:       python-python-memcached
+Recommends:     python-SQLAlchemy
+Recommends:     python-cryptography
+Recommends:     python-pycryptopp >= 0.5.12
+Recommends:     python-pycrypto
+Recommends:     python-pymongo
+Recommends:     python-redis
 BuildArch:      noarch
+%ifpython2
+Requires:       python-funcsigs
+Provides:       %{oldpython}-beaker = %{version}
+Obsoletes:      %{oldpython}-beaker < %{version}
 %endif
+%python_subpackages
 
 %description
 Beaker is a web session and general caching library that includes WSGI 
@@ -53,7 +75,7 @@
 Beaker includes Cache and Session WSGI middleware to ease integration with
 WSGI capable frameworks, and is automatically used by Pylons.
 
-Features:
+Features include:
 
 * Fast, robust performance
 * Multiple reader/single writer lock system to avoid duplicate simultaneous
@@ -74,16 +96,21 @@
 %setup -q -n Beaker-%{version}
 
 %build
-python setup.py build
+%python_build
 
 %install
-python setup.py install --prefix=%{_prefix} --root=%{buildroot}
-%fdupes %buildroot/%_prefix
+%python_install
+%python_expand %fdupes %{buildroot}%{$python_sitelib}
+
+%if %{with test}
+%check
+%python_exec setup.py test
+%endif
 
-%files
+%files %{python_files}
 %defattr(-,root,root,-)
 %doc README.rst
 %{python_sitelib}/beaker/
-%{python_sitelib}/Beaker-%{version}-py%{py_ver}.egg-info
+%{python_sitelib}/Beaker-%{version}-py*.egg-info
 
 %changelog

++++++ Beaker-1.8.1.tar.gz -> Beaker-1.9.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/Beaker.egg-info/PKG-INFO 
new/Beaker-1.9.0/Beaker.egg-info/PKG-INFO
--- old/Beaker-1.8.1/Beaker.egg-info/PKG-INFO   2016-10-24 01:40:20.000000000 
+0200
+++ new/Beaker-1.9.0/Beaker.egg-info/PKG-INFO   2017-06-18 23:34:02.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: Beaker
-Version: 1.8.1
+Version: 1.9.0
 Summary: A Session and Caching library with WSGI Middleware
 Home-page: https://beaker.readthedocs.io/
 Author: Ben Bangert, Mike Bayer, Philip Jenvey, Alessandro Molina
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/Beaker.egg-info/SOURCES.txt 
new/Beaker-1.9.0/Beaker.egg-info/SOURCES.txt
--- old/Beaker-1.8.1/Beaker.egg-info/SOURCES.txt        2016-10-24 
01:40:20.000000000 +0200
+++ new/Beaker-1.9.0/Beaker.egg-info/SOURCES.txt        2017-06-18 
23:34:02.000000000 +0200
@@ -22,12 +22,16 @@
 beaker/util.py
 beaker/crypto/__init__.py
 beaker/crypto/jcecrypto.py
+beaker/crypto/noencryption.py
 beaker/crypto/nsscrypto.py
 beaker/crypto/pbkdf2.py
+beaker/crypto/pyca_cryptography.py
 beaker/crypto/pycrypto.py
 beaker/crypto/util.py
 beaker/ext/__init__.py
 beaker/ext/database.py
 beaker/ext/google.py
 beaker/ext/memcached.py
+beaker/ext/mongodb.py
+beaker/ext/redisnm.py
 beaker/ext/sqla.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/Beaker.egg-info/requires.txt 
new/Beaker-1.9.0/Beaker.egg-info/requires.txt
--- old/Beaker-1.8.1/Beaker.egg-info/requires.txt       2016-10-24 
01:40:20.000000000 +0200
+++ new/Beaker-1.9.0/Beaker.egg-info/requires.txt       2017-06-18 
23:34:02.000000000 +0200
@@ -1,15 +1,26 @@
 funcsigs
 
+[crypto]
+pycryptopp>=0.5.12
+
+[cryptography]
+cryptography
+
+[pycrypto]
+pycrypto
+
+[pycryptodome]
+pycryptodome
+
 [testsuite]
 nose
-webtest
 Mock
-pycrypto
+pycryptodome
+cryptography
+webtest
 coverage
 SQLALchemy
-
-[crypto]
-pycryptopp>=0.5.12
-
-[pycrypto]
-pycrypto
\ No newline at end of file
+pymongo
+redis
+pylibmc
+python-memcached
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/PKG-INFO new/Beaker-1.9.0/PKG-INFO
--- old/Beaker-1.8.1/PKG-INFO   2016-10-24 01:40:20.000000000 +0200
+++ new/Beaker-1.9.0/PKG-INFO   2017-06-18 23:34:02.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: Beaker
-Version: 1.8.1
+Version: 1.9.0
 Summary: A Session and Caching library with WSGI Middleware
 Home-page: https://beaker.readthedocs.io/
 Author: Ben Bangert, Mike Bayer, Philip Jenvey, Alessandro Molina
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/__init__.py 
new/Beaker-1.9.0/beaker/__init__.py
--- old/Beaker-1.8.1/beaker/__init__.py 2016-10-24 01:25:44.000000000 +0200
+++ new/Beaker-1.9.0/beaker/__init__.py 2017-06-18 23:32:51.000000000 +0200
@@ -1 +1 @@
-__version__ = '1.8.1'
+__version__ = '1.9.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/_compat.py 
new/Beaker-1.9.0/beaker/_compat.py
--- old/Beaker-1.8.1/beaker/_compat.py  2016-01-25 15:29:15.000000000 +0100
+++ new/Beaker-1.9.0/beaker/_compat.py  2017-03-08 20:47:07.000000000 +0100
@@ -1,3 +1,4 @@
+from __future__ import absolute_import
 import sys
 
 # True if we are running on Python 2.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/cache.py 
new/Beaker-1.9.0/beaker/cache.py
--- old/Beaker-1.8.1/beaker/cache.py    2016-09-17 17:01:47.000000000 +0200
+++ new/Beaker-1.9.0/beaker/cache.py    2017-06-18 22:41:27.000000000 +0200
@@ -20,6 +20,8 @@
 import beaker.ext.database as database
 import beaker.ext.sqla as sqla
 import beaker.ext.google as google
+import beaker.ext.mongodb as mongodb
+import beaker.ext.redisnm as redisnm
 from functools import wraps
 
 # Initialize the cache region dict
@@ -116,14 +118,16 @@
 
 # Initialize the basic available backends
 clsmap = _backends({
-          'memory': container.MemoryNamespaceManager,
-          'dbm': container.DBMNamespaceManager,
-          'file': container.FileNamespaceManager,
-          'ext:memcached': memcached.MemcachedNamespaceManager,
-          'ext:database': database.DatabaseNamespaceManager,
-          'ext:sqla': sqla.SqlaNamespaceManager,
-          'ext:google': google.GoogleNamespaceManager,
-          })
+    'memory': container.MemoryNamespaceManager,
+    'dbm': container.DBMNamespaceManager,
+    'file': container.FileNamespaceManager,
+    'ext:memcached': memcached.MemcachedNamespaceManager,
+    'ext:database': database.DatabaseNamespaceManager,
+    'ext:sqla': sqla.SqlaNamespaceManager,
+    'ext:google': google.GoogleNamespaceManager,
+    'ext:mongodb': mongodb.MongoNamespaceManager,
+    'ext:redis': redisnm.RedisNamespaceManager
+})
 
 
 def cache_region(region, *args):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/container.py 
new/Beaker-1.9.0/beaker/container.py
--- old/Beaker-1.8.1/beaker/container.py        2015-02-25 16:37:38.000000000 
+0100
+++ new/Beaker-1.9.0/beaker/container.py        2017-06-18 22:41:27.000000000 
+0200
@@ -409,7 +409,8 @@
             if storedtime is None:
                 storedtime = time.time()
             debug("set_value stored time %r expire time %r", storedtime, 
self.expire_argument)
-            self.namespace.set_value(self.key, (storedtime, 
self.expire_argument, value))
+            self.namespace.set_value(self.key, (storedtime, 
self.expire_argument, value),
+                                     expiretime=self.expire_argument)
         finally:
             self.namespace.release_write_lock()
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/crypto/__init__.py 
new/Beaker-1.9.0/beaker/crypto/__init__.py
--- old/Beaker-1.8.1/beaker/crypto/__init__.py  2016-01-25 23:36:28.000000000 
+0100
+++ new/Beaker-1.9.0/beaker/crypto/__init__.py  2017-03-08 20:45:16.000000000 
+0100
@@ -1,48 +1,77 @@
 from .._compat import JYTHON
 
-from warnings import warn
 
 from beaker.crypto.pbkdf2 import pbkdf2
 from beaker.crypto.util import hmac, sha1, hmac_sha1, md5
 from beaker import util
+from beaker.exceptions import InvalidCryptoBackendError
 
 keyLength = None
 DEFAULT_NONCE_BITS = 128
 
-if JYTHON:
-    try:
-        from beaker.crypto.jcecrypto import getKeyLength, aesEncrypt
-        keyLength = getKeyLength()
-    except ImportError:
-        pass
-else:
-    try:
-        from beaker.crypto.nsscrypto import getKeyLength, aesEncrypt, 
aesDecrypt
-        keyLength = getKeyLength()
-    except ImportError:
+CRYPTO_MODULES = {}
+
+
+def load_default_module():
+    """ Load the default crypto module
+    """
+    if JYTHON:
         try:
-            from beaker.crypto.pycrypto import getKeyLength, aesEncrypt, 
aesDecrypt
-            keyLength = getKeyLength()
+            from beaker.crypto import jcecrypto
+            return jcecrypto
         except ImportError:
             pass
+    else:
+        try:
+            from beaker.crypto import nsscrypto
+            return nsscrypto
+        except ImportError:
+            try:
+                from beaker.crypto import pycrypto
+                return pycrypto
+            except ImportError:
+                pass
+    from beaker.crypto import noencryption
+    return noencryption
+
+
+def register_crypto_module(name, mod):
+    """
+    Register the given module under the name given.
+    """
+    CRYPTO_MODULES[name] = mod
+
+
+def get_crypto_module(name):
+    """
+    Get the active crypto module for this name
+    """
+    if name not in CRYPTO_MODULES:
+        if name == 'default':
+            register_crypto_module('default', load_default_module())
+        elif name == 'nss':
+            from beaker.crypto import nsscrypto
+            register_crypto_module(name, nsscrypto)
+        elif name == 'pycrypto':
+            from beaker.crypto import pycrypto
+            register_crypto_module(name, pycrypto)
+        elif name == 'cryptography':
+            from beaker.crypto import pyca_cryptography
+            register_crypto_module(name, pyca_cryptography)
+        else:
+            raise InvalidCryptoBackendError(
+                "No crypto backend with name '%s' is registered." % name)
+
+    return CRYPTO_MODULES[name]
 
-if not keyLength:
-    has_aes = False
-else:
-    has_aes = True
-
-if has_aes and keyLength < 32:
-    warn('Crypto implementation only supports key lengths up to %d bits. '
-         'Generated session cookies may be incompatible with other '
-         'environments' % (keyLength * 8))
 
 
-def generateCryptoKeys(master_key, salt, iterations):
+def generateCryptoKeys(master_key, salt, iterations, keylen):
     # NB: We XOR parts of the keystream into the randomly-generated parts, just
     # in case os.urandom() isn't as random as it should be.  Note that if
     # os.urandom() returns truly random data, this will have no effect on the
     # overall security.
-    return pbkdf2(master_key, salt, iterations=iterations, dklen=keyLength)
+    return pbkdf2(master_key, salt, iterations=iterations, dklen=keylen)
 
 
 def get_nonce_size(number_of_bits):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/crypto/jcecrypto.py 
new/Beaker-1.9.0/beaker/crypto/jcecrypto.py
--- old/Beaker-1.8.1/beaker/crypto/jcecrypto.py 2015-02-25 16:37:38.000000000 
+0100
+++ new/Beaker-1.9.0/beaker/crypto/jcecrypto.py 2017-03-08 20:45:16.000000000 
+0100
@@ -8,6 +8,8 @@
 download the "Unlimited Strength Jurisdiction Policy Files" from Sun,
 which will allow encryption using 256 bit AES keys.
 """
+from warnings import warn
+
 from javax.crypto import Cipher
 from javax.crypto.spec import SecretKeySpec, IvParameterSpec
 
@@ -26,7 +28,14 @@
 # magic.
 aesDecrypt = aesEncrypt
 
+has_aes = True
 
 def getKeyLength():
     maxlen = Cipher.getMaxAllowedKeyLength('AES/CTR/NoPadding')
     return min(maxlen, 256) / 8
+
+
+if getKeyLength() < 32:
+    warn('Crypto implementation only supports key lengths up to %d bits. '
+         'Generated session cookies may be incompatible with other '
+         'environments' % (getKeyLength() * 8))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/crypto/noencryption.py 
new/Beaker-1.9.0/beaker/crypto/noencryption.py
--- old/Beaker-1.8.1/beaker/crypto/noencryption.py      1970-01-01 
01:00:00.000000000 +0100
+++ new/Beaker-1.9.0/beaker/crypto/noencryption.py      2017-03-08 
20:45:16.000000000 +0100
@@ -0,0 +1,12 @@
+"""Encryption module that does nothing"""
+
+def aesEncrypt(data, key):
+    return data
+
+def aesDecrypt(data, key):
+    return data
+
+has_aes = False
+
+def getKeyLength():
+    return 32
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/crypto/pyca_cryptography.py 
new/Beaker-1.9.0/beaker/crypto/pyca_cryptography.py
--- old/Beaker-1.8.1/beaker/crypto/pyca_cryptography.py 1970-01-01 
01:00:00.000000000 +0100
+++ new/Beaker-1.9.0/beaker/crypto/pyca_cryptography.py 2017-03-08 
20:45:16.000000000 +0100
@@ -0,0 +1,52 @@
+"""Encryption module that uses pyca/cryptography"""
+
+import os
+import json
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.ciphers import (
+    Cipher, algorithms, modes
+)
+
+
+def aesEncrypt(data, key):
+    # Generate a random 96-bit IV.
+    iv = os.urandom(12)
+
+    # Construct an AES-GCM Cipher object with the given key and a
+    # randomly generated IV.
+    encryptor = Cipher(
+        algorithms.AES(key),
+        modes.GCM(iv),
+        backend=default_backend()
+    ).encryptor()
+
+    # Encrypt the plaintext and get the associated ciphertext.
+    # GCM does not require padding.
+    ciphertext = encryptor.update(data) + encryptor.finalize()
+
+    return iv + encryptor.tag + ciphertext
+
+
+def aesDecrypt(data, key):
+    iv = data[:12]
+    tag = data[12:28]
+    ciphertext = data[28:]
+
+    # Construct a Cipher object, with the key, iv, and additionally the
+    # GCM tag used for authenticating the message.
+    decryptor = Cipher(
+        algorithms.AES(key),
+        modes.GCM(iv, tag),
+        backend=default_backend()
+    ).decryptor()
+
+    # Decryption gets us the authenticated plaintext.
+    # If the tag does not match an InvalidTag exception will be raised.
+    return decryptor.update(ciphertext) + decryptor.finalize()
+
+
+has_aes = True
+
+def getKeyLength():
+    return 32
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/crypto/pycrypto.py 
new/Beaker-1.9.0/beaker/crypto/pycrypto.py
--- old/Beaker-1.8.1/beaker/crypto/pycrypto.py  2015-02-25 16:37:38.000000000 
+0100
+++ new/Beaker-1.9.0/beaker/crypto/pycrypto.py  2017-03-08 20:45:16.000000000 
+0100
@@ -28,7 +28,7 @@
                          counter=Counter.new(128, initial_value=0))
         return cipher.decrypt(data)
 
-
+has_aes = True
 
 def getKeyLength():
     return 32
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/ext/mongodb.py 
new/Beaker-1.9.0/beaker/ext/mongodb.py
--- old/Beaker-1.8.1/beaker/ext/mongodb.py      1970-01-01 01:00:00.000000000 
+0100
+++ new/Beaker-1.9.0/beaker/ext/mongodb.py      2017-06-18 22:41:27.000000000 
+0200
@@ -0,0 +1,161 @@
+import datetime
+import os
+import threading
+import time
+import pickle
+
+try:
+    import pymongo
+    import pymongo.errors
+    import bson
+except ImportError:
+    pymongo = None
+    bson = None
+
+from beaker.container import NamespaceManager
+from beaker.synchronization import SynchronizerImpl
+from beaker.util import SyncDict, machine_identifier
+from beaker.crypto.util import sha1
+from beaker._compat import string_type, PY2
+
+
+class MongoNamespaceManager(NamespaceManager):
+    """Provides the :class:`.NamespaceManager` API over MongoDB."""
+    MAX_KEY_LENGTH = 1024
+
+    clients = SyncDict()
+
+    def __init__(self, namespace, url, **kw):
+        super(MongoNamespaceManager, self).__init__(namespace)
+        self.lock_dir = None  # MongoDB uses mongo itself for locking.
+
+        if pymongo is None:
+            raise RuntimeError('pymongo3 is not available')
+
+        if isinstance(url, string_type):
+            self.client = MongoNamespaceManager.clients.get(url, 
pymongo.MongoClient, url)
+        else:
+            self.client = url
+        self.db = self.client.get_default_database()
+
+    def _format_key(self, key):
+        if not isinstance(key, str):
+            key = key.decode('ascii')
+        if len(key) > (self.MAX_KEY_LENGTH - len(self.namespace) - 1):
+            if not PY2:
+                key = key.encode('utf-8')
+            key = sha1(key).hexdigest()
+        return '%s:%s' % (self.namespace, key)
+
+    def get_creation_lock(self, key):
+        return MongoSynchronizer(self._format_key(key), self.client)
+
+    def __getitem__(self, key):
+        self._clear_expired()
+        entry = self.db.backer_cache.find_one({'_id': self._format_key(key)})
+        if entry is None:
+            raise KeyError(key)
+        return pickle.loads(entry['value'])
+
+    def __contains__(self, key):
+        self._clear_expired()
+        entry = self.db.backer_cache.find_one({'_id': self._format_key(key)})
+        return entry is not None
+
+    def has_key(self, key):
+        return key in self
+
+    def set_value(self, key, value, expiretime=None):
+        self._clear_expired()
+
+        expiration = None
+        if expiretime is not None:
+            expiration = time.time() + expiretime
+
+        value = pickle.dumps(value)
+        self.db.backer_cache.update_one({'_id': self._format_key(key)},
+                                        {'$set': {'value': bson.Binary(value),
+                                                  'expiration': expiration}},
+                                        upsert=True)
+
+    def __setitem__(self, key, value):
+        self.set_value(key, value)
+
+    def __delitem__(self, key):
+        self._clear_expired()
+        self.db.backer_cache.delete_many({'_id': self._format_key(key)})
+
+    def do_remove(self):
+        self.db.backer_cache.delete_many({'_id': {'$regex': '^%s' % 
self.namespace}})
+
+    def keys(self):
+        return [e['key'].split(':', 1)[-1] for e in 
self.db.backer_cache.find_all(
+            {'_id': {'$regex': '^%s' % self.namespace}}
+        )]
+
+    def _clear_expired(self):
+        now = time.time()
+        self.db.backer_cache.delete_many({'_id': {'$regex': '^%s' % 
self.namespace},
+                                          'expiration': {'$ne': None, '$lte': 
now}})
+
+
+class MongoSynchronizer(SynchronizerImpl):
+    # If a cache entry generation function can take a lot,
+    # but 15 minutes is more than a reasonable time.
+    LOCK_EXPIRATION = 900
+    MACHINE_ID = machine_identifier()
+
+    def __init__(self, identifier, url):
+        super(MongoSynchronizer, self).__init__()
+        self.identifier = identifier
+        if isinstance(url, string_type):
+            self.client = MongoNamespaceManager.clients.get(url, 
pymongo.MongoClient, url)
+        else:
+            self.client = url
+        self.db = self.client.get_default_database()
+
+    def _clear_expired_locks(self):
+        now = datetime.datetime.utcnow()
+        expired = now - datetime.timedelta(seconds=self.LOCK_EXPIRATION)
+        self.db.beaker_locks.delete_many({'_id': self.identifier, 'timestamp': 
{'$lte': expired}})
+        return now
+
+    def _get_owner_id(self):
+        return '%s-%s-%s' % (self.MACHINE_ID, os.getpid(), 
threading.current_thread().ident)
+
+    def do_release_read_lock(self):
+        self.db.beaker_locks.update_one({'_id': self.identifier, 'readers': 
self._get_owner_id()},
+                                        {'$pull': {'readers': 
self._get_owner_id()}})
+
+    def do_acquire_read_lock(self, wait):
+        now = self._clear_expired_locks()
+        while True:
+            try:
+                self.db.beaker_locks.update_one({'_id': self.identifier, 
'owner': None},
+                                                {'$set': {'timestamp': now},
+                                                 '$push': {'readers': 
self._get_owner_id()}},
+                                                upsert=True)
+                return True
+            except pymongo.errors.DuplicateKeyError:
+                if not wait:
+                    return False
+                time.sleep(0.2)
+
+    def do_release_write_lock(self):
+        self.db.beaker_locks.delete_one({'_id': self.identifier, 'owner': 
self._get_owner_id()})
+
+    def do_acquire_write_lock(self, wait):
+        now = self._clear_expired_locks()
+        while True:
+            try:
+                self.db.beaker_locks.update_one({'_id': self.identifier, 
'owner': None,
+                                                 'readers': []},
+                                                {'$set': {'owner': 
self._get_owner_id(),
+                                                          'timestamp': now}},
+                                                upsert=True)
+                return True
+            except pymongo.errors.DuplicateKeyError:
+                if not wait:
+                    return False
+                time.sleep(0.2)
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/ext/redisnm.py 
new/Beaker-1.9.0/beaker/ext/redisnm.py
--- old/Beaker-1.8.1/beaker/ext/redisnm.py      1970-01-01 01:00:00.000000000 
+0100
+++ new/Beaker-1.9.0/beaker/ext/redisnm.py      2017-06-18 22:41:27.000000000 
+0200
@@ -0,0 +1,129 @@
+import os
+import threading
+import time
+import pickle
+
+try:
+    import redis
+except ImportError:
+    redis = None
+
+from beaker.container import NamespaceManager
+from beaker.synchronization import SynchronizerImpl
+from beaker.util import SyncDict, machine_identifier
+from beaker.crypto.util import sha1
+from beaker._compat import string_type, PY2
+
+
+class RedisNamespaceManager(NamespaceManager):
+    """Provides the :class:`.NamespaceManager` API over Redis."""
+    MAX_KEY_LENGTH = 1024
+
+    clients = SyncDict()
+
+    def __init__(self, namespace, url, **kw):
+        super(RedisNamespaceManager, self).__init__(namespace)
+        self.lock_dir = None  # Redis uses redis itself for locking.
+
+        if redis is None:
+            raise RuntimeError('redis is not available')
+
+        if isinstance(url, string_type):
+            self.client = RedisNamespaceManager.clients.get(url, 
redis.StrictRedis.from_url, url)
+        else:
+            self.client = url
+
+    def _format_key(self, key):
+        if not isinstance(key, str):
+            key = key.decode('ascii')
+        if len(key) > (self.MAX_KEY_LENGTH - len(self.namespace) - 
len('beaker_cache:') - 1):
+            if not PY2:
+                key = key.encode('utf-8')
+            key = sha1(key).hexdigest()
+        return 'beaker_cache:%s:%s' % (self.namespace, key)
+
+    def get_creation_lock(self, key):
+        return RedisSynchronizer(self._format_key(key), self.client)
+
+    def __getitem__(self, key):
+        entry = self.client.get(self._format_key(key))
+        if entry is None:
+            raise KeyError(key)
+        return pickle.loads(entry)
+
+    def __contains__(self, key):
+        return self.client.exists(self._format_key(key))
+
+    def has_key(self, key):
+        return key in self
+
+    def set_value(self, key, value, expiretime=None):
+        value = pickle.dumps(value)
+        if expiretime is not None:
+            self.client.setex(self._format_key(key), int(expiretime), value)
+        else:
+            self.client.set(self._format_key(key), value)
+
+    def __setitem__(self, key, value):
+        self.set_value(key, value)
+
+    def __delitem__(self, key):
+        self.client.delete(self._format_key(key))
+
+    def do_remove(self):
+        for k in self.keys():
+            self.client.delete(k)
+
+    def keys(self):
+        return self.client.keys('beaker_cache:%s:*' % self.namespace)
+
+
+class RedisSynchronizer(SynchronizerImpl):
+    """Synchronizer based on redis.
+
+    This Synchronizer only supports 1 reader or 1 writer at time, not 
concurrent readers.
+    """
+    # If a cache entry generation function can take a lot,
+    # but 15 minutes is more than a reasonable time.
+    LOCK_EXPIRATION = 900
+    MACHINE_ID = machine_identifier()
+
+    def __init__(self, identifier, url):
+        super(RedisSynchronizer, self).__init__()
+        self.identifier = 'beaker_lock:%s' % identifier
+        if isinstance(url, string_type):
+            self.client = RedisNamespaceManager.clients.get(url, 
redis.StrictRedis.from_url, url)
+        else:
+            self.client = url
+
+    def _get_owner_id(self):
+        return (
+            '%s-%s-%s' % (self.MACHINE_ID, os.getpid(), 
threading.current_thread().ident)
+        ).encode('ascii')
+
+    def do_release_read_lock(self):
+        self.do_release_write_lock()
+
+    def do_acquire_read_lock(self, wait):
+        self.do_acquire_write_lock(wait)
+
+    def do_release_write_lock(self):
+        identifier = self.identifier
+        owner_id = self._get_owner_id()
+        def execute_release(pipe):
+            lock_value = pipe.get(identifier)
+            if lock_value == owner_id:
+                pipe.delete(identifier)
+        self.client.transaction(execute_release, identifier)
+
+    def do_acquire_write_lock(self, wait):
+        owner_id = self._get_owner_id()
+        while True:
+            if self.client.setnx(self.identifier, owner_id):
+                self.client.pexpire(self.identifier, self.LOCK_EXPIRATION * 
1000)
+                return True
+
+            if not wait:
+                return False
+            time.sleep(0.2)
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/session.py 
new/Beaker-1.9.0/beaker/session.py
--- old/Beaker-1.8.1/beaker/session.py  2016-10-04 22:56:46.000000000 +0200
+++ new/Beaker-1.9.0/beaker/session.py  2017-06-18 22:41:24.000000000 +0200
@@ -3,7 +3,7 @@
 import os
 import time
 from datetime import datetime, timedelta
-from beaker.crypto import hmac as HMAC, hmac_sha1 as SHA1, sha1, 
get_nonce_size, DEFAULT_NONCE_BITS
+from beaker.crypto import hmac as HMAC, hmac_sha1 as SHA1, sha1, 
get_nonce_size, DEFAULT_NONCE_BITS, get_crypto_module
 from beaker import crypto, util
 from beaker.cache import clsmap
 from beaker.exceptions import BeakerException, InvalidCryptoBackendError
@@ -126,6 +126,7 @@
                                For security reason this is 128bits be default. 
If you want
                                to keep backward compatibility with sessions 
generated before 1.8.0
                                set this to 48.
+    :param crypto_type: encryption module to use
     """
     def __init__(self, request, id=None, invalidate_corrupt=False,
                  use_cookies=True, type=None, data_dir=None,
@@ -134,6 +135,7 @@
                  data_serializer='pickle', secret=None,
                  secure=False, namespace_class=None, httponly=False,
                  encrypt_key=None, validate_key=None, 
encrypt_nonce_bits=DEFAULT_NONCE_BITS,
+                 crypto_type='default',
                  **namespace_args):
         if not type:
             if data_dir:
@@ -170,6 +172,7 @@
         self.encrypt_key = encrypt_key
         self.validate_key = validate_key
         self.encrypt_nonce_size = get_nonce_size(encrypt_nonce_bits)
+        self.crypto_module = get_crypto_module(crypto_type)
         self.id = id
         self.accessed_dict = {}
         self.invalidate_corrupt = invalidate_corrupt
@@ -262,6 +265,7 @@
         return expires_date
 
     def _update_cookie_out(self, set_cookie=True):
+        self._set_cookie_values()
         self.request['cookie_out'] = self.cookie[self.key].output(header='')
         self.request['set_cookie'] = set_cookie
 
@@ -281,7 +285,6 @@
             self.is_new = True
             self.last_accessed = None
         if self.use_cookies:
-            self._set_cookie_values()
             sc = set_new is False
             self._update_cookie_out(set_cookie=sc)
 
@@ -290,8 +293,7 @@
         return self['_creation_time']
 
     def _set_domain(self, domain):
-        self['_domain'] = domain
-        self.cookie[self.key]['domain'] = domain
+        self['_domain'] = self._domain = domain
         self._update_cookie_out()
 
     def _get_domain(self):
@@ -301,7 +303,6 @@
 
     def _set_path(self, path):
         self['_path'] = self._path = path
-        self.cookie[self.key]['path'] = path
         self._update_cookie_out()
 
     def _get_path(self):
@@ -316,23 +317,27 @@
             nonce_len, nonce_b64len = self.encrypt_nonce_size
             nonce = b64encode(os.urandom(nonce_len))[:nonce_b64len]
             encrypt_key = crypto.generateCryptoKeys(self.encrypt_key,
-                                                    self.validate_key + nonce, 
1)
+                                                    self.validate_key + nonce,
+                                                    1,
+                                                    
self.crypto_module.getKeyLength())
             data = self.serializer.dumps(session_data)
-            return nonce + b64encode(crypto.aesEncrypt(data, encrypt_key))
+            return nonce + b64encode(self.crypto_module.aesEncrypt(data, 
encrypt_key))
         else:
             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
+        """Base64, decipher, then un-serialize the data for the session
         dict"""
         if self.encrypt_key:
             __, nonce_b64len = self.encrypt_nonce_size
             nonce = session_data[:nonce_b64len]
             encrypt_key = crypto.generateCryptoKeys(self.encrypt_key,
-                                                    self.validate_key + nonce, 
1)
+                                                    self.validate_key + nonce,
+                                                    1,
+                                                    
self.crypto_module.getKeyLength())
             payload = b64decode(session_data[nonce_b64len:])
-            data = crypto.aesDecrypt(payload, encrypt_key)
+            data = self.crypto_module.aesDecrypt(payload, encrypt_key)
         else:
             data = b64decode(session_data)
 
@@ -478,7 +483,7 @@
             creates a new session id, retains all session data
 
             Its a good security practice to regnerate the id after a client
-            elevates priviliges.
+            elevates privileges.
 
         """
         self._create_id(set_new=False)
@@ -541,15 +546,19 @@
                                invalidated and a new session created,
                                otherwise invalid data will cause an exception.
     :type invalidate_corrupt: bool
+    :param crypto_type: The crypto module to use.
     """
     def __init__(self, request, key='beaker.session.id', timeout=None,
                  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, 
invalidate_corrupt=False,
+                 crypto_type='default',
                  **kwargs):
 
-        if not crypto.has_aes and encrypt_key:
+        self.crypto_module = get_crypto_module(crypto_type)
+
+        if not self.crypto_module.has_aes and encrypt_key:
             raise InvalidCryptoBackendError("No AES library is installed, 
can't generate "
                                             "encrypted cookie-only Session.")
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/synchronization.py 
new/Beaker-1.9.0/beaker/synchronization.py
--- old/Beaker-1.8.1/beaker/synchronization.py  2015-02-25 16:37:38.000000000 
+0100
+++ new/Beaker-1.9.0/beaker/synchronization.py  2017-06-18 22:41:27.000000000 
+0200
@@ -197,13 +197,13 @@
     def do_release_read_lock(self):
         raise NotImplementedError()
 
-    def do_acquire_read_lock(self):
+    def do_acquire_read_lock(self, wait):
         raise NotImplementedError()
 
     def do_release_write_lock(self):
         raise NotImplementedError()
 
-    def do_acquire_write_lock(self):
+    def do_acquire_write_lock(self, wait):
         raise NotImplementedError()
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/beaker/util.py 
new/Beaker-1.9.0/beaker/util.py
--- old/Beaker-1.8.1/beaker/util.py     2016-09-17 17:07:29.000000000 +0200
+++ new/Beaker-1.9.0/beaker/util.py     2017-06-18 22:41:27.000000000 +0200
@@ -1,4 +1,9 @@
 """Beaker utilities"""
+import hashlib
+import socket
+
+import binascii
+
 from ._compat import PY2, string_type, unicode_text, NoneType, dictkeyslist, 
im_class, im_func, pickle, func_signature, \
     default_im_func
 
@@ -331,7 +336,7 @@
         ('expire', (int, NoneType),
          "expire must be an integer representing how many seconds the cache is 
valid for"),
         ('regions', (list, tuple, NoneType),
-         "Regions must be a comma seperated list of valid regions"),
+         "Regions must be a comma separated list of valid regions"),
         ('key_length', (int, NoneType),
          "key_length must be an integer which indicates the longest a key can 
be before hashing"),
     ]
@@ -478,3 +483,12 @@
     else:
         serializer = PickleSerializer()
     return serializer.loads(data_string)
+
+
+def machine_identifier():
+    machine_hash = hashlib.md5()
+    if not PY2:
+        machine_hash.update(socket.gethostname().encode())
+    else:
+        machine_hash.update(socket.gethostname())
+    return binascii.hexlify(machine_hash.digest()[0:3]).decode('ascii')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Beaker-1.8.1/setup.py new/Beaker-1.9.0/setup.py
--- old/Beaker-1.8.1/setup.py   2016-09-17 17:01:47.000000000 +0200
+++ new/Beaker-1.9.0/setup.py   2017-06-18 22:41:27.000000000 +0200
@@ -23,7 +23,12 @@
     INSTALL_REQUIRES.append('funcsigs')
 
 
-TESTS_REQUIRE = ['nose', 'webtest', 'Mock', 'pycrypto']
+TESTS_REQUIRE = ['nose', 'Mock', 'pycryptodome', 'cryptography']
+
+if py_version == (2, 6):
+    TESTS_REQUIRE.append('WebTest<2.0.24')
+else:
+    TESTS_REQUIRE.append('webtest')
 
 if py_version == (3, 2):
     TESTS_REQUIRE.append('coverage < 4.0')
@@ -31,12 +36,16 @@
     TESTS_REQUIRE.append('coverage')
 
 if not sys.platform.startswith('java') and not sys.platform == 'cli':
-    TESTS_REQUIRE.extend(['SQLALchemy'])
+    TESTS_REQUIRE.extend(['SQLALchemy', 'pymongo', 'redis'])
     try:
         import sqlite3
     except ImportError:
         TESTS_REQUIRE.append('pysqlite')
 
+    if py_version[0] == 2:
+        TESTS_REQUIRE.extend(['pylibmc', 'python-memcached'])
+
+
 setup(name='Beaker',
       version=VERSION,
       description="A Session and Caching library with WSGI Middleware",
@@ -69,6 +78,8 @@
       extras_require={
           'crypto': ['pycryptopp>=0.5.12'],
           'pycrypto': ['pycrypto'],
+          'pycryptodome': ['pycryptodome'],
+          'cryptography': ['cryptography'],
           'testsuite': [TESTS_REQUIRE]
       },
       test_suite='nose.collector',


Reply via email to