From a806c8c97f388c013ce3af6c7e237718aa78dbc2 Mon Sep 17 00:00:00 2001 From: Stefaan Lippens Date: Thu, 18 Mar 2021 16:12:53 +0100 Subject: [PATCH] Issue #192: Initial/experimental support for default_client related: EP-3700/EP-3759 --- CHANGELOG.md | 1 + openeo/rest/auth/oidc.py | 11 ++++++++++- openeo/rest/connection.py | 21 +++++++++++++++++---- tests/rest/auth/test_oidc.py | 12 ++++++++++++ 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4f81674e..512cfa2e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add initial/experimental support for OIDC device code flow with PKCE (alternative for client secret) ([#191](https://github.com/Open-EO/openeo-python-client/issues/191) / EP-3700) - When creating a connection: use "https://" by default when no protocol is specified - `DataCube.mask_polygon`: support `Parameter` argument for `mask` +- Add initial/experimental support for default OIDC client ([#192](https://github.com/Open-EO/openeo-python-client/issues/192), [Open-EO/openeo-api#366](https://github.com/Open-EO/openeo-api/pull/366)) ### Changed diff --git a/openeo/rest/auth/oidc.py b/openeo/rest/auth/oidc.py index 0dcb27133..b75c33392 100644 --- a/openeo/rest/auth/oidc.py +++ b/openeo/rest/auth/oidc.py @@ -219,7 +219,12 @@ def _decode(data: str) -> dict: class OidcProviderInfo: """OpenID Connect Provider information, as provided by an openEO back-end (endpoint `/credentials/oidc`)""" - def __init__(self, issuer: str = None, discovery_url: str = None, scopes: List[str] = None): + # TODO: The "default_client" feature is still experimental in openEO API. See Open-EO/openeo-api#366 + + def __init__( + self, issuer: str = None, discovery_url: str = None, scopes: List[str] = None, + default_client: Union[dict, None] = None + ): if issuer is None and discovery_url is None: raise ValueError("At least `issuer` or `discovery_url` should be specified") self.discovery_url = discovery_url or (issuer.rstrip("/") + "/.well-known/openid-configuration") @@ -230,6 +235,7 @@ def __init__(self, issuer: str = None, discovery_url: str = None, scopes: List[s # 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) + self.default_client = default_client def get_scopes_string(self, request_refresh_token: bool = False): """ @@ -244,6 +250,9 @@ def get_scopes_string(self, request_refresh_token: bool = False): scopes = scopes | {"offline_access"} return " ".join(sorted(scopes)) + def get_default_client_id(self) -> Union[str, None]: + return self.default_client and self.default_client.get("id") + class OidcClientInfo: """ diff --git a/openeo/rest/connection.py b/openeo/rest/connection.py index fe48f12b7..d0d547fb6 100644 --- a/openeo/rest/connection.py +++ b/openeo/rest/connection.py @@ -331,7 +331,11 @@ def _get_oidc_provider(self, provider_id: Union[str, None] = None) -> Tuple[str, raise OpenEoClientException("No provider_id given. Available: {p!r}.".format( p=list(providers.keys())) ) - provider = OidcProviderInfo(issuer=provider["issuer"], scopes=provider.get("scopes")) + provider = OidcProviderInfo( + issuer=provider["issuer"], scopes=provider.get("scopes"), + # TODO: This "default_client" feature is still experimental in openEO API. See Open-EO/openeo-api#366 + default_client=provider.get("default_client") + ) else: # Per spec: '/credentials/oidc' will redirect to OpenID Connect discovery document provider = OidcProviderInfo(discovery_url=self.build_url('/credentials/oidc')) @@ -355,9 +359,18 @@ def _get_oidc_provider_and_client_info( client_id, client_secret = self._get_auth_config().get_oidc_client_configs( backend=self._orig_url, provider_id=provider_id ) - _log.info("Using client_id {c!r} from config (provider {p!r})".format(c=client_id, p=provider_id)) - if client_id is None: - raise OpenEoClientException("No client ID found.") + if client_id: + _log.info("Using client_id {c!r} from config (provider {p!r})".format(c=client_id, p=provider_id)) + if client_id is None: + # TODO: This "default_client" feature is still experimental in openEO API. See Open-EO/openeo-api#366 + # Try "default_client" from backend's provider info. + client_id = provider.get_default_client_id() + if client_id: + _log.info("Using default client_id {c!r} from OIDC provider {p!r} info.".format( + c=client_id, p=provider_id + )) + if client_id is None: + raise OpenEoClientException("No client ID found.") client_info = OidcClientInfo(client_id=client_id, client_secret=client_secret, provider=provider) diff --git a/tests/rest/auth/test_oidc.py b/tests/rest/auth/test_oidc.py index 983aa9e3f..dfe79fab6 100644 --- a/tests/rest/auth/test_oidc.py +++ b/tests/rest/auth/test_oidc.py @@ -122,6 +122,18 @@ def test_provider_info_scopes(requests_mock): ).get_scopes_string() +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") + assert info.get_default_client_id() is None + + +def test_provider_info_default_client_available(requests_mock): + requests_mock.get("https://authit.test/.well-known/openid-configuration", json={}) + info = OidcProviderInfo(issuer="https://authit.test", default_client={"id": "jak4l0v3-45lsdfe3d"}) + assert info.get_default_client_id() == "jak4l0v3-45lsdfe3d" + + @pytest.mark.parametrize( ["scopes_supported", "expected"], [ (["openid", "email"], "openid"),