commit python-Beaker for openSUSE:Factory
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 - toddrme2178@gmail.com + +- 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 - jmatejek@suse.com + +- 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',
participants (1)
-
root@hilbert.suse.de