From 7ad17ffaafc3d20123642028a30641e4b195a440 Mon Sep 17 00:00:00 2001 From: Emmanuel Mathot Date: Tue, 11 Aug 2026 18:27:44 +0200 Subject: [PATCH] Fix OIDC scope handling to preserve requested scopes not listed in provider's discovery --- CHANGELOG.md | 1 + openeo/rest/auth/oidc.py | 6 ++++-- tests/rest/auth/test_oidc.py | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f92231a73..59d593786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Clarify UDF documentation on required function type annotations. ([#757](https://github.com/Open-EO/openeo-python-client/issues/757)) +- `OidcProviderInfo` no longer drops requested OIDC scopes that are not listed in the provider's `scopes_supported` discovery field which made it impossible to authenticate against such providers. ([#930](https://github.com/Open-EO/openeo-python-client/issues/930)) ## [0.51.0] - 2026-07-16 diff --git a/openeo/rest/auth/oidc.py b/openeo/rest/auth/oidc.py index 5120d5f37..8ff53451c 100644 --- a/openeo/rest/auth/oidc.py +++ b/openeo/rest/auth/oidc.py @@ -279,9 +279,11 @@ def __init__( except Exception as e: raise OidcException(f"Failed to obtain OIDC discovery document from {self.discovery_url!r}: {e!r}") from e self.issuer = issuer or self.config["issuer"] - # Minimal set of scopes to request self._supported_scopes = self.config.get("scopes_supported", ["openid"]) - self._scopes = {"openid"}.union(scopes or []).intersection(self._supported_scopes) + # Don't filter requested scopes against `scopes_supported`: it's only a RECOMMENDED + # discovery field (RFC 8414 section 2), and some providers (e.g. Microsoft Entra ID) + # report a fixed, incomplete list there regardless of which scopes they actually accept. + self._scopes = {"openid"}.union(scopes or []) log.debug(f"Scopes: provider supported {self._supported_scopes} & backend desired {scopes} -> {self._scopes}") self.default_clients = default_clients self.authorization_parameters = authorization_parameters or {} diff --git a/tests/rest/auth/test_oidc.py b/tests/rest/auth/test_oidc.py index 3d0cc73dd..e8e135179 100644 --- a/tests/rest/auth/test_oidc.py +++ b/tests/rest/auth/test_oidc.py @@ -148,6 +148,23 @@ def test_provider_info_scopes(requests_mock): ).get_scopes_string() +def test_provider_info_scopes_not_in_scopes_supported(requests_mock): + """ + Requested scopes should be preserved even when the provider's `scopes_supported` + discovery field does not list them (e.g. Microsoft Entra ID, which reports a fixed + tenant-wide list there regardless of which custom scopes it actually accepts). + https://github.com/Open-EO/openeo-python-client/issues/930 + """ + requests_mock.get( + "https://authit.test/.well-known/openid-configuration", + json={"scopes_supported": ["openid", "profile", "email", "offline_access"]}, + ) + provider = OidcProviderInfo( + issuer="https://authit.test", scopes=["openid", "profile", "email", "api://client-id/openeo"] + ) + assert provider.get_scopes_string() == "api://client-id/openeo email openid profile" + + def test_provider_info_default_client_none(requests_mock): requests_mock.get("https://authit.test/.well-known/openid-configuration", json={}) info = OidcProviderInfo(issuer="https://authit.test")