Hello community,

here is the log from the commit of package python-keyrings.alt for 
openSUSE:Factory checked in at 2017-06-07 09:55:40
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-keyrings.alt (Old)
 and      /work/SRC/openSUSE:Factory/.python-keyrings.alt.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-keyrings.alt"

Wed Jun  7 09:55:40 2017 rev:4 rq:501399 version:2.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-keyrings.alt/python-keyrings.alt.changes  
2017-05-10 20:36:10.904213834 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-keyrings.alt.new/python-keyrings.alt.changes 
    2017-06-07 09:56:27.997837806 +0200
@@ -1,0 +2,7 @@
+Tue Jun  6 12:41:35 UTC 2017 - dmuel...@suse.com
+
+- update to 2.2.:
+  * Drop dependency on keyring.py27compat and use six
+  instead.
+
+-------------------------------------------------------------------

Old:
----
  keyrings.alt-2.0.tar.gz

New:
----
  keyrings.alt-2.2.tar.gz

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

Other differences:
------------------
++++++ python-keyrings.alt.spec ++++++
--- /var/tmp/diff_new_pack.rDMKUO/_old  2017-06-07 09:56:28.873714033 +0200
+++ /var/tmp/diff_new_pack.rDMKUO/_new  2017-06-07 09:56:28.873714033 +0200
@@ -18,7 +18,7 @@
 
 
 Name:           python-keyrings.alt
-Version:        2.0
+Version:        2.2
 Release:        0
 Summary:        Alternate keyring implementations
 License:        MIT
@@ -38,11 +38,13 @@
 BuildRequires:  python-pycrypto
 BuildRequires:  python-pytest
 BuildRequires:  python-pytest-runner
+BuildRequires:  python-six
 Requires:       python-fs >= 0.5
 Requires:       python-gdata
 Requires:       python-keyczar
 Requires:       python-keyring
 Requires:       python-pycrypto
+Requires:       python-six
 # Testing requirements
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
 %if 0%{?suse_version} && 0%{?suse_version} <= 1110

++++++ keyrings.alt-2.0.tar.gz -> keyrings.alt-2.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/.coveragerc 
new/keyrings.alt-2.2/.coveragerc
--- old/keyrings.alt-2.0/.coveragerc    1970-01-01 01:00:00.000000000 +0100
+++ new/keyrings.alt-2.2/.coveragerc    2017-03-25 21:20:01.000000000 +0100
@@ -0,0 +1,29 @@
+[run]
+branch = True
+
+[report]
+exclude_lines =
+    # Have to re-enable the standard pragma
+    pragma: no cover
+
+    # Don't complain about missing debug-only code:
+    def __repr__
+
+    # Don't complain if tests don't hit defensive assertion code:
+    raise AssertionError
+    raise NotImplementedError
+
+    # Don't complain if non-runnable code isn't run:
+    if 0:
+    if __name__ == .__main__.:
+
+    # don't try to cover abstracts
+    @abc.abstractmethod
+    @abc.abstractproperty
+    
+    # don't try to cover special properties
+    @properties.NonDataProperty
+
+show_missing = True
+ignore_errors = True
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/.travis.yml 
new/keyrings.alt-2.2/.travis.yml
--- old/keyrings.alt-2.0/.travis.yml    2016-12-22 18:51:45.000000000 +0100
+++ new/keyrings.alt-2.2/.travis.yml    2017-03-25 21:20:01.000000000 +0100
@@ -2,7 +2,7 @@
 language: python
 python:
 - 2.7
-- 3.5
+- 3.6
 install:
 - pip install tox "setuptools>=28.2"
 script:
@@ -16,8 +16,9 @@
   on:
     tags: true
     all_branches: true
-    python: 3.5
+    python: 3.6
   user: jaraco
   distributions: dists
+  skip_upload_docs: true
   password:
     secure: 
iRtbY9yEN8qwJ7wiycL8N5NdZhRDENT5M8Bkpm7YSN5OiM41LRwFYnHDoQhYpz9L/UdfbovgEvv1XsQX/o8XRTn5efmhXRgS/FY3cA7Ry0GG41hmk2fnAgYuivA8EGoPveS9CnbU71ikL0HjRHv93+7Pz1kyMEYXRUpbMZ16K74o1J1MLOoCdQXXlKWfYjqAV/BglWwjIkwXVmNM2JwGV7U2hJ8zjsX1Bn7XqsZdC2KB6jK1frVjEHDfbv3NLQYIzQYqGw8GMWa+9EGVfFzqPnQmUC3E5GA93rC1SceBUb2RDBfCUpyFkyin3lQ05EvyVJoIaZO56gJ9Py1XzzSinSTjylgTxvFWx4uweowv5oM9OWnV+3SsZDw5+55fW5CYiMjdqjLso83gTkc8jbDHeK73Yh0sju2+QnOejnMzKRaBtCBubUkL+TPjEzoeUYSKLgW+iVHIKNKEKNME/WLzNtEhQwEFbLD5u/0bmHMBFPt40cXv3kSz6tz7NSjCHyJbtsBnyadYWRQTTRoSPnNIt0P716feUY/R5/Am5TIo2ZHKkGbRkuqFcJoJYhKau2M24WkRA4og8uBSyliShovHkrbahe1MpbJ/ZtmIU583FQjxIlGQQtNPN4QTSnQ1fUpvyhQY89GupbSxuek7jlmxmIm9SgAy6w9PUDZ5FKuJzm4=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/CHANGES.rst 
new/keyrings.alt-2.2/CHANGES.rst
--- old/keyrings.alt-2.0/CHANGES.rst    2016-12-22 18:51:45.000000000 +0100
+++ new/keyrings.alt-2.2/CHANGES.rst    2017-03-25 21:20:01.000000000 +0100
@@ -1,3 +1,17 @@
+2.2
+===
+
+#17: Drop dependency on keyring.py27compat and use six
+instead.
+
+#16: Minor tweaks to file-based backends.
+
+2.1
+===
+
+Add persistent scheme and version tags for file based backends.
+Prepare for associated data handling in file based schemes.
+
 2.0
 ===
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/PKG-INFO 
new/keyrings.alt-2.2/PKG-INFO
--- old/keyrings.alt-2.0/PKG-INFO       2016-12-22 18:53:14.000000000 +0100
+++ new/keyrings.alt-2.2/PKG-INFO       2017-03-25 21:22:20.000000000 +0100
@@ -1,6 +1,6 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.2
 Name: keyrings.alt
-Version: 2.0
+Version: 2.2
 Summary: Alternate keyring implementations
 Home-page: https://github.com/jaraco/keyrings.alt
 Author: Jason R. Coombs
@@ -19,15 +19,12 @@
         Alternate keyring backend implementations for use with the
         `keyring package <https://pypi.python.org/pypi/keyring>`_.
         
-        Docs
-        ====
+        License
+        =======
         
-        There's `no good mechanism for publishing documentation
-        <https://github.com/pypa/python-packaging-user-guide/pull/266>`_
-        easily. If there's a documentation link above, it's probably
-        stale because PyPI-based documentation is deprecated. This
-        project may have documentation published at ReadTheDocs, but
-        probably not. Good luck finding it.
+        License is indicated in the project metadata (typically one or more
+        of the Trove classifiers). For more details, see `this explanation
+        <https://github.com/jaraco/skeleton/issues/1>`_.
         
 Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
@@ -35,3 +32,4 @@
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
+Requires-Python: >=2.7
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/README.rst 
new/keyrings.alt-2.2/README.rst
--- old/keyrings.alt-2.0/README.rst     2016-12-22 18:51:45.000000000 +0100
+++ new/keyrings.alt-2.2/README.rst     2017-03-25 21:20:01.000000000 +0100
@@ -11,12 +11,9 @@
 Alternate keyring backend implementations for use with the
 `keyring package <https://pypi.python.org/pypi/keyring>`_.
 
-Docs
-====
+License
+=======
 
-There's `no good mechanism for publishing documentation
-<https://github.com/pypa/python-packaging-user-guide/pull/266>`_
-easily. If there's a documentation link above, it's probably
-stale because PyPI-based documentation is deprecated. This
-project may have documentation published at ReadTheDocs, but
-probably not. Good luck finding it.
+License is indicated in the project metadata (typically one or more
+of the Trove classifiers). For more details, see `this explanation
+<https://github.com/jaraco/skeleton/issues/1>`_.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/docs/conf.py 
new/keyrings.alt-2.2/docs/conf.py
--- old/keyrings.alt-2.0/docs/conf.py   2016-12-22 18:51:45.000000000 +0100
+++ new/keyrings.alt-2.2/docs/conf.py   2017-03-25 21:20:01.000000000 +0100
@@ -1,7 +1,13 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
-import pkg_resources
+import os
+import sys
+import subprocess
+import datetime
+
+if 'check_output' not in dir(subprocess):
+       import subprocess32 as subprocess
 
 extensions = [
     'sphinx.ext.autodoc',
@@ -9,11 +15,19 @@
 ]
 
 # General information about the project.
-project = 'keyrings.alt'
-copyright = '2016 Jason R. Coombs'
 
-# The short X.Y version.
-version = pkg_resources.require(project)[0].version
+root = os.path.join(os.path.dirname(__file__), '..')
+setup_script = os.path.join(root, 'setup.py')
+fields = ['--name', '--version', '--url', '--author']
+dist_info_cmd = [sys.executable, setup_script] + fields
+output_bytes = subprocess.check_output(dist_info_cmd, cwd=root)
+project, version, url, author = 
output_bytes.decode('utf-8').strip().split('\n')
+
+origin_date = datetime.date(2016,1,1)
+today = datetime.date.today()
+
+copyright = '{origin_date.year}-{today.year} {author}'.format(**locals())
+
 # The full version, including alpha/beta/rc tags.
 release = version
 
@@ -24,16 +38,21 @@
                using=dict(
                        GH='https://github.com',
                        project=project,
+                       url=url,
                ),
                replace=[
                        dict(
                                pattern=r"(Issue )?#(?P<issue>\d+)",
-                               url='{GH}/jaraco/{project}/issues/{issue}',
+                               url='{url}/issues/{issue}',
                        ),
                        dict(
                                
pattern=r"^(?m)((?P<scm_version>v?\d+(\.\d+){1,2}))\n[-=]+\n",
                                with_scm="{text}\n{rev[timestamp]:%d %b %Y}\n",
                        ),
+                       dict(
+                               pattern=r"PEP[- ](?P<pep_number>\d+)",
+                               
url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/',
+                       ),
                ],
        ),
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/keyrings/alt/Gnome.py 
new/keyrings.alt-2.2/keyrings/alt/Gnome.py
--- old/keyrings.alt-2.0/keyrings/alt/Gnome.py  2016-12-22 18:51:45.000000000 
+0100
+++ new/keyrings.alt-2.2/keyrings/alt/Gnome.py  2017-03-25 21:20:01.000000000 
+0100
@@ -5,10 +5,11 @@
 except (ImportError, ValueError):
     pass
 
+import six
+
 from keyring.backend import KeyringBackend
 from keyring.errors import PasswordSetError, PasswordDeleteError
 from keyring.util import properties
-from keyring.py27compat import unicode_str
 
 
 class Keyring(KeyringBackend):
@@ -65,7 +66,7 @@
             return None
 
         secret = items[0].secret
-        return secret if isinstance(secret, unicode_str) else 
secret.decode('utf-8')
+        return secret if isinstance(secret, six.text_type) else 
secret.decode('utf-8')
 
     def set_password(self, service, username, password):
         """Set password for the username of the service
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/keyrings/alt/Google.py 
new/keyrings.alt-2.2/keyrings/alt/Google.py
--- old/keyrings.alt-2.0/keyrings/alt/Google.py 2016-12-22 18:51:45.000000000 
+0100
+++ new/keyrings.alt-2.2/keyrings/alt/Google.py 2017-03-25 21:20:01.000000000 
+0100
@@ -6,6 +6,9 @@
 import codecs
 import base64
 import io
+import pickle
+
+from six.moves import input
 
 try:
     import gdata.docs.service
@@ -15,7 +18,6 @@
 from . import keyczar
 from keyring import errors
 from keyring import credentials
-from keyring.py27compat import input, pickle
 from keyring.backend import KeyringBackend
 from keyring.util import properties
 from keyring.errors import ExceptionRaisedContext
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/keyrings/alt/file.py 
new/keyrings.alt-2.2/keyrings/alt/file.py
--- old/keyrings.alt-2.0/keyrings/alt/file.py   2016-12-22 18:51:45.000000000 
+0100
+++ new/keyrings.alt-2.2/keyrings/alt/file.py   2017-03-25 21:20:01.000000000 
+0100
@@ -1,34 +1,36 @@
 from __future__ import with_statement
 
 import os
-import getpass
-import base64
 import sys
 import json
+import getpass
 
-from keyring.py27compat import configparser
+from six.moves import configparser
 
 from keyring.util import properties
 from keyring.util.escape import escape as escape_for_ini
 
-from . import file_base
-
+from keyrings.alt.file_base import (
+        Keyring, decodebytes, encodebytes,
+)
 
-class PlaintextKeyring(file_base.Keyring):
+class PlaintextKeyring(Keyring):
     """Simple File Keyring with no encryption"""
 
     priority = .5
     "Applicable for all platforms, but not recommended"
 
     filename = 'keyring_pass.cfg'
+    scheme = 'no encyption'
+    version = '1.0'
 
-    def encrypt(self, password):
-        """Directly return the password itself.
+    def encrypt(self, password, assoc = None):
+        """Directly return the password itself, ignore associated data.
         """
         return password
 
-    def decrypt(self, password_encrypted):
-        """Directly return encrypted password.
+    def decrypt(self, password_encrypted, assoc = None):
+        """Directly return encrypted password, ignore associated data.
         """
         return password_encrypted
 
@@ -37,7 +39,8 @@
     """
     PyCrypto-backed Encryption support
     """
-
+    scheme = '[PBKDF2] AES256.CFB'
+    version = '1.0'
     block_size = 32
 
     def _create_cipher(self, password, salt, IV):
@@ -54,17 +57,17 @@
             password = getpass.getpass(
                 "Please set a password for your new keyring: ")
             confirm = getpass.getpass('Please confirm the password: ')
-            if password != confirm:
+            if password != confirm:     # pragma: no cover
                 sys.stderr.write("Error: Your passwords didn't match\n")
                 continue
-            if '' == password.strip():
+            if '' == password.strip():  # pragma: no cover
                 # forbid the blank password
                 sys.stderr.write("Error: blank passwords aren't allowed.\n")
                 continue
             return password
 
 
-class EncryptedKeyring(Encrypted, file_base.Keyring):
+class EncryptedKeyring(Encrypted, Keyring):
     """PyCrypto File Keyring"""
 
     filename = 'crypted_pass.cfg'
@@ -78,9 +81,9 @@
             __import__('Crypto.Cipher.AES')
             __import__('Crypto.Protocol.KDF')
             __import__('Crypto.Random')
-        except ImportError:
+        except ImportError:     # pragma: no cover
             raise RuntimeError("PyCrypto required")
-        if not json:
+        if not json:            # pragma: no cover
             raise RuntimeError("JSON implementation such as simplejson "
                 "required.")
         return .6
@@ -101,8 +104,15 @@
         self.keyring_key = self._get_new_password()
         # set a reference password, used to check that the password provided
         #  matches for subsequent checks.
-        self.set_password('keyring-setting', 'password reference',
-            'password reference value')
+        self.set_password('keyring-setting',
+                          'password reference',
+                          'password reference value')
+        self._write_config_value('keyring-setting',
+                                 'scheme',
+                                 self.scheme)
+        self._write_config_value('keyring-setting',
+                                 'version',
+                                 self.version)
 
     def _check_file(self):
         """
@@ -120,6 +130,50 @@
             )
         except (configparser.NoSectionError, configparser.NoOptionError):
             return False
+        try:
+            self._check_scheme(config)
+        except AttributeError:
+            # accept a missing scheme
+            return True
+        return self._check_version(config)
+
+    def _check_scheme(self, config):
+        """
+        check for a valid scheme
+
+        raise ValueError otherwise
+        raise AttributeError if missing
+        """
+        try:
+            scheme = config.get(
+                escape_for_ini('keyring-setting'),
+                escape_for_ini('scheme'),
+            )
+        except (configparser.NoSectionError, configparser.NoOptionError):
+            raise AttributeError("Encryption scheme missing")
+
+        # remove pointless crypto module name
+        if scheme.startswith('PyCrypto '):
+            scheme = scheme[9:]
+
+        if scheme != self.scheme:
+            raise ValueError("Encryption scheme mismatch "
+                             "(exp.: %s, found: %s)" % (self.scheme, scheme))
+
+    def _check_version(self, config):
+        """
+        check for a valid version
+        an existing scheme implies an existing version as well
+
+        return True, if version is valid, and False otherwise
+        """
+        try:
+            self.file_version = config.get(
+                    escape_for_ini('keyring-setting'),
+                    escape_for_ini('version'),
+            )
+        except (configparser.NoSectionError, configparser.NoOptionError):
+            return False
         return True
 
     def _unlock(self):
@@ -142,7 +196,8 @@
         """
         del self.keyring_key
 
-    def encrypt(self, password):
+    def encrypt(self, password, assoc = None):
+        # encrypt password, ignore associated data
         from Crypto.Random import get_random_bytes
         salt = get_random_bytes(self.block_size)
         from Crypto.Cipher import AES
@@ -154,14 +209,15 @@
             salt=salt, IV=IV, password_encrypted=password_encrypted,
         )
         for key in data:
-            data[key] = base64.encodestring(data[key]).decode()
+            # spare a few bytes: throw away newline from base64 encoding
+            data[key] = encodebytes(data[key]).decode()[:-1]
         return json.dumps(data).encode()
 
-    def decrypt(self, password_encrypted):
-        # unpack the encrypted payload
+    def decrypt(self, password_encrypted, assoc = None):
+        # unpack the encrypted payload, ignore associated data
         data = json.loads(password_encrypted.decode())
         for key in data:
-            data[key] = base64.decodestring(data[key].encode())
+            data[key] = decodebytes(data[key].encode())
         cipher = self._create_cipher(self.keyring_key, data['salt'],
             data['IV'])
         plaintext = cipher.decrypt(data['password_encrypted'])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/keyrings/alt/file_base.py 
new/keyrings.alt-2.2/keyrings/alt/file_base.py
--- old/keyrings.alt-2.0/keyrings/alt/file_base.py      2016-12-22 
18:51:45.000000000 +0100
+++ new/keyrings.alt-2.2/keyrings/alt/file_base.py      2017-03-25 
21:20:01.000000000 +0100
@@ -1,16 +1,26 @@
 from __future__ import with_statement
 
 import os
-import base64
 import abc
+import base64
 
-from keyring.py27compat import configparser
+from six.moves import configparser
 
 from keyring.errors import PasswordDeleteError
 from keyring.backend import KeyringBackend
 from keyring.util import platform_, properties
 from keyring.util.escape import escape as escape_for_ini
 
+try:
+    encodebytes = base64.encodebytes
+except AttributeError:  # pragma: no cover
+    encodebytes = base64.encodestring
+
+try:
+    decodebytes = base64.decodebytes
+except AttributeError:  # pragma: no cover
+    decodebytes = base64.decodestring
+
 
 class FileBacked(object):
     @abc.abstractproperty
@@ -27,8 +37,30 @@
         """
         return os.path.join(platform_.data_root(), self.filename)
 
+    @abc.abstractproperty
+    def scheme(self):
+        """
+        The encryption scheme used to store the passwords.
+        """
+        return 'not defined'
+
+    @abc.abstractproperty
+    def version(self):
+        """
+        The encryption version used to store the passwords.
+        """
+        return None
+
+    @properties.NonDataProperty
+    def file_version(self):
+        """
+        The encryption version used in file to store the passwords.
+        """
+        return None
+
     def __repr__(self):
-        tmpl = "<{self.__class__.__name__} at {self.file_path}>"
+        tmpl = "<{self.__class__.__name__} with {self.scheme} " \
+               "v.{self.version} at {self.file_path}>"
         return tmpl.format(**locals())
 
 
@@ -43,22 +75,28 @@
     """
 
     @abc.abstractmethod
-    def encrypt(self, password):
+    def encrypt(self, password, assoc = None):
         """
-        Given a password (byte string), return an encrypted byte string.
+        Given a password (byte string) and assoc (byte string, optional),
+        return an encrypted byte string.
+
+        assoc provides associated data (typically: service and username)
         """
 
     @abc.abstractmethod
-    def decrypt(self, password_encrypted):
+    def decrypt(self, password_encrypted, assoc = None):
         """
-        Given a password encrypted by a previous call to `encrypt`, return
-        the original byte string.
+        Given a password encrypted by a previous call to `encrypt`, and assoc
+        (byte string, optional), return the original byte string.
+
+        assoc provides associated data (typically: service and username)
         """
 
     def get_password(self, service, username):
         """
         Read the password from the file.
         """
+        assoc = self._generate_assoc(service, username)
         service = escape_for_ini(service)
         username = escape_for_ini(username)
 
@@ -71,9 +109,13 @@
         try:
             password_base64 = config.get(service, username).encode()
             # decode with base64
-            password_encrypted = base64.decodestring(password_base64)
-            # decrypted the password
-            password = self.decrypt(password_encrypted).decode('utf-8')
+            password_encrypted = decodebytes(password_base64)
+            # decrypt the password with associated data
+            try:
+                password = self.decrypt(password_encrypted, 
assoc).decode('utf-8')
+            except ValueError:
+                # decrypt the password without associated data
+                password = self.decrypt(password_encrypted).decode('utf-8')
         except (configparser.NoOptionError, configparser.NoSectionError):
             password = None
         return password
@@ -81,14 +123,21 @@
     def set_password(self, service, username, password):
         """Write the password in the file.
         """
-        service = escape_for_ini(service)
-        username = escape_for_ini(username)
-
+        assoc = self._generate_assoc(service, username)
         # encrypt the password
-        password_encrypted = self.encrypt(password.encode('utf-8'))
-        # encode with base64
-        password_base64 = base64.encodestring(password_encrypted).decode()
+        password_encrypted = self.encrypt(password.encode('utf-8'), assoc)
+        # encode with base64 and add line break to untangle config file
+        password_base64 = '\n' + encodebytes(password_encrypted).decode()
+
+        self._write_config_value(service, username, password_base64)
+
+    def _generate_assoc(self, service, username):
+        """Generate tamper resistant bytestring of associated data
+        """
+        return (escape_for_ini(service) + '\0' +
+                escape_for_ini(username)).encode()
 
+    def _write_config_value(self, service, key, value):
         # ensure the file exists
         self._ensure_file_path()
 
@@ -96,10 +145,13 @@
         config = configparser.RawConfigParser()
         config.read(self.file_path)
 
+        service = escape_for_ini(service)
+        key = escape_for_ini(key)
+
         # update the keyring with the password
         if not config.has_section(service):
             config.add_section(service)
-        config.set(service, username, password_base64)
+        config.set(service, key, value)
 
         # save the keyring back to the file
         with open(self.file_path, 'w') as config_file:
@@ -111,7 +163,7 @@
         If it doesn't, create it with "go-rwx" permissions.
         """
         storage_root = os.path.dirname(self.file_path)
-        if storage_root and not os.path.isdir(storage_root):
+        if storage_root and not os.path.isdir(storage_root):    # pragma: no 
cover
             os.makedirs(storage_root)
         if not os.path.isfile(self.file_path):
             # create the file without group/world permissions
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/keyrings/alt/pyfs.py 
new/keyrings.alt-2.2/keyrings/alt/pyfs.py
--- old/keyrings.alt-2.0/keyrings/alt/pyfs.py   2016-12-22 18:51:45.000000000 
+0100
+++ new/keyrings.alt-2.2/keyrings/alt/pyfs.py   2017-03-25 21:20:01.000000000 
+0100
@@ -2,7 +2,7 @@
 import base64
 import sys
 
-from keyring.py27compat import configparser
+from six.moves import configparser
 
 from keyring import errors
 from keyring.util.escape import escape as escape_for_ini
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/keyrings.alt.egg-info/PKG-INFO 
new/keyrings.alt-2.2/keyrings.alt.egg-info/PKG-INFO
--- old/keyrings.alt-2.0/keyrings.alt.egg-info/PKG-INFO 2016-12-22 
18:53:14.000000000 +0100
+++ new/keyrings.alt-2.2/keyrings.alt.egg-info/PKG-INFO 2017-03-25 
21:22:20.000000000 +0100
@@ -1,6 +1,6 @@
-Metadata-Version: 1.1
+Metadata-Version: 1.2
 Name: keyrings.alt
-Version: 2.0
+Version: 2.2
 Summary: Alternate keyring implementations
 Home-page: https://github.com/jaraco/keyrings.alt
 Author: Jason R. Coombs
@@ -19,15 +19,12 @@
         Alternate keyring backend implementations for use with the
         `keyring package <https://pypi.python.org/pypi/keyring>`_.
         
-        Docs
-        ====
+        License
+        =======
         
-        There's `no good mechanism for publishing documentation
-        <https://github.com/pypa/python-packaging-user-guide/pull/266>`_
-        easily. If there's a documentation link above, it's probably
-        stale because PyPI-based documentation is deprecated. This
-        project may have documentation published at ReadTheDocs, but
-        probably not. Good luck finding it.
+        License is indicated in the project metadata (typically one or more
+        of the Trove classifiers). For more details, see `this explanation
+        <https://github.com/jaraco/skeleton/issues/1>`_.
         
 Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
@@ -35,3 +32,4 @@
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
+Requires-Python: >=2.7
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/keyrings.alt.egg-info/SOURCES.txt 
new/keyrings.alt-2.2/keyrings.alt.egg-info/SOURCES.txt
--- old/keyrings.alt-2.0/keyrings.alt.egg-info/SOURCES.txt      2016-12-22 
18:53:14.000000000 +0100
+++ new/keyrings.alt-2.2/keyrings.alt.egg-info/SOURCES.txt      2017-03-25 
21:22:20.000000000 +0100
@@ -1,3 +1,4 @@
+.coveragerc
 .travis.yml
 CHANGES.rst
 LICENSE
@@ -17,6 +18,7 @@
 keyrings.alt.egg-info/dependency_links.txt
 keyrings.alt.egg-info/entry_points.txt
 keyrings.alt.egg-info/namespace_packages.txt
+keyrings.alt.egg-info/requires.txt
 keyrings.alt.egg-info/top_level.txt
 keyrings/alt/Gnome.py
 keyrings/alt/Google.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/keyrings.alt.egg-info/requires.txt 
new/keyrings.alt-2.2/keyrings.alt.egg-info/requires.txt
--- old/keyrings.alt-2.0/keyrings.alt.egg-info/requires.txt     1970-01-01 
01:00:00.000000000 +0100
+++ new/keyrings.alt-2.2/keyrings.alt.egg-info/requires.txt     2017-03-25 
21:22:20.000000000 +0100
@@ -0,0 +1 @@
+six
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/setup.cfg 
new/keyrings.alt-2.2/setup.cfg
--- old/keyrings.alt-2.0/setup.cfg      2016-12-22 18:53:14.000000000 +0100
+++ new/keyrings.alt-2.2/setup.cfg      2017-03-25 21:22:20.000000000 +0100
@@ -5,9 +5,6 @@
 [wheel]
 universal = 1
 
-[upload]
-repository = https://upload.pypi.org/legacy/
-
 [egg_info]
 tag_build = 
 tag_date = 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/setup.py 
new/keyrings.alt-2.2/setup.py
--- old/keyrings.alt-2.0/setup.py       2016-12-22 18:51:45.000000000 +0100
+++ new/keyrings.alt-2.2/setup.py       2017-03-25 21:20:01.000000000 +0100
@@ -3,20 +3,16 @@
 # Project skeleton maintained at https://github.com/jaraco/skeleton
 
 import io
-import sys
 
 import setuptools
 
 with io.open('README.rst', encoding='utf-8') as readme:
     long_description = readme.read()
 
-needs_wheel = {'release', 'bdist_wheel', 'dists'}.intersection(sys.argv)
-wheel = ['wheel'] if needs_wheel else []
-
 name = 'keyrings.alt'
 description = 'Alternate keyring implementations'
 
-setup_params = dict(
+params = dict(
     name=name,
     use_scm_version=True,
     author="Jason R. Coombs",
@@ -27,13 +23,15 @@
     packages=setuptools.find_packages(exclude=['tests']),
     include_package_data=True,
     namespace_packages=name.split('.')[:-1],
+    python_requires='>=2.7',
     install_requires=[
+        'six',
     ],
     extras_require={
     },
     setup_requires=[
         'setuptools_scm>=1.15.0',
-    ] + wheel,
+    ],
     classifiers=[
         "Development Status :: 5 - Production/Stable",
         "Intended Audience :: Developers",
@@ -54,4 +52,4 @@
     },
 )
 if __name__ == '__main__':
-    setuptools.setup(**setup_params)
+    setuptools.setup(**params)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/tests/mocks.py 
new/keyrings.alt-2.2/tests/mocks.py
--- old/keyrings.alt-2.0/tests/mocks.py 2016-12-22 18:51:45.000000000 +0100
+++ new/keyrings.alt-2.2/tests/mocks.py 2017-03-25 21:20:01.000000000 +0100
@@ -4,8 +4,9 @@
 
 import base64
 import io
+import pickle
 
-from keyring.py27compat import pickle, unicode_str
+import six
 
 
 class MockAtom(object):
@@ -113,8 +114,7 @@
             else:
                 raise put_err()
         # save the data for asserting against
-        assert isinstance(data, str) or isinstance(data, unicode_str), \
-            'Should be a string'
+        assert isinstance(data, six.string_types), 'Should be a string'
         self._put_data =  pickle.loads(base64.urlsafe_b64decode(data))
         self._put_count += 1
         return MockEntry('', 'mockentry%3A' + '')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/tests/requirements.txt 
new/keyrings.alt-2.2/tests/requirements.txt
--- old/keyrings.alt-2.0/tests/requirements.txt 2016-12-22 18:51:45.000000000 
+0100
+++ new/keyrings.alt-2.2/tests/requirements.txt 2017-03-25 21:20:01.000000000 
+0100
@@ -1,6 +1,7 @@
 pytest >= 2.8
+subprocess32; python_version=="2.6"
 backports.unittest_mock
-keyring[test]
+keyring[test] >= 10.3.1
 
 fs>=0.5,<2
 pycrypto
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/tests/test_Google.py 
new/keyrings.alt-2.2/tests/test_Google.py
--- old/keyrings.alt-2.0/tests/test_Google.py   2016-12-22 18:51:45.000000000 
+0100
+++ new/keyrings.alt-2.2/tests/test_Google.py   2017-03-25 21:20:01.000000000 
+0100
@@ -1,13 +1,15 @@
 import codecs
 import base64
 import unittest
+import pickle
+
+from six.moves import input
 
 from keyring.tests.test_backend import BackendBasicTests
 from keyrings.alt import Google
 from keyring.credentials import SimpleCredential
 from keyring.backend import NullCrypter
 from keyring import errors
-from keyring.py27compat import input, pickle
 from . import mocks
 
 def is_gdata_supported():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/keyrings.alt-2.0/tests/test_file.py 
new/keyrings.alt-2.2/tests/test_file.py
--- old/keyrings.alt-2.0/tests/test_file.py     2016-12-22 18:51:45.000000000 
+0100
+++ new/keyrings.alt-2.2/tests/test_file.py     2017-03-25 21:20:01.000000000 
+0100
@@ -4,16 +4,26 @@
 import errno
 import unittest
 
+from six.moves import configparser
+
+import pytest
+from unittest import mock
+
 from keyring.tests.test_backend import BackendBasicTests
 from keyring.tests.util import random_string
 
+from keyring.util.escape import escape as escape_for_ini
+
 from keyrings.alt import file
+from keyrings.alt.file_base import encodebytes
+
+from keyring.errors import PasswordDeleteError
+
 
 class FileKeyringTests(BackendBasicTests):
 
     def setUp(self):
         super(FileKeyringTests, self).setUp()
-        self.keyring = self.init_keyring()
         self.keyring.file_path = self.tmp_keyring_file = tempfile.mktemp()
 
     def tearDown(self):
@@ -24,6 +34,16 @@
             if e.errno != errno.ENOENT: # No such file or directory
                 raise
 
+    def get_config(self):
+        # setting a password triggers keyring file creation
+        config = configparser.RawConfigParser()
+        config.read(self.keyring.file_path)
+        return config
+
+    def save_config(self, config):
+        with open(self.keyring.file_path, 'w') as config_file:
+            config.write(config_file)
+
     def test_encrypt_decrypt(self):
         password = random_string(20)
         # keyring.encrypt expects bytes
@@ -32,6 +52,147 @@
 
         self.assertEqual(password, self.keyring.decrypt(encrypted))
 
+    def test_encrypt_decrypt_without_assoc(self):
+        # generate keyring
+        self.keyring.set_password('system', 'user', 'password')
+        config = self.get_config()
+        # generate and save password without assoc data
+        encrypted = self.keyring.encrypt('password'.encode('utf-8'))
+        password_base64 = '\n' + encodebytes(encrypted).decode()
+        config.set('system', 'user', password_base64)
+        self.save_config(config)
+        self.assertEqual(self.keyring.get_password('system', 'user'), 
'password')
+
+    def test_delete_password(self):
+        self.keyring.set_password('system', 'user', 'password')
+        with pytest.raises(PasswordDeleteError):
+            self.keyring.delete_password('system', 'xxxx')
+        with pytest.raises(PasswordDeleteError):
+            self.keyring.delete_password('xxxxxx', 'xxxx')
+
+    def test_file(self):
+        if not hasattr(self.keyring, '_check_file'):
+            return
+        # keyring file doesn't exist yet
+        self.assertTrue(self.keyring._check_file() == False)
+        # generate keyring
+        self.keyring.set_password('system', 'user', 'password')
+        # valid keyring file exist now
+        self.assertTrue(self.keyring._check_file() == True)
+        # lock keyring
+        self.keyring._lock()
+        # fetch password from keyring
+        self.assertTrue(self.keyring.get_password('system', 'user') == 
'password')
+        # test missing password reference
+        config = self.get_config()
+        krsetting = escape_for_ini('keyring-setting')
+        pwref = escape_for_ini('password reference')
+        #pwrefval = config.get(krsetting, pwref)
+        config.remove_option(krsetting, pwref)
+        self.save_config(config)
+        self.assertTrue(self.keyring._check_file() == False)
+
+    def test_scheme(self):
+        # scheme exists
+        self.assertTrue(self.keyring.scheme is not None)
+        if not hasattr(self.keyring, '_check_file'):
+            return
+
+        # keyring file doesn't exist yet
+        self.assertTrue(self.keyring._check_file() == False)
+        # generate keyring
+        self.keyring.set_password('system', 'user', 'password')
+        config = self.get_config()
+        krsetting = escape_for_ini('keyring-setting')
+        scheme = escape_for_ini('scheme')
+        defscheme = '[PBKDF2] AES256.CFB'
+
+        # default scheme match
+        self.assertTrue(config.get(krsetting, scheme) == defscheme)
+
+        # invalid AES mode
+        config.set(krsetting, scheme, defscheme.replace('CFB', 'XXX'))
+        with pytest.raises(ValueError):
+            self.keyring._check_scheme(config)
+
+         # compatibility with former scheme format
+        config.set(krsetting, scheme, 'PyCrypto ' + defscheme)
+        self.assertTrue(self.keyring._check_scheme(config) == None)
+
+        # test with invalid KDF
+        config.set(krsetting, scheme, defscheme.replace('PBKDF2', 'scrypt'))
+        with pytest.raises(ValueError):
+            self.keyring._check_scheme(config)
+
+        # a missing scheme is valid
+        config.remove_option(krsetting, scheme)
+        self.save_config(config)
+        self.assertTrue(self.keyring._check_file() == True)
+
+        with pytest.raises(AttributeError):
+            self.keyring._check_scheme(config)
+
+    def test_version(self):
+        # version exists
+        self.assertTrue(self.keyring.version is not None)
+        if not hasattr(self.keyring, '_check_version'):
+            return
+
+        # generate keyring
+        self.keyring.set_password('system', 'user', 'password')
+        config = self.get_config()
+
+        # default version valid
+        self.assertTrue(self.keyring._check_version(config) == True)
+
+        krsetting = escape_for_ini('keyring-setting')
+        version = escape_for_ini('version')
+
+        # invalid, if version is missing
+        config.remove_option(krsetting, version)
+        self.save_config(config)
+        self.assertTrue(self.keyring._check_version(config) == False)
+
+
+class EncryptedFileKeyringTestCase(FileKeyringTests, unittest.TestCase):
+
+    def setUp(self):
+        super(EncryptedFileKeyringTestCase, self).setUp()
+        self.mock_getpass()
+
+    def mock_getpass(self, password = 'abcdef'):
+        fake_getpass = mock.Mock(return_value=password)
+        self.patcher = mock.patch('getpass.getpass', fake_getpass)
+        self.patcher.start()
+
+    def tearDown(self):
+        self.patcher.stop()
+
+    def init_keyring(self):
+        return file.EncryptedKeyring()
+
+    def test_wrong_password(self):
+        self.keyring.set_password('system', 'user', 'password')
+        self.patcher.stop()
+        self.mock_getpass('wrong')
+        with pytest.raises(ValueError):
+            self.keyring._unlock()
+        self.patcher.stop()
+        self.mock_getpass()
+
+    @unittest.skipIf(sys.platform == 'win32',
+        "Group/World permissions aren't meaningful on Windows")
+    def test_keyring_not_created_world_writable(self):
+        """
+        Ensure that when keyring creates the file that it's not overly-
+        permissive.
+        """
+        self.keyring.set_password('system', 'user', 'password')
+
+        self.assertTrue(os.path.exists(self.keyring.file_path))
+        group_other_perms = os.stat(self.keyring.file_path).st_mode & 0o077
+        self.assertEqual(group_other_perms, 0)
+
 
 class UncryptedFileKeyringTestCase(FileKeyringTests, unittest.TestCase):
 


Reply via email to