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",

Reply via email to