Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-keyring-pass for openSUSE:Factory checked in at 2024-01-08 23:46:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-keyring-pass (Old) and /work/SRC/openSUSE:Factory/.python-keyring-pass.new.21961 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-keyring-pass" Mon Jan 8 23:46:32 2024 rev:2 rq:1137571 version:0.9.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-keyring-pass/python-keyring-pass.changes 2022-10-03 13:45:03.845389300 +0200 +++ /work/SRC/openSUSE:Factory/.python-keyring-pass.new.21961/python-keyring-pass.changes 2024-01-08 23:46:38.261312400 +0100 @@ -1,0 +2,9 @@ +Mon Jan 8 13:14:52 UTC 2024 - Matej Cepl <mc...@cepl.eu> + +- Update to 0.9.2: + - add get_credential() + - Use functools.lru_cache when functools.cache is not available by @bo5o in #11 + - Bump cryptography to 41.0.2 + - v0.8.0: migrate to Poetry & add configurable binary + +------------------------------------------------------------------- Old: ---- keyring_pass-0.7.1.tar.gz New: ---- keyring_pass-0.9.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-keyring-pass.spec ++++++ --- /var/tmp/diff_new_pack.NY3uNj/_old 2024-01-08 23:46:39.685364177 +0100 +++ /var/tmp/diff_new_pack.NY3uNj/_new 2024-01-08 23:46:39.693364468 +0100 @@ -1,7 +1,7 @@ # -# spec file for package keyring-pass +# spec file for package python-keyring-pass # -# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,15 +16,16 @@ # -%{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-keyring-pass -Version: 0.7.1 +Version: 0.9.2 Release: 0 Summary: Pass backend for python-keyring License: MIT URL: https://github.com/nazarewk/keyring_pass Source: https://files.pythonhosted.org/packages/source/k/keyring_pass/keyring_pass-%{version}.tar.gz -BuildRequires: %{python_module setuptools} +BuildRequires: %{python_module pip} +BuildRequires: %{python_module poetry-core} +BuildRequires: %{python_module wheel} BuildRequires: fdupes BuildRequires: python-rpm-macros Requires: python-jaraco.classes @@ -43,10 +44,10 @@ %autosetup -p1 -n keyring_pass-%{version} %build -%python_build +%pyproject_wheel %install -%python_install +%pyproject_install %python_expand %fdupes %{buildroot}%{$python_sitelib} %files %{python_files} ++++++ keyring_pass-0.7.1.tar.gz -> keyring_pass-0.9.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring_pass-0.7.1/PKG-INFO new/keyring_pass-0.9.2/PKG-INFO --- old/keyring_pass-0.7.1/PKG-INFO 2022-09-13 12:23:35.634640500 +0200 +++ new/keyring_pass-0.9.2/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,36 +1,59 @@ Metadata-Version: 2.1 -Name: keyring_pass -Version: 0.7.1 +Name: keyring-pass +Version: 0.9.2 Summary: https://www.passwordstore.org/ backend for https://pypi.org/project/keyring/ Home-page: https://github.com/nazarewk/keyring_pass +License: MIT +Keywords: keyring,pass Author: Krzysztof Nazarewski Author-email: 3494992+nazar...@users.noreply.github.com -Classifier: Programming Language :: Python :: 3 +Requires-Python: >=3.7 Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: OS Independent -Requires-Python: >3.3 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Requires-Dist: jaraco-classes (>=3.2.3) +Requires-Dist: keyring (>=23.9.3) +Project-URL: Repository, https://github.com/nazarewk/keyring_pass Description-Content-Type: text/markdown -License-File: LICENSE # keyring_pass This is a [`pass`](https://www.passwordstore.org/) backend for [`keyring`](https://pypi.org/project/keyring/) -Install with `pip install keyring-pass` and set the following content in your [`keyringrc.cfg`](https://pypi.org/project/keyring/#config-file-path) file: +Install with `pip install keyring-pass` and set the following content in +your [`keyringrc.cfg`](https://pypi.org/project/keyring/#config-file-path) file: ```ini [backend] -default-keyring=keyring_pass.PasswordStoreBackend +default-keyring = keyring_pass.PasswordStoreBackend ``` You can modify the default `python-keyring` prefix for `pass`, by: + - adding following to `keyringrc.cfg`: - + ```ini [pass] key-prefix=alternative/prefix/path + binary=gopass ``` -- (for `keyring` version 23.0.0 or higher) setting environment variable `KEYRING_PROPERTY_PASS_KEY_PREFIX` +- (for `keyring` version 23.0.0 or higher) setting environment variables: + - `KEYRING_PROPERTY_PASS_KEY_PREFIX` + - `KEYRING_PROPERTY_PASS_BINARY` + +- You can clear the path (start from root), by setting above to `.` or an empty value (just `key-prefix=` on the line). + +## Test your setup + +You can check if your setup works end-to-end (creates, reads and deletes a key from password store). + +```shell +# warning: this will create and delete a key at `<prefix>/test/asd` in your password store +python -m keyring_pass +``` -You can clear the path (store in root), by setting above to `.` or an empty value. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring_pass-0.7.1/README.md new/keyring_pass-0.9.2/README.md --- old/keyring_pass-0.7.1/README.md 2022-09-13 10:38:21.000000000 +0200 +++ new/keyring_pass-0.9.2/README.md 2022-10-20 15:22:58.861889600 +0200 @@ -2,21 +2,35 @@ This is a [`pass`](https://www.passwordstore.org/) backend for [`keyring`](https://pypi.org/project/keyring/) -Install with `pip install keyring-pass` and set the following content in your [`keyringrc.cfg`](https://pypi.org/project/keyring/#config-file-path) file: +Install with `pip install keyring-pass` and set the following content in +your [`keyringrc.cfg`](https://pypi.org/project/keyring/#config-file-path) file: ```ini [backend] -default-keyring=keyring_pass.PasswordStoreBackend +default-keyring = keyring_pass.PasswordStoreBackend ``` You can modify the default `python-keyring` prefix for `pass`, by: + - adding following to `keyringrc.cfg`: - + ```ini [pass] key-prefix=alternative/prefix/path + binary=gopass ``` -- (for `keyring` version 23.0.0 or higher) setting environment variable `KEYRING_PROPERTY_PASS_KEY_PREFIX` +- (for `keyring` version 23.0.0 or higher) setting environment variables: + - `KEYRING_PROPERTY_PASS_KEY_PREFIX` + - `KEYRING_PROPERTY_PASS_BINARY` + +- You can clear the path (start from root), by setting above to `.` or an empty value (just `key-prefix=` on the line). -You can clear the path (store in root), by setting above to `.` or an empty value. +## Test your setup + +You can check if your setup works end-to-end (creates, reads and deletes a key from password store). + +```shell +# warning: this will create and delete a key at `<prefix>/test/asd` in your password store +python -m keyring_pass +``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring_pass-0.7.1/keyring_pass/__init__.py new/keyring_pass-0.9.2/keyring_pass/__init__.py --- old/keyring_pass-0.7.1/keyring_pass/__init__.py 2022-09-13 10:38:21.000000000 +0200 +++ new/keyring_pass-0.9.2/keyring_pass/__init__.py 2024-01-08 12:38:23.499510300 +0100 @@ -1,74 +1,188 @@ -import codecs +# -*- coding: utf-8 -*- import configparser import os +import re import shutil import subprocess import sys import keyring from jaraco.classes import properties -from keyring import backend +from keyring import backend, credentials from keyring.util import platform_ as platform +try: + from functools import cache +except ImportError: + from functools import lru_cache -def command(cmd, **kwargs): - kwargs.setdefault('stderr', sys.stderr) + cache = lru_cache(maxsize=None) + + +def command(cmd, **kwargs) -> str: + kwargs.setdefault("encoding", "utf8") + kwargs.setdefault("stderr", sys.stderr) try: - output = subprocess.check_output(cmd, **kwargs) + return subprocess.check_output(cmd, **kwargs) except subprocess.CalledProcessError as exc: - pattern = b'password store is empty' + pattern = "password store is empty" if pattern in exc.output: raise RuntimeError(exc.output) - sys.stderr.write(exc.stdout.decode('utf8')) + sys.stderr.write(exc.stdout) raise - return codecs.decode(output, 'utf8') + + +@cache +def _load_config( + keyring_cfg=os.path.join(platform.config_root(), "keyringrc.cfg"), +): + cfg = {} + if not os.path.exists(keyring_cfg): + return cfg + + config = configparser.RawConfigParser() + config.read(keyring_cfg) + for attr, option in PasswordStoreBackend.INI_OPTIONS.items(): + try: + cfg[attr] = config.get("pass", option) + except (configparser.NoSectionError, configparser.NoOptionError): + pass + return cfg class PasswordStoreBackend(backend.KeyringBackend): - pass_key_prefix = 'python-keyring' + pass_key_prefix = "python-keyring" + pass_binary = "pass" + pass_exact_service = True + + INI_OPTIONS = { + "pass_key_prefix": "key-prefix", + "pass_binary": "binary", + "pass_exact_service": "exact-service", + } def __init__(self): + for k, v in _load_config().items(): + setattr(self, k, v) + if isinstance(self.pass_exact_service, str): + self.pass_exact_service = self.pass_exact_service.lower() == "true" + self.pass_key_prefix = os.path.normpath(self.pass_key_prefix) super().__init__() - self._load_config() - - def _load_config(self): - keyring_cfg = os.path.join(platform.config_root(), 'keyringrc.cfg') - if not os.path.exists(keyring_cfg): - return - - config = configparser.RawConfigParser() - config.read(keyring_cfg) - try: - self.pass_key_prefix = config.get('pass', 'key-prefix') - except (configparser.NoSectionError, configparser.NoOptionError): - pass @properties.classproperty - @classmethod def priority(cls): - if not shutil.which('pass'): - raise RuntimeError('`pass` executable is missing!') + binary = _load_config().get("pass_binary", cls.pass_binary) + if not shutil.which(binary): + raise RuntimeError(f"`{binary}` executable is missing!") - command(['pass', 'ls']) + command([binary, "ls"]) return 1 def get_key(self, service, username): - return os.path.join( - self.pass_key_prefix, - service, - username, + service = os.path.normpath(service) + path = ( + os.path.join(self.pass_key_prefix, service) + if self.pass_key_prefix + else service ) + if username: + path = os.path.join(path, username) + return path def set_password(self, servicename, username, password): password = password.splitlines()[0] - inp = '%s\n' % password + inp = "%s\n" % password inp *= 2 - command(['pass', 'insert', '--force', self.get_key(servicename, username)], input=inp.encode('utf8')) + command( + [ + self.pass_binary, + "insert", + "--force", + self.get_key(servicename, username), + ], + input=inp, + ) + + def get_credential(self, servicename, username): + if username: + return credentials.SimpleCredential( + username, + self.get_password(servicename, username), + ) + try: + servicename = os.path.normpath(servicename) + service_key = self.get_key(servicename, None) + output = command([self.pass_binary, "ls", service_key]) + except subprocess.CalledProcessError as exc: + if exc.returncode == 1: + return None + raise + # Currently pass only has tree like structure output. + # Internally it uses the command tree but does not pass + # output formatter options (e.g., for json). + lines = output.splitlines() + lines = [re.sub(r"\x1B\[([0-9]+;)?[0-9]+m", "", line) for line in lines] + + # Assumption that output paths must contain leaves. + # Services and users are entries in our keyring. Thus remove any + # non-word char from the left. + entries = [re.sub(r"^\W+", "", line) for line in lines] + # Just in case: Remove empty entries and corresponding lines + indents, entries = zip( + *[ + (len(line) - len(entry), entry) + for line, entry in zip(lines, entries) + if entry + ] + ) + # The count of removed characters tells us how far elements are indented. + # Elements with the same count are on the same level. + # EOF tree is at indent 0 again. + indents = list(indents) + [0] + entries = list(entries) + # Now to identify the user entries from service keys we must identify + # which elements do not have further entries further down the structure. + # This means that the next element is not indented any further. + users_ids = [ + i for i, j in enumerate(zip(indents[:-1], indents[1:])) if j[0] >= j[1] + ] + # A user with the least indent is closest to the specified service path. + users_ids = sorted(users_ids, key=lambda i: indents[i]) + + # Parse hierarchy and get complete path + if users_ids: + idx = users_ids[0] + username = entries[idx] + # current level of the last added service key + branch_indent = indents[idx] + # last level that can be added + # so stop there. + top_indent = indents[1] + paths = [] + + while branch_indent > top_indent: + idx = idx - 1 + # higher up in the tree or same level + indent = indents[idx] + if indent < branch_indent: + # less indented means new service key + paths.insert(0, entries[idx]) + branch_indent = indent + + found_service = os.path.join(servicename, *paths) if paths else servicename + + if (not self.pass_exact_service) or servicename == found_service: + return credentials.SimpleCredential( + username, self.get_password(found_service, username) + ) + return None def get_password(self, servicename, username): try: - ret = command(['pass', 'show', self.get_key(servicename, username)]) + ret = command( + [self.pass_binary, "show", self.get_key(servicename, username)] + ) except subprocess.CalledProcessError as exc: if exc.returncode == 1: return None @@ -76,21 +190,49 @@ return ret.splitlines()[0] def delete_password(self, service, username): - command(['pass', 'rm', '--force', self.get_key(service, username)]) + command([self.pass_binary, "rm", "--force", self.get_key(service, username)]) -if __name__ == '__main__': - svc = 'test' - user = 'asd' - pwd = 'zxc' +if __name__ == "__main__": + svc = "test" + user = "asd" + pwd = "zxc" + + keyring.set_keyring(PasswordStoreBackend()) + errors = [] + print(f"testing with {svc=} {user=} {pwd=}") try: keyring.set_password(svc, user, pwd) returned = keyring.get_password(svc, user) - + credential = keyring.get_credential(svc, None) finally: keyring.delete_password(svc, user) if returned != pwd: - print('{} != {}'.format(returned, pwd)) + errors.append(f"get_password(): {returned=} != {pwd=}") + else: + print(f"OK: get_password(): matches") + + if not credential: + errors.append(f"get_credential() not found ({credential=})") + else: + print(f"OK: get_credential(): found") + if credential.username != user: + errors.append( + f"get_credential(): {credential.username=} doesn't match {user=}" + ) + else: + print(f"OK: get_credential(): username matches") + + if credential.password != pwd: + errors.append( + f"get_credential(): {credential.password=} doesn't match {pwd=}" + ) + else: + print(f"OK: get_credential(): password matches") + + for err in errors: + print(f"ERROR: {err}") + if errors: sys.exit(1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring_pass-0.7.1/keyring_pass.egg-info/PKG-INFO new/keyring_pass-0.9.2/keyring_pass.egg-info/PKG-INFO --- old/keyring_pass-0.7.1/keyring_pass.egg-info/PKG-INFO 2022-09-13 12:23:35.000000000 +0200 +++ new/keyring_pass-0.9.2/keyring_pass.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,36 +0,0 @@ -Metadata-Version: 2.1 -Name: keyring-pass -Version: 0.7.1 -Summary: https://www.passwordstore.org/ backend for https://pypi.org/project/keyring/ -Home-page: https://github.com/nazarewk/keyring_pass -Author: Krzysztof Nazarewski -Author-email: 3494992+nazar...@users.noreply.github.com -Classifier: Programming Language :: Python :: 3 -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: OS Independent -Requires-Python: >3.3 -Description-Content-Type: text/markdown -License-File: LICENSE - -# keyring_pass - -This is a [`pass`](https://www.passwordstore.org/) backend for [`keyring`](https://pypi.org/project/keyring/) - -Install with `pip install keyring-pass` and set the following content in your [`keyringrc.cfg`](https://pypi.org/project/keyring/#config-file-path) file: - -```ini -[backend] -default-keyring=keyring_pass.PasswordStoreBackend -``` - -You can modify the default `python-keyring` prefix for `pass`, by: -- adding following to `keyringrc.cfg`: - - ```ini - [pass] - key-prefix=alternative/prefix/path - ``` - -- (for `keyring` version 23.0.0 or higher) setting environment variable `KEYRING_PROPERTY_PASS_KEY_PREFIX` - -You can clear the path (store in root), by setting above to `.` or an empty value. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring_pass-0.7.1/keyring_pass.egg-info/SOURCES.txt new/keyring_pass-0.9.2/keyring_pass.egg-info/SOURCES.txt --- old/keyring_pass-0.7.1/keyring_pass.egg-info/SOURCES.txt 2022-09-13 12:23:35.000000000 +0200 +++ new/keyring_pass-0.9.2/keyring_pass.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,10 +0,0 @@ -LICENSE -README.md -setup.py -keyring_pass/__init__.py -keyring_pass.egg-info/PKG-INFO -keyring_pass.egg-info/SOURCES.txt -keyring_pass.egg-info/dependency_links.txt -keyring_pass.egg-info/entry_points.txt -keyring_pass.egg-info/requires.txt -keyring_pass.egg-info/top_level.txt \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring_pass-0.7.1/keyring_pass.egg-info/dependency_links.txt new/keyring_pass-0.9.2/keyring_pass.egg-info/dependency_links.txt --- old/keyring_pass-0.7.1/keyring_pass.egg-info/dependency_links.txt 2022-09-13 12:23:35.000000000 +0200 +++ new/keyring_pass-0.9.2/keyring_pass.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring_pass-0.7.1/keyring_pass.egg-info/entry_points.txt new/keyring_pass-0.9.2/keyring_pass.egg-info/entry_points.txt --- old/keyring_pass-0.7.1/keyring_pass.egg-info/entry_points.txt 2022-09-13 12:23:35.000000000 +0200 +++ new/keyring_pass-0.9.2/keyring_pass.egg-info/entry_points.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,2 +0,0 @@ -[keyring.backends] -pass = keyring_pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring_pass-0.7.1/keyring_pass.egg-info/requires.txt new/keyring_pass-0.9.2/keyring_pass.egg-info/requires.txt --- old/keyring_pass-0.7.1/keyring_pass.egg-info/requires.txt 2022-09-13 12:23:35.000000000 +0200 +++ new/keyring_pass-0.9.2/keyring_pass.egg-info/requires.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,2 +0,0 @@ -keyring -jaraco.classes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring_pass-0.7.1/keyring_pass.egg-info/top_level.txt new/keyring_pass-0.9.2/keyring_pass.egg-info/top_level.txt --- old/keyring_pass-0.7.1/keyring_pass.egg-info/top_level.txt 2022-09-13 12:23:35.000000000 +0200 +++ new/keyring_pass-0.9.2/keyring_pass.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -keyring_pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring_pass-0.7.1/pyproject.toml new/keyring_pass-0.9.2/pyproject.toml --- old/keyring_pass-0.7.1/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 +++ new/keyring_pass-0.9.2/pyproject.toml 2024-01-08 12:39:54.790916000 +0100 @@ -0,0 +1,28 @@ +[tool.poetry] +name = "keyring-pass" +version = "0.9.2" +description = "https://www.passwordstore.org/ backend for https://pypi.org/project/keyring/" +authors = ["Krzysztof Nazarewski <3494992+nazar...@users.noreply.github.com>"] +homepage = "https://github.com/nazarewk/keyring_pass" +repository = "https://github.com/nazarewk/keyring_pass" +license = "MIT" +readme = "README.md" +keywords = [ + "keyring", + "pass", +] +classifiers = [] +packages = [{ include = "keyring_pass" }] + +[tool.poetry.dependencies] +python = ">=3.7" # required by `jaraco.classes` +keyring = ">=23.9.3" +jaraco-classes = ">=3.2.3" + +[tool.poetry.plugins."keyring.backends"] +pass = "keyring_pass" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring_pass-0.7.1/setup.cfg new/keyring_pass-0.9.2/setup.cfg --- old/keyring_pass-0.7.1/setup.cfg 2022-09-13 12:23:35.635640400 +0200 +++ new/keyring_pass-0.9.2/setup.cfg 1970-01-01 01:00:00.000000000 +0100 @@ -1,4 +0,0 @@ -[egg_info] -tag_build = -tag_date = 0 - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/keyring_pass-0.7.1/setup.py new/keyring_pass-0.9.2/setup.py --- old/keyring_pass-0.7.1/setup.py 2022-09-13 10:39:55.000000000 +0200 +++ new/keyring_pass-0.9.2/setup.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,35 +0,0 @@ -import setuptools - -with open('README.md', 'r') as f: - long_description = f.read() - -setuptools.setup( - name='keyring_pass', - version='0.7.1', - author='Krzysztof Nazarewski', - author_email='3494992+nazar...@users.noreply.github.com', - description='https://www.passwordstore.org/ backend for https://pypi.org/project/keyring/', - long_description=long_description, - long_description_content_type="text/markdown", - url='https://github.com/nazarewk/keyring_pass', - - python_requires='>3.3', - packages=['keyring_pass'], - - entry_points={ - 'keyring.backends': [ - 'pass = keyring_pass', - ] - }, - - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - - install_requires=[ - 'keyring', - "jaraco.classes", - ] -)