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


Reply via email to