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):