Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-google-auth-oauthlib for 
openSUSE:Factory checked in at 2026-10-09 21:46:16
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-google-auth-oauthlib (Old)
 and      /work/SRC/openSUSE:Factory/.python-google-auth-oauthlib.new.3698736 
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-google-auth-oauthlib"

Fri Oct  9 21:46:16 2026 rev:26 rq:1383810 version:1.5.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-google-auth-oauthlib/python-google-auth-oauthlib.changes
  2026-09-04 12:38:51.489313937 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-google-auth-oauthlib.new.3698736/python-google-auth-oauthlib.changes
     2026-10-09 21:47:41.281132485 +0200
@@ -1,0 +2,7 @@
+Fri Oct  9 09:01:54 UTC 2026 - John Paul Adrian Glaubitz 
<[email protected]>
+
+- Update to 1.5.0
+  * declare Python3.15 support
+  * **google-auth-oauthlib:** prevent ipv6 address reuse (#18463)
+
+-------------------------------------------------------------------

Old:
----
  google_auth_oauthlib-1.4.1.tar.gz

New:
----
  google_auth_oauthlib-1.5.0.tar.gz

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

Other differences:
------------------
++++++ python-google-auth-oauthlib.spec ++++++
--- /var/tmp/diff_new_pack.csnSJh/_old  2026-10-09 21:47:42.099166792 +0200
+++ /var/tmp/diff_new_pack.csnSJh/_new  2026-10-09 21:47:42.102166918 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-google-auth-oauthlib
-Version:        1.4.1
+Version:        1.5.0
 Release:        0
 Summary:        Google authentication library
 License:        Apache-2.0

++++++ google_auth_oauthlib-1.4.1.tar.gz -> google_auth_oauthlib-1.5.0.tar.gz 
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/google_auth_oauthlib-1.4.1/PKG-INFO 
new/google_auth_oauthlib-1.5.0/PKG-INFO
--- old/google_auth_oauthlib-1.4.1/PKG-INFO     2026-08-24 23:26:12.335985400 
+0200
+++ new/google_auth_oauthlib-1.5.0/PKG-INFO     2026-09-29 20:58:38.328330300 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: google-auth-oauthlib
-Version: 1.4.1
+Version: 1.5.0
 Summary: Google Authentication Library
 Home-page: 
https://github.com/googleapis/google-cloud-python/tree/main/packages/google-auth-oauthlib
 Author: Google Cloud Platform
@@ -13,6 +13,7 @@
 Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3.13
 Classifier: Programming Language :: Python :: 3.14
+Classifier: Programming Language :: Python :: 3.15
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: Apache Software License
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google_auth_oauthlib-1.4.1/google_auth_oauthlib/flow.py 
new/google_auth_oauthlib-1.5.0/google_auth_oauthlib/flow.py
--- old/google_auth_oauthlib-1.4.1/google_auth_oauthlib/flow.py 2026-08-24 
23:22:35.128714800 +0200
+++ new/google_auth_oauthlib-1.5.0/google_auth_oauthlib/flow.py 2026-09-29 
20:56:54.652821000 +0200
@@ -48,10 +48,12 @@
     https://developers.google.com/identity/protocols/oauth2
 
 """
-from base64 import urlsafe_b64encode
+
+import errno
 import hashlib
 import json
 import logging
+from base64 import urlsafe_b64encode
 
 try:
     from secrets import SystemRandom
@@ -59,11 +61,11 @@
     from random import SystemRandom
 
 import socket
-from string import ascii_letters, digits
 import sys
 import webbrowser
 import wsgiref.simple_server
 import wsgiref.util
+from string import ascii_letters, digits
 
 import google.auth.transport.requests
 import google.oauth2.credentials
@@ -382,7 +384,7 @@
         timeout_seconds=None,
         token_audience=None,
         browser=None,
-        **kwargs
+        **kwargs,
     ):
         """Run the flow using the server strategy.
 
@@ -491,14 +493,66 @@
     Setting `WSGIServer.allow_reuse_address` is not enough, since it sets 
`SO_REUSEADDR`
     and not `SO_EXCLUSIVEADDRUSE`. `SO_REUSEADDR` alone allows other processes 
to bind
     to the same address and port on Windows.
+
+    When bound to `localhost`, also reserves the IPv6 loopback (`::1`) so
+    another process listening on `::1` cannot intercept the OAuth callback.
     """
 
     allow_reuse_address = False
+    _PROBE_TIMEOUT_SECONDS = 0.1
 
-    def server_bind(self):
+    def __init__(self, *args, **kwargs):
+        self._ipv6_socket = None
+        super().__init__(*args, **kwargs)
+
+    @staticmethod
+    def is_listener_present(family: int, addr: str, port: int) -> bool:
+        """Check if another process is already listening on (addr, port) by
+        attempting a test connection.
+
+        This is needed because on Windows, `bind()` on `::1` succeeds even when
+        another process from the same user is already listening on all
+        interfaces (`[::]`).
+        """
+        try:
+            with socket.socket(family, socket.SOCK_STREAM) as probe:
+                probe.settimeout(_ExclusiveWSGIServer._PROBE_TIMEOUT_SECONDS)
+                return probe.connect_ex((addr, port)) == 0
+        except OSError:
+            return False
+
+    @staticmethod
+    def _set_exclusive_addr_use(sock: socket.socket) -> None:
         if sys.platform == "win32" and hasattr(socket, "SO_EXCLUSIVEADDRUSE"):
-            self.socket.setsockopt(socket.SOL_SOCKET, 
socket.SO_EXCLUSIVEADDRUSE, 1)
+            sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
+
+    def _close_ipv6_socket(self):
+        if self._ipv6_socket is not None:
+            self._ipv6_socket.close()
+            self._ipv6_socket = None
+
+    def server_bind(self):
+        host = self.server_address[0]
+        self._set_exclusive_addr_use(self.socket)
         super().server_bind()
+        port = self.server_address[1]
+        # Reserve IPv6 loopback (::1) so another process cannot intercept 
localhost callbacks.
+        if host == "localhost" and port and hasattr(socket, "AF_INET6"):
+            # base class (TCPServer) calls server_close on error
+            if self.is_listener_present(socket.AF_INET6, "::1", port):
+                raise OSError(errno.EADDRINUSE, "Address already in use")
+            # Hold `::1` without calling `listen()` so no other process can 
claim
+            # the port while the browser falls back from `::1` to `127.0.0.1`.
+            try:
+                self._ipv6_socket = socket.socket(socket.AF_INET6, 
socket.SOCK_STREAM)
+                self._set_exclusive_addr_use(self._ipv6_socket)
+                self._ipv6_socket.bind(("::1", port))
+            except OSError:
+                self._close_ipv6_socket()
+
+    def server_close(self):
+        self._close_ipv6_socket()
+        super().server_close()
 
 
 class _WSGIRequestHandler(wsgiref.simple_server.WSGIRequestHandler):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google_auth_oauthlib-1.4.1/google_auth_oauthlib/helpers.py 
new/google_auth_oauthlib-1.5.0/google_auth_oauthlib/helpers.py
--- old/google_auth_oauthlib-1.4.1/google_auth_oauthlib/helpers.py      
2026-08-24 23:22:36.333726400 +0200
+++ new/google_auth_oauthlib-1.5.0/google_auth_oauthlib/helpers.py      
2026-09-29 20:56:53.517582200 +0200
@@ -24,9 +24,9 @@
 import datetime
 import json
 
-from google.auth import external_account_authorized_user
 import google.oauth2.credentials
 import requests_oauthlib
+from google.auth import external_account_authorized_user
 
 _REQUIRED_CONFIG_KEYS = frozenset(("auth_uri", "token_uri", "client_id"))
 
@@ -123,7 +123,7 @@
 
     if not session.token:
         raise ValueError(
-            "There is no access token for this session, did you call " 
"fetch_token?"
+            "There is no access token for this session, did you call 
fetch_token?"
         )
 
     if "3pi" in client_config:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google_auth_oauthlib-1.4.1/google_auth_oauthlib/interactive.py 
new/google_auth_oauthlib-1.5.0/google_auth_oauthlib/interactive.py
--- old/google_auth_oauthlib-1.4.1/google_auth_oauthlib/interactive.py  
2026-08-24 23:22:33.916703000 +0200
+++ new/google_auth_oauthlib-1.5.0/google_auth_oauthlib/interactive.py  
2026-09-29 20:56:58.690577500 +0200
@@ -50,6 +50,13 @@
             is_open = False
         else:
             is_open = True
+    # Also verify that no process is already listening on IPv6 loopback (::1).
+    if is_open and hasattr(socket, "AF_INET6"):
+        is_open = (
+            not 
google_auth_oauthlib.flow._ExclusiveWSGIServer.is_listener_present(
+                socket.AF_INET6, "::1", port
+            )
+        )
     return is_open
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google_auth_oauthlib-1.4.1/google_auth_oauthlib.egg-info/PKG-INFO 
new/google_auth_oauthlib-1.5.0/google_auth_oauthlib.egg-info/PKG-INFO
--- old/google_auth_oauthlib-1.4.1/google_auth_oauthlib.egg-info/PKG-INFO       
2026-08-24 23:26:12.317807400 +0200
+++ new/google_auth_oauthlib-1.5.0/google_auth_oauthlib.egg-info/PKG-INFO       
2026-09-29 20:58:38.309458300 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: google-auth-oauthlib
-Version: 1.4.1
+Version: 1.5.0
 Summary: Google Authentication Library
 Home-page: 
https://github.com/googleapis/google-cloud-python/tree/main/packages/google-auth-oauthlib
 Author: Google Cloud Platform
@@ -13,6 +13,7 @@
 Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3.13
 Classifier: Programming Language :: Python :: 3.14
+Classifier: Programming Language :: Python :: 3.15
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: Apache Software License
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/google_auth_oauthlib-1.4.1/setup.py 
new/google_auth_oauthlib-1.5.0/setup.py
--- old/google_auth_oauthlib-1.4.1/setup.py     2026-08-24 23:22:32.498689400 
+0200
+++ new/google_auth_oauthlib-1.5.0/setup.py     2026-09-29 20:56:56.537579500 
+0200
@@ -30,7 +30,7 @@
     long_description = fh.read()
 
 
-version = "1.4.1"
+version = "1.5.0"
 
 setup(
     name="google-auth-oauthlib",
@@ -51,7 +51,7 @@
     extras_require={"tool": TOOL_DEPENDENCIES},
     entry_points={
         "console_scripts": [
-            "google-oauthlib-tool" "=google_auth_oauthlib.tool.__main__:main 
[tool]"
+            "google-oauthlib-tool=google_auth_oauthlib.tool.__main__:main 
[tool]"
         ]
     },
     python_requires=">=3.10",
@@ -64,6 +64,7 @@
         "Programming Language :: Python :: 3.12",
         "Programming Language :: Python :: 3.13",
         "Programming Language :: Python :: 3.14",
+        "Programming Language :: Python :: 3.15",
         "Development Status :: 5 - Production/Stable",
         "Intended Audience :: Developers",
         "License :: OSI Approved :: Apache Software License",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/google_auth_oauthlib-1.4.1/tests/unit/test_flow.py 
new/google_auth_oauthlib-1.5.0/tests/unit/test_flow.py
--- old/google_auth_oauthlib-1.4.1/tests/unit/test_flow.py      2026-08-24 
23:22:33.550699500 +0200
+++ new/google_auth_oauthlib-1.5.0/tests/unit/test_flow.py      2026-09-29 
20:56:54.083581700 +0200
@@ -14,17 +14,17 @@
 
 import concurrent.futures
 import datetime
-from functools import partial
 import json
 import logging
 import os
 import re
 import socket
 import time
-from unittest import mock
 import urllib
 import webbrowser
 import wsgiref.simple_server
+from functools import partial
+from unittest import mock
 
 import pytest
 import requests
@@ -566,14 +566,19 @@
 
 class TestExclusiveWSGIServer(object):
     def test_exclusive_wsgi_server_bind_windows(self):
-        with mock.patch("sys.platform", "win32"), mock.patch(
-            "google_auth_oauthlib.flow.socket"
-        ) as mock_socket:
+        with (
+            mock.patch("sys.platform", "win32"),
+            mock.patch("google_auth_oauthlib.flow.socket") as mock_socket,
+        ):
             mock_socket.SOL_SOCKET = socket.SOL_SOCKET
             mock_socket.SO_EXCLUSIVEADDRUSE = getattr(socket, 
"SO_EXCLUSIVEADDRUSE", 1)
+            mock_socket.AF_INET = socket.AF_INET
+            mock_socket.AF_INET6 = socket.AF_INET6
+            mock_socket.SOCK_STREAM = socket.SOCK_STREAM
+            
mock_socket.socket.return_value.__enter__.return_value.connect_ex.return_value 
= 1
 
             server = flow._ExclusiveWSGIServer(
-                ("localhost", 0), flow._WSGIRequestHandler, 
bind_and_activate=False
+                ("localhost", 8085), flow._WSGIRequestHandler, 
bind_and_activate=False
             )
             server.socket = mock.Mock()
 
@@ -582,6 +587,12 @@
                 server.socket.setsockopt.assert_called_once_with(
                     mock_socket.SOL_SOCKET, mock_socket.SO_EXCLUSIVEADDRUSE, 1
                 )
+                server._ipv6_socket.setsockopt.assert_called_once_with(
+                    mock_socket.SOL_SOCKET, mock_socket.SO_EXCLUSIVEADDRUSE, 1
+                )
+                server._ipv6_socket.bind.assert_called_once_with(("::1", 8085))
+                server.server_close()
+                assert server._ipv6_socket is None
 
     def test_exclusive_wsgi_server_bind_non_windows(self):
         with mock.patch("sys.platform", "linux"):
@@ -593,3 +604,84 @@
             with mock.patch.object(wsgiref.simple_server.WSGIServer, 
"server_bind"):
                 server.server_bind()
                 server.socket.setsockopt.assert_not_called()
+                server.server_close()
+
+    def test_exclusive_wsgi_server_detects_existing_listeners(self):
+        with (
+            mock.patch("sys.platform", "win32"),
+            mock.patch("google_auth_oauthlib.flow.socket") as mock_socket,
+        ):
+            mock_socket.SOL_SOCKET = socket.SOL_SOCKET
+            mock_socket.SO_EXCLUSIVEADDRUSE = 1
+            mock_socket.AF_INET = socket.AF_INET
+            mock_socket.AF_INET6 = socket.AF_INET6
+            mock_socket.SOCK_STREAM = socket.SOCK_STREAM
+
+            server = flow._ExclusiveWSGIServer(
+                ("localhost", 8085), flow._WSGIRequestHandler, 
bind_and_activate=False
+            )
+            server.socket = mock.Mock()
+
+            # 1. Pre-existing IPv6 listener on ::1 / [::]
+            
mock_socket.socket.return_value.__enter__.return_value.connect_ex.return_value 
= 0
+            with mock.patch.object(wsgiref.simple_server.WSGIServer, 
"server_bind"):
+                with pytest.raises(OSError):
+                    server.server_bind()
+
+            # 2. is_listener_present returns False when socket() raises OSError
+            mock_socket.socket.side_effect = OSError("socket error")
+            assert not flow._ExclusiveWSGIServer.is_listener_present(
+                socket.AF_INET6, "::1", 8085
+            )
+
+    def test_exclusive_wsgi_server_ipv4_literal_ignores_ipv6(self):
+        # An address literal is served verbatim in redirect_uri, so the browser
+        # never resolves `localhost` and `::1` is irrelevant.
+        server = flow._ExclusiveWSGIServer(
+            ("127.0.0.1", 8085), flow._WSGIRequestHandler, 
bind_and_activate=False
+        )
+        server.socket = mock.Mock()
+
+        with (
+            mock.patch.object(wsgiref.simple_server.WSGIServer, "server_bind"),
+            mock.patch.object(
+                flow._ExclusiveWSGIServer, "is_listener_present", 
return_value=True
+            ) as is_listener_present,
+        ):
+            server.server_bind()
+
+        is_listener_present.assert_not_called()
+        assert server._ipv6_socket is None
+
+    def test_exclusive_wsgi_server_ipv6_bind_errors(self):
+        import errno
+
+        server = flow._ExclusiveWSGIServer(
+            ("localhost", 8085), flow._WSGIRequestHandler, 
bind_and_activate=False
+        )
+        server.socket = mock.Mock()
+
+        with (
+            mock.patch.object(wsgiref.simple_server.WSGIServer, "server_bind"),
+            mock.patch.object(
+                flow._ExclusiveWSGIServer, "is_listener_present", 
return_value=False
+            ),
+            mock.patch("google_auth_oauthlib.flow.socket") as mock_socket,
+        ):
+            mock_socket.AF_INET6 = socket.AF_INET6
+            mock_socket.SOCK_STREAM = socket.SOCK_STREAM
+
+            # 1. Ignored when socket(AF_INET6) itself raises EAFNOSUPPORT
+            mock_socket.socket.side_effect = OSError(
+                errno.EAFNOSUPPORT, "IPv6 disabled in kernel"
+            )
+            server.server_bind()
+            assert server._ipv6_socket is None
+
+            # 2. Non-fatal when bind(("::1", ...)) fails but no listener is 
present
+            mock_socket.socket.side_effect = None
+            mock_socket.socket.return_value.bind.side_effect = OSError(
+                errno.EADDRINUSE, "Held by non-listening socket"
+            )
+            server.server_bind()
+            assert server._ipv6_socket is None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google_auth_oauthlib-1.4.1/tests/unit/test_helpers.py 
new/google_auth_oauthlib-1.5.0/tests/unit/test_helpers.py
--- old/google_auth_oauthlib-1.4.1/tests/unit/test_helpers.py   2026-08-24 
23:22:35.746720800 +0200
+++ new/google_auth_oauthlib-1.5.0/tests/unit/test_helpers.py   2026-09-29 
20:56:52.452769000 +0200
@@ -17,9 +17,9 @@
 import os
 from unittest import mock
 
-from google.auth import external_account_authorized_user
 import google.oauth2.credentials
 import pytest
+from google.auth import external_account_authorized_user
 
 from google_auth_oauthlib import helpers
 
@@ -133,9 +133,9 @@
 
     client_secrets_info = CLIENT_SECRETS_INFO["web"].copy()
     client_secrets_info["3pi"] = True
-    client_secrets_info[
-        "token_info_url"
-    ] = "https://accounts.google.com/o/oauth2/introspect";
+    client_secrets_info["token_info_url"] = (
+        "https://accounts.google.com/o/oauth2/introspect";
+    )
     credentials = helpers.credentials_from_session(session, 
client_secrets_info)
 
     assert isinstance(credentials, 
external_account_authorized_user.Credentials)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/google_auth_oauthlib-1.4.1/tests/unit/test_interactive.py 
new/google_auth_oauthlib-1.5.0/tests/unit/test_interactive.py
--- old/google_auth_oauthlib-1.4.1/tests/unit/test_interactive.py       
2026-08-24 23:22:33.901703000 +0200
+++ new/google_auth_oauthlib-1.5.0/tests/unit/test_interactive.py       
2026-09-29 20:56:57.134579000 +0200
@@ -55,6 +55,33 @@
     socket_instance.listen.assert_has_calls(mock.call(1) for _ in range(100))
 
 
+def test_is_port_open_ipv6_checks(monkeypatch):
+    import errno
+
+    from google_auth_oauthlib import interactive as module_under_test
+
+    sock4 = mock.create_autospec(socket.socket, instance=True)
+    sock6 = mock.MagicMock()
+
+    def make_sock(family, type_):
+        if family == socket.AF_INET6:
+            return sock6
+        return sock4
+
+    monkeypatch.setattr(socket, "socket", make_sock)
+
+    sock6.__enter__.return_value.connect_ex.return_value = 0
+    assert not module_under_test.is_port_open(8085)
+
+    def mock_socket_fn(family, type_):
+        if family == socket.AF_INET6:
+            raise OSError(errno.EAFNOSUPPORT, "IPv6 disabled")
+        return sock4
+
+    monkeypatch.setattr(socket, "socket", mock_socket_fn)
+    assert module_under_test.is_port_open(8085)
+
+
 def test_get_user_credentials():
     from google_auth_oauthlib import flow
     from google_auth_oauthlib import interactive as module_under_test
@@ -86,9 +113,12 @@
     monkeypatch.setattr(module_under_test, "find_open_port", 
mock_find_open_port)
     mock_flow = mock.create_autospec(flow.InstalledAppFlow, instance=True)
 
-    with mock.patch(
-        "google_auth_oauthlib.flow.InstalledAppFlow", autospec=True
-    ) as mock_flow, pytest.raises(ConnectionError):
+    with (
+        mock.patch(
+            "google_auth_oauthlib.flow.InstalledAppFlow", autospec=True
+        ) as mock_flow,
+        pytest.raises(ConnectionError),
+    ):
         mock_flow.from_client_config.return_value = mock_flow
         module_under_test.get_user_credentials(
             ["scopes"], "some-client-id", "shh-secret"

Reply via email to