Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-osc-tiny for openSUSE:Factory
checked in at 2023-03-06 18:56:43
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-osc-tiny (Old)
and /work/SRC/openSUSE:Factory/.python-osc-tiny.new.31432 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-osc-tiny"
Mon Mar 6 18:56:43 2023 rev:28 rq:1069656 version:0.7.12
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-osc-tiny/python-osc-tiny.changes
2023-01-09 17:24:18.947332699 +0100
+++
/work/SRC/openSUSE:Factory/.python-osc-tiny.new.31432/python-osc-tiny.changes
2023-03-06 18:56:46.137037468 +0100
@@ -1,0 +2,8 @@
+Mon Mar 6 06:03:47 UTC 2023 - Andreas Hasenkopf <[email protected]>
+
+- Release .0.7.12
+ * Enhanced usability and reliability for `HttpSignatureAuth`
+ * Prevent sharing of sessions across forked processes
+ * Fixed typo in quickstart doc
+
+-------------------------------------------------------------------
Old:
----
osc-tiny-0.7.11.tar.gz
New:
----
osc-tiny-0.7.12.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-osc-tiny.spec ++++++
--- /var/tmp/diff_new_pack.BQa3lI/_old 2023-03-06 18:56:47.405043937 +0100
+++ /var/tmp/diff_new_pack.BQa3lI/_new 2023-03-06 18:56:47.409043958 +0100
@@ -18,7 +18,7 @@
%define skip_python2 1
Name: python-osc-tiny
-Version: 0.7.11
+Version: 0.7.12
Release: 0
Summary: Client API for openSUSE BuildService
License: MIT
++++++ osc-tiny-0.7.11.tar.gz -> osc-tiny-0.7.12.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.7.11/PKG-INFO new/osc-tiny-0.7.12/PKG-INFO
--- old/osc-tiny-0.7.11/PKG-INFO 2023-01-09 09:43:30.914676200 +0100
+++ new/osc-tiny-0.7.12/PKG-INFO 2023-03-06 07:03:02.507382900 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: osc-tiny
-Version: 0.7.11
+Version: 0.7.12
Summary: Client API for openSUSE BuildService
Home-page: http://github.com/crazyscientist/osc-tiny
Download-URL: http://github.com/crazyscientist/osc-tiny/tarball/master
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.7.11/osc_tiny.egg-info/PKG-INFO
new/osc-tiny-0.7.12/osc_tiny.egg-info/PKG-INFO
--- old/osc-tiny-0.7.11/osc_tiny.egg-info/PKG-INFO 2023-01-09
09:43:30.000000000 +0100
+++ new/osc-tiny-0.7.12/osc_tiny.egg-info/PKG-INFO 2023-03-06
07:03:02.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: osc-tiny
-Version: 0.7.11
+Version: 0.7.12
Summary: Client API for openSUSE BuildService
Home-page: http://github.com/crazyscientist/osc-tiny
Download-URL: http://github.com/crazyscientist/osc-tiny/tarball/master
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.7.11/osctiny/__init__.py
new/osc-tiny-0.7.12/osctiny/__init__.py
--- old/osc-tiny-0.7.11/osctiny/__init__.py 2023-01-09 09:43:21.000000000
+0100
+++ new/osc-tiny-0.7.12/osctiny/__init__.py 2023-03-06 07:02:53.000000000
+0100
@@ -6,4 +6,4 @@
__all__ = ['Osc', 'bs_requests', 'buildresults', 'comments', 'packages',
'projects', 'search', 'users']
-__version__ = "0.7.11"
+__version__ = "0.7.12"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.7.11/osctiny/osc.py
new/osc-tiny-0.7.12/osctiny/osc.py
--- old/osc-tiny-0.7.11/osctiny/osc.py 2023-01-09 09:43:21.000000000 +0100
+++ new/osc-tiny-0.7.12/osctiny/osc.py 2023-03-06 07:02:53.000000000 +0100
@@ -10,6 +10,7 @@
from io import BufferedReader, BytesIO, StringIO
import gc
import logging
+import os
from pathlib import Path
import re
from ssl import get_default_verify_paths
@@ -163,14 +164,16 @@
self.search = Search(osc_obj=self)
self.users = Person(osc_obj=self)
- hash_value =
b64encode(f'{self.username}@{self.url}@{self.ssh_key}'.encode())
- self._session_id = f"session_{hash_value}"
-
def __del__(self):
# Just in case ;-)
gc.collect()
@property
+ def _session_id(self) -> str:
+ session_hash =
b64encode(f'{self.username}@{self.url}'.encode()).decode()
+ return f"session_{session_hash}_{os.getpid()}_{threading.get_ident()}"
+
+ @property
def _session(self) -> Session:
"""
Session object
@@ -197,18 +200,13 @@
Possibly wrapped in CacheControl, if installed.
"""
+ if not self.cache or CacheControl is None:
+ return self._session
+
key = f"cached_{self._session_id}"
session = getattr(THREAD_LOCAL, key, None)
if not session:
- if self.cache:
- # pylint: disable=broad-except
- try:
- session = CacheControl(self._session)
- except Exception as error:
- session = self._session
- warnings.warn("Cannot use the cache: {}".format(error),
RuntimeWarning)
- else:
- session = self._session
+ session = CacheControl(self._session)
setattr(THREAD_LOCAL, key, session)
return session
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.7.11/osctiny/tests/test_utils.py
new/osc-tiny-0.7.12/osctiny/tests/test_utils.py
--- old/osc-tiny-0.7.11/osctiny/tests/test_utils.py 2023-01-09
09:43:21.000000000 +0100
+++ new/osc-tiny-0.7.12/osctiny/tests/test_utils.py 2023-03-06
07:02:53.000000000 +0100
@@ -16,7 +16,8 @@
from requests import Response
import responses
-from ..osc import Osc
+from ..osc import Osc, THREAD_LOCAL
+from ..utils.auth import HttpSignatureAuth
from ..utils.changelog import ChangeLog, Entry
from ..utils.conf import get_config_path, get_credentials
from ..utils.mapping import Mappable
@@ -396,14 +397,31 @@
@mock.patch("osctiny.utils.auth.time", return_value=123456)
class TestAuth(TestCase):
+ def _clear_thread_local(self):
+ try:
+ delattr(THREAD_LOCAL, self.osc._session_id)
+
+ except AttributeError:
+ pass
+
@mock.patch("osctiny.utils.auth.is_ssh_key_readable", return_value=(True,
None))
def setUp(self, *_):
super().setUp()
mocked_path = mock.MagicMock(spec=Path)
mocked_path.configure_mock(**{"is_file.return_value": True})
- self.osc = Osc("https://api.example.com", "nemo", "password",
ssh_key_file=mocked_path)
+
+ self.osc = Osc(url="https://api.example.com",
+ username="nemo",
+ password="password",
+ ssh_key_file=mocked_path)
+
+ self._clear_thread_local()
self.osc.session.auth.ssh_sign = lambda *args, **kwargs: "Hello World"
+ def tearDown(self):
+ super().tearDown()
+ self._clear_thread_local()
+
def setup_response(self, headers: dict):
responses.reset()
responses.add(
@@ -426,6 +444,8 @@
@responses.activate
def test_handle_401(self, *_):
+ self.assertIsInstance(self.osc.session.auth, HttpSignatureAuth)
+
with self.subTest("No WWW-Authenticate header"):
self.setup_response({"Foo": "Bar"})
response =
self.osc.session.get("https://api.example.com/hello-world")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.7.11/osctiny/utils/auth.py
new/osc-tiny-0.7.12/osctiny/utils/auth.py
--- old/osc-tiny-0.7.11/osctiny/utils/auth.py 2023-01-09 09:43:21.000000000
+0100
+++ new/osc-tiny-0.7.12/osctiny/utils/auth.py 2023-03-06 07:02:53.000000000
+0100
@@ -7,9 +7,8 @@
import typing
from base64 import b64decode, b64encode
import logging
-import os
from pathlib import Path
-from subprocess import Popen, PIPE, DEVNULL
+from subprocess import Popen, PIPE, TimeoutExpired
import re
import sys
from time import time
@@ -22,12 +21,6 @@
from .errors import OscError
-SSH_ENV = os.environ.copy()
-for env_var in ("SSH_AUTH_SOCK", "SSH_AGENT_PID"):
- if env_var in SSH_ENV:
- del SSH_ENV[env_var]
-
-
def get_auth_header_from_orignal_response(r: Response) -> typing.Optional[str]:
"""
Extract the "www-authenticate" header from the private original response
attribute of a response
@@ -61,10 +54,42 @@
return None
+def ssh_sign(message: str, namespace: str, ssh_key_file: Path,
+ password: typing.Optional[str] = None) -> str:
+ """
+ Create an SSH signature for message
+
+ :param message: The message/data to sign
+ :param namespace: The purpose of the signature (see SSH docs for details)
+ :param ssh_key_file: Path to SSH key
+ :param password: Passphrase
+ :return: Signature
+
+ .. versionadded:: 0.7.12
+ """
+ cmd = ['ssh-keygen', '-Y', 'sign', '-f', ssh_key_file.as_posix(), '-q',
+ '-n', namespace]
+ timeout = 10
+ if password:
+ cmd += ['-P', password]
+
+ encoding = sys.getdefaultencoding()
+
+ with Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, encoding=encoding)
as proc:
+ try:
+ signature, error = proc.communicate(input=message, timeout=timeout)
+ if proc.returncode:
+ raise OscError(f"ssh-keygen returned {proc.returncode}:
{error}")
+ return signature
+ except TimeoutExpired as error:
+ proc.kill()
+ raise OscError(f"ssh-keygen did not return within {timeout}
second(s)") from error
+
+
def is_ssh_key_readable(ssh_key_file: Path, password: typing.Optional[str]) \
-> typing.Tuple[bool, typing.Optional[str]]:
"""
- Check whether SSH key can be read/unlocked
+ Check whether SSH signing is viable
:param ssh_key_file: Path to SSH key
:param password: Passphrase
@@ -79,18 +104,21 @@
.. versionchanged:: 0.7.10
* Return the error message, if key cannot be unlocked
- """
- cmd = ['ssh-keygen', '-y', '-f', ssh_key_file.as_posix()]
- if password:
- cmd += ['-P', password]
- with Popen(cmd, stdin=DEVNULL, stderr=PIPE, stdout=DEVNULL,
- env=SSH_ENV if password else os.environ) as proc:
- _, error = proc.communicate()
- if proc.returncode == 0:
- return True, None
+ .. versionchanged:: 0.7.12
+
+ * Instead of checking whether the key is readable, this function
checks whether it can
+ actually be used for signing
+ """
+ try:
+ ssh_sign(message="Test",
+ namespace="None",
+ ssh_key_file=ssh_key_file,
+ password=password)
+ except OscError as error:
+ return False, str(error)
- return False, error.decode("utf-8") if isinstance(error, bytes) else error
+ return True, None
class HttpSignatureAuth(HTTPDigestAuth):
@@ -151,23 +179,15 @@
"""
data = "\n".join(f"({header}): {self._thread_local.chal[header]}"
for header in self._thread_local.chal["headers"])
- cmd = ['ssh-keygen', '-Y', 'sign', '-f', self.ssh_key_file.as_posix(),
'-q',
- '-n', self._thread_local.chal.get('realm', '')]
- if self.password:
- cmd += ['-P', self.password]
-
- encoding = sys.getdefaultencoding()
- with Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE,
- env=SSH_ENV if self.password else os.environ) as proc:
- signature, error = proc.communicate(data.encode(encoding))
- if proc.returncode:
- raise OscError(f"ssh-keygen returned {proc.returncode}:
{error}")
-
- match = re.match(br"\A-----BEGIN SSH SIGNATURE-----\n(.*)\n-----END
SSH SIGNATURE-----",
+ signature = ssh_sign(message=data,
+ namespace=self._thread_local.chal.get('realm',
''),
+ ssh_key_file=self.ssh_key_file,
+ password=self.password)
+ match = re.match(r"\A-----BEGIN SSH SIGNATURE-----\n(.*)\n-----END SSH
SIGNATURE-----",
signature, re.S)
if not match:
raise OscError("Could not generate challenge response")
- return b64encode(b64decode(match.group(1))).decode(encoding)
+ return
b64encode(b64decode(match.group(1))).decode(sys.getdefaultencoding())
def build_digest_header(self, method: str, url: str) -> str:
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.7.11/setup.py new/osc-tiny-0.7.12/setup.py
--- old/osc-tiny-0.7.11/setup.py 2023-01-09 09:43:21.000000000 +0100
+++ new/osc-tiny-0.7.12/setup.py 2023-03-06 07:02:53.000000000 +0100
@@ -26,7 +26,7 @@
setup(
name='osc-tiny',
- version='0.7.11',
+ version='0.7.12',
description='Client API for openSUSE BuildService',
long_description=long_description,
long_description_content_type="text/markdown",