Hello community,
here is the log from the commit of package python-SQLAlchemy-Utils for
openSUSE:Factory checked in at 2018-05-08 13:38:48
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-SQLAlchemy-Utils (Old)
and /work/SRC/openSUSE:Factory/.python-SQLAlchemy-Utils.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-SQLAlchemy-Utils"
Tue May 8 13:38:48 2018 rev:10 rq:605121 version:0.33.3
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-SQLAlchemy-Utils/python-SQLAlchemy-Utils.changes
2018-03-26 13:13:19.539208875 +0200
+++
/work/SRC/openSUSE:Factory/.python-SQLAlchemy-Utils.new/python-SQLAlchemy-Utils.changes
2018-05-08 13:38:49.854324337 +0200
@@ -1,0 +2,13 @@
+Sun May 6 05:44:06 UTC 2018 - [email protected]
+
+- update to version 0.33.3:
+ * Added new AesGcmEngine (#322, pull request courtesy of
+ manishahluwalia)
+
+- changes from version 0.33.2:
+ * Added support for universal wheels (#312, pull request courtesy of
+ nsoranzo)
+ * Fixed usage of template0 and template1 with postgres database
+ functions. (#286, pull request courtesy of funkybob)
+
+-------------------------------------------------------------------
Old:
----
SQLAlchemy-Utils-0.33.1.tar.gz
New:
----
SQLAlchemy-Utils-0.33.3.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-SQLAlchemy-Utils.spec ++++++
--- /var/tmp/diff_new_pack.DydxIu/_old 2018-05-08 13:38:50.410304268 +0200
+++ /var/tmp/diff_new_pack.DydxIu/_new 2018-05-08 13:38:50.414304125 +0200
@@ -18,21 +18,19 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-SQLAlchemy-Utils
-Version: 0.33.1
+Version: 0.33.3
Release: 0
Summary: Various utility functions for SQLAlchemy
License: BSD-3-Clause
Group: Development/Languages/Python
-Url: https://github.com/kvesteri/sqlalchemy-utils
+URL: https://github.com/kvesteri/sqlalchemy-utils
Source:
https://files.pythonhosted.org/packages/source/S/SQLAlchemy-Utils/SQLAlchemy-Utils-%{version}.tar.gz
BuildRequires: %{python_module SQLAlchemy}
BuildRequires: %{python_module devel}
BuildRequires: %{python_module setuptools}
BuildRequires: python-rpm-macros
Requires: python-SQLAlchemy
-BuildRoot: %{_tmppath}/%{name}-%{version}-build
BuildArch: noarch
-
%python_subpackages
%description
@@ -48,8 +46,8 @@
%python_install
%files %{python_files}
-%defattr(-,root,root,-)
-%doc LICENSE README.rst
+%license LICENSE
+%doc README.rst
%{python_sitelib}/*
%changelog
++++++ SQLAlchemy-Utils-0.33.1.tar.gz -> SQLAlchemy-Utils-0.33.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/SQLAlchemy-Utils-0.33.1/CHANGES.rst
new/SQLAlchemy-Utils-0.33.3/CHANGES.rst
--- old/SQLAlchemy-Utils-0.33.1/CHANGES.rst 2018-03-19 15:50:26.000000000
+0100
+++ new/SQLAlchemy-Utils-0.33.3/CHANGES.rst 2018-04-29 09:12:35.000000000
+0200
@@ -4,6 +4,19 @@
Here you can see the full list of changes between each SQLAlchemy-Utils
release.
+0.33.3 (2018-04-29)
+^^^^^^^^^^^^^^^^^^^
+
+- Added new AesGcmEngine (#322, pull request courtesy of manishahluwalia)
+
+
+0.33.2 (2018-04-02)
+^^^^^^^^^^^^^^^^^^^
+
+- Added support for universal wheels (#312, pull request courtesy of nsoranzo)
+- Fixed usage of template0 and template1 with postgres database functions.
(#286, pull request courtesy of funkybob)
+
+
0.33.1 (2018-03-19)
^^^^^^^^^^^^^^^^^^^
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/SQLAlchemy-Utils-0.33.1/PKG-INFO
new/SQLAlchemy-Utils-0.33.3/PKG-INFO
--- old/SQLAlchemy-Utils-0.33.1/PKG-INFO 2018-03-19 15:54:38.000000000
+0100
+++ new/SQLAlchemy-Utils-0.33.3/PKG-INFO 2018-04-29 09:24:56.000000000
+0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: SQLAlchemy-Utils
-Version: 0.33.1
+Version: 0.33.3
Summary: Various utility functions for SQLAlchemy.
Home-page: https://github.com/kvesteri/sqlalchemy-utils
Author: Konsta Vesterinen, Ryan Leckey, Janne Vanhala, Vesa Uimonen
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/SQLAlchemy-Utils-0.33.1/SQLAlchemy_Utils.egg-info/PKG-INFO
new/SQLAlchemy-Utils-0.33.3/SQLAlchemy_Utils.egg-info/PKG-INFO
--- old/SQLAlchemy-Utils-0.33.1/SQLAlchemy_Utils.egg-info/PKG-INFO
2018-03-19 15:54:38.000000000 +0100
+++ new/SQLAlchemy-Utils-0.33.3/SQLAlchemy_Utils.egg-info/PKG-INFO
2018-04-29 09:24:56.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: SQLAlchemy-Utils
-Version: 0.33.1
+Version: 0.33.3
Summary: Various utility functions for SQLAlchemy.
Home-page: https://github.com/kvesteri/sqlalchemy-utils
Author: Konsta Vesterinen, Ryan Leckey, Janne Vanhala, Vesa Uimonen
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/SQLAlchemy-Utils-0.33.1/SQLAlchemy_Utils.egg-info/SOURCES.txt
new/SQLAlchemy-Utils-0.33.3/SQLAlchemy_Utils.egg-info/SOURCES.txt
--- old/SQLAlchemy-Utils-0.33.1/SQLAlchemy_Utils.egg-info/SOURCES.txt
2018-03-19 15:54:38.000000000 +0100
+++ new/SQLAlchemy-Utils-0.33.3/SQLAlchemy_Utils.egg-info/SOURCES.txt
2018-04-29 09:24:56.000000000 +0200
@@ -4,6 +4,7 @@
MANIFEST.in
README.rst
conftest.py
+setup.cfg
setup.py
SQLAlchemy_Utils.egg-info/PKG-INFO
SQLAlchemy_Utils.egg-info/SOURCES.txt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/SQLAlchemy-Utils-0.33.1/setup.cfg
new/SQLAlchemy-Utils-0.33.3/setup.cfg
--- old/SQLAlchemy-Utils-0.33.1/setup.cfg 2018-03-19 15:54:38.000000000
+0100
+++ new/SQLAlchemy-Utils-0.33.3/setup.cfg 2018-04-29 09:24:56.000000000
+0200
@@ -1,3 +1,6 @@
+[bdist_wheel]
+universal = 1
+
[egg_info]
tag_build =
tag_date = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/SQLAlchemy-Utils-0.33.1/sqlalchemy_utils/__init__.py
new/SQLAlchemy-Utils-0.33.3/sqlalchemy_utils/__init__.py
--- old/SQLAlchemy-Utils-0.33.1/sqlalchemy_utils/__init__.py 2018-03-19
15:50:31.000000000 +0100
+++ new/SQLAlchemy-Utils-0.33.3/sqlalchemy_utils/__init__.py 2018-04-29
09:12:41.000000000 +0200
@@ -99,4 +99,4 @@
WeekDaysType
)
-__version__ = '0.33.1'
+__version__ = '0.33.3'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/SQLAlchemy-Utils-0.33.1/sqlalchemy_utils/functions/database.py
new/SQLAlchemy-Utils-0.33.3/sqlalchemy_utils/functions/database.py
--- old/SQLAlchemy-Utils-0.33.1/sqlalchemy_utils/functions/database.py
2017-11-11 18:05:01.000000000 +0100
+++ new/SQLAlchemy-Utils-0.33.3/sqlalchemy_utils/functions/database.py
2018-04-02 16:53:39.000000000 +0200
@@ -461,7 +461,7 @@
url = copy(make_url(url))
database = url.database
if url.drivername.startswith('postgres'):
- url.database = 'template1'
+ url.database = 'postgres'
else:
url.database = None
@@ -529,7 +529,7 @@
database = url.database
if url.drivername.startswith('postgres'):
- url.database = 'template1'
+ url.database = 'postgres'
elif not url.drivername.startswith('sqlite'):
url.database = None
@@ -544,7 +544,7 @@
)
if not template:
- template = 'template0'
+ template = 'template1'
text = "CREATE DATABASE {0} ENCODING '{1}' TEMPLATE {2}".format(
quote(engine, database),
@@ -591,7 +591,7 @@
database = url.database
if url.drivername.startswith('postgres'):
- url.database = 'template1'
+ url.database = 'postgres'
elif not url.drivername.startswith('sqlite'):
url.database = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/SQLAlchemy-Utils-0.33.1/sqlalchemy_utils/types/encrypted/encrypted_type.py
new/SQLAlchemy-Utils-0.33.3/sqlalchemy_utils/types/encrypted/encrypted_type.py
---
old/SQLAlchemy-Utils-0.33.1/sqlalchemy_utils/types/encrypted/encrypted_type.py
2018-02-18 15:38:57.000000000 +0100
+++
new/SQLAlchemy-Utils-0.33.3/sqlalchemy_utils/types/encrypted/encrypted_type.py
2018-04-29 09:09:37.000000000 +0200
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import base64
import datetime
+import os
import six
from sqlalchemy.types import LargeBinary, String, TypeDecorator
@@ -18,6 +19,7 @@
Cipher, algorithms, modes
)
from cryptography.fernet import Fernet
+ from cryptography.exceptions import InvalidTag
except ImportError:
pass
@@ -30,6 +32,10 @@
pass
+class InvalidCiphertextError(Exception):
+ pass
+
+
class EncryptionDecryptionBaseEngine(object):
"""A base encryption and decryption engine.
@@ -54,7 +60,18 @@
class AesEngine(EncryptionDecryptionBaseEngine):
- """Provide AES encryption and decryption methods."""
+ """Provide AES encryption and decryption methods.
+
+ You may also consider using the AesGcmEngine instead -- that may be
+ a better fit for some cases.
+
+ You should NOT use the AesGcmEngine if you want to be able to search
+ for a row based on the value of an encrypted column. Use AesEngine
+ instead, since that allows you to perform such searches.
+
+ If you don't need to search by the value of an encypted column, the
+ AesGcmEngine provides better security.
+ """
BLOCK_SIZE = 16
@@ -110,6 +127,73 @@
return decrypted
+class AesGcmEngine(EncryptionDecryptionBaseEngine):
+ """Provide AES/GCM encryption and decryption methods.
+
+ You may also consider using the AesEngine instead -- that may be
+ a better fit for some cases.
+
+ You should NOT use this AesGcmEngine if you want to be able to search
+ for a row based on the value of an encrypted column. Use AesEngine
+ instead, since that allows you to perform such searches.
+
+ If you don't need to search by the value of an encypted column, the
+ AesGcmEngine provides better security.
+ """
+
+ BLOCK_SIZE = 16
+ IV_BYTES_NEEDED = 12
+ TAG_SIZE_BYTES = BLOCK_SIZE
+
+ def _initialize_engine(self, parent_class_key):
+ self.secret_key = parent_class_key
+
+ def encrypt(self, value):
+ if not isinstance(value, six.string_types):
+ value = repr(value)
+ if isinstance(value, six.text_type):
+ value = str(value)
+ value = value.encode()
+ iv = os.urandom(self.IV_BYTES_NEEDED)
+ cipher = Cipher(
+ algorithms.AES(self.secret_key),
+ modes.GCM(iv),
+ backend=default_backend()
+ )
+ encryptor = cipher.encryptor()
+ encrypted = encryptor.update(value) + encryptor.finalize()
+ assert len(encryptor.tag) == self.TAG_SIZE_BYTES
+ encrypted = base64.b64encode(iv + encryptor.tag + encrypted)
+ return encrypted
+
+ def decrypt(self, value):
+ if isinstance(value, six.text_type):
+ value = str(value)
+ decrypted = base64.b64decode(value)
+ if len(decrypted) < self.IV_BYTES_NEEDED + self.TAG_SIZE_BYTES:
+ raise InvalidCiphertextError()
+ iv = decrypted[:self.IV_BYTES_NEEDED]
+ tag = decrypted[self.IV_BYTES_NEEDED:
+ self.IV_BYTES_NEEDED + self.TAG_SIZE_BYTES]
+ decrypted = decrypted[self.IV_BYTES_NEEDED + self.TAG_SIZE_BYTES:]
+ cipher = Cipher(
+ algorithms.AES(self.secret_key),
+ modes.GCM(iv, tag),
+ backend=default_backend()
+ )
+ decryptor = cipher.decryptor()
+ try:
+ decrypted = decryptor.update(decrypted) + decryptor.finalize()
+ except InvalidTag:
+ raise InvalidCiphertextError()
+ if not isinstance(decrypted, six.string_types):
+ try:
+ decrypted = decrypted.decode('utf-8')
+ except UnicodeDecodeError:
+ raise InvalidCiphertextError()
+ return decrypted
+
+
class FernetEngine(EncryptionDecryptionBaseEngine):
"""Provide Fernet encryption and decryption methods."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/SQLAlchemy-Utils-0.33.1/tests/types/test_encrypted.py
new/SQLAlchemy-Utils-0.33.3/tests/types/test_encrypted.py
--- old/SQLAlchemy-Utils-0.33.1/tests/types/test_encrypted.py 2018-02-18
15:38:57.000000000 +0100
+++ new/SQLAlchemy-Utils-0.33.3/tests/types/test_encrypted.py 2018-04-29
09:09:37.000000000 +0200
@@ -6,8 +6,10 @@
from sqlalchemy_utils import ColorType, EncryptedType, PhoneNumberType
from sqlalchemy_utils.types.encrypted.encrypted_type import (
AesEngine,
+ AesGcmEngine,
DatetimeHandler,
- FernetEngine
+ FernetEngine,
+ InvalidCiphertextError
)
cryptography = None
@@ -441,3 +443,93 @@
original_date_isoformat,
python_type
) == original_date
+
+
[email protected]('cryptography is None')
+class TestAesGcmEngine(object):
+ KEY = b'0123456789ABCDEF'
+
+ def setup_method(self):
+ self.engine = AesGcmEngine()
+ self.engine._initialize_engine(TestAesGcmEngine.KEY)
+
+ def test_roundtrip(self):
+ for l in range(0, 36):
+ plaintext = '0123456789abcdefghijklmnopqrstuvwxyz'[:l]
+ encrypted = self.engine.encrypt(plaintext)
+ decrypted = self.engine.decrypt(encrypted)
+ assert plaintext == decrypted, "Round-trip failed for len: %d" % l
+
+ def test_modified_iv_fails_to_decrypt(self):
+ plaintext = 'abcdefgh'
+ encrypted = self.engine.encrypt(plaintext)
+ # 3rd char will be IV. Modify it
+ POS = 3
+ encrypted = encrypted[:POS] + \
+ (b'A' if encrypted[POS] != b'A' else b'B') + \
+ encrypted[POS + 1:]
+ with pytest.raises(InvalidCiphertextError):
+ self.engine.decrypt(encrypted)
+
+ def test_modified_tag_fails_to_decrypt(self):
+ plaintext = 'abcdefgh'
+ encrypted = self.engine.encrypt(plaintext)
+ # 19th char will be tag. Modify it
+ POS = 19
+ encrypted = encrypted[:POS] + \
+ (b'A' if encrypted[POS] != b'A' else b'B') + \
+ encrypted[POS + 1:]
+ with pytest.raises(InvalidCiphertextError):
+ self.engine.decrypt(encrypted)
+
+ def test_modified_ciphertext_fails_to_decrypt(self):
+ plaintext = 'abcdefgh'
+ encrypted = self.engine.encrypt(plaintext)
+ # 43rd char will be ciphertext. Modify it
+ POS = 43
+ encrypted = encrypted[:POS] + \
+ (b'A' if encrypted[POS] != b'A' else b'B') + \
+ encrypted[POS + 1:]
+ with pytest.raises(InvalidCiphertextError):
+ self.engine.decrypt(encrypted)
+
+ def test_too_short_ciphertext_fails_to_decrypt(self):
+ plaintext = 'abcdefgh'
+ encrypted = self.engine.encrypt(plaintext)[:20]
+ with pytest.raises(InvalidCiphertextError):
+ self.engine.decrypt(encrypted)
+
+ def test_different_ciphertexts_each_time(self):
+ plaintext = 'abcdefgh'
+ encrypted1 = self.engine.encrypt(plaintext)
+ encrypted2 = self.engine.encrypt(plaintext)
+ assert self.engine.decrypt(encrypted1) == \
+ self.engine.decrypt(encrypted2)
+ # The following has a very low probability of failing
+ # accidentally (2^-96)
+ assert encrypted1 != encrypted2
+
+
+class TestAesGcmEncryptedType(EncryptedTypeTestCase):
+
+ @pytest.fixture
+ def encryption_engine(self):
+ return AesGcmEngine
+
+ # GCM doesn't need padding. This is here just because we're reusing test
+ # code that requires this
+ @pytest.fixture
+ def padding_mechanism(self):
+ return 'pkcs5'
+
+ def test_lookup_by_encrypted_string(self, session, User, user, user_name):
+ test = session.query(User).filter(
+ User.username == "someonex"
+ ).first()
+
+ # With probability 1-2^-96, the 2 different encryptions will choose a
+ # different IV, and will therefore result in different ciphertexts.
+ # Thus, the 2 values will almost certainly be different, even though
+ # we're really searching for the same username. Hence, the above search
+ # will fail
+ assert test is None