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"