Skip to content

Commit 7e297f6

Browse files
committed
Add authenticate_oidc_access_token() for auth with out-of-band access token #590
1 parent 83fb7a9 commit 7e297f6

File tree

3 files changed

+64
-5
lines changed

3 files changed

+64
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Add experimental `openeo.testing.results` subpackage with reusable test utilities for comparing batch job results with reference data
1313
- `MultiBackendJobManager`: add initial support for storing job metadata in Parquet file (instead of CSV) ([#571](https://github.com/Open-EO/openeo-python-client/issues/571))
14+
- Add `Connection.authenticate_oidc_access_token()` to set up authorization headers with an access token that is obtained "out-of-band" ([#598](https://github.com/Open-EO/openeo-python-client/issues/598))
1415

1516
### Changed
1617

openeo/rest/connection.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -382,13 +382,19 @@ def authenticate_basic(self, username: Optional[str] = None, password: Optional[
382382
self.auth = BasicBearerAuth(access_token=resp["access_token"])
383383
return self
384384

385-
def _get_oidc_provider(self, provider_id: Union[str, None] = None) -> Tuple[str, OidcProviderInfo]:
385+
def _get_oidc_provider(
386+
self, provider_id: Union[str, None] = None, parse_info: bool = True
387+
) -> Tuple[str, Union[OidcProviderInfo, None]]:
386388
"""
387-
Get OpenID Connect discovery URL for given provider_id
389+
Get provider id and info, based on context.
390+
If provider_id is given, verify it against backend's list of providers.
391+
If not given, find a suitable provider based on env vars, config or backend's default.
388392
389393
:param provider_id: id of OIDC provider as specified by backend (/credentials/oidc).
390394
Can be None if there is just one provider.
391-
:return: updated provider_id and provider info object
395+
:param parse_info: whether to parse the provider info into an :py:class:`OidcProviderInfo` object
396+
(which involves a ".well-known/openid-configuration" request)
397+
:return: resolved/verified provider_id and provider info object (unless ``parse_info`` is False)
392398
"""
393399
oidc_info = self.get("/credentials/oidc", expected_status=200).json()
394400
providers = OrderedDict((p["id"], p) for p in oidc_info["providers"])
@@ -434,8 +440,10 @@ def _get_oidc_provider(self, provider_id: Union[str, None] = None) -> Tuple[str,
434440
_log.info(
435441
f"No OIDC provider given. Using first provider {provider_id!r} as advertised by backend."
436442
)
437-
provider = OidcProviderInfo.from_dict(provider)
438-
return provider_id, provider
443+
444+
provider_info = OidcProviderInfo.from_dict(provider) if parse_info else None
445+
446+
return provider_id, provider_info
439447

440448
def _get_oidc_provider_and_client_info(
441449
self,
@@ -766,6 +774,26 @@ def authenticate_oidc(
766774
print("Authenticated using device code flow.")
767775
return con
768776

777+
def authenticate_oidc_access_token(self, access_token: str, provider_id: Optional[str] = None) -> None:
778+
"""
779+
Set up authorization headers directly with an OIDC access token.
780+
781+
:py:class:`Connection` provides multiple methods to handle various OIDC authentication flows end-to-end.
782+
If you already obtained a valid OIDC access token in another "out-of-band" way, you can use this method to
783+
set up the authorization headers appropriately.
784+
785+
:param access_token: OIDC access token
786+
:param provider_id: id of the OIDC provider as listed by the openEO backend (``/credentials/oidc``).
787+
If not specified, the first (default) OIDC provider will be used.
788+
:param skip_verification: Skip clients-side verification of the provider_id
789+
against the backend's list of providers to avoid and related OIDC configuration
790+
791+
.. versionadded:: 0.31.0
792+
"""
793+
provider_id, _ = self._get_oidc_provider(provider_id=provider_id, parse_info=False)
794+
self.auth = OidcBearerAuth(provider_id=provider_id, access_token=access_token)
795+
self._oidc_auth_renewer = None
796+
769797
def request(
770798
self,
771799
method: str,

tests/rest/test_connection.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2349,6 +2349,36 @@ def test_authenticate_oidc_auto_renew_expired_access_token_initial_client_creden
23492349
assert "Failed to obtain new access token (grant 'client_credentials')" in caplog.text
23502350

23512351

2352+
class TestAuthenticateOidcAccessToken:
2353+
@pytest.fixture(autouse=True)
2354+
def _setup(self, requests_mock):
2355+
requests_mock.get(API_URL, json=build_capabilities())
2356+
requests_mock.get(
2357+
API_URL + "credentials/oidc",
2358+
json={"providers": [{"id": "oi", "issuer": "https://oidc.test", "title": "example", "scopes": ["openid"]}]},
2359+
)
2360+
2361+
def test_authenticate_oidc_access_token_default_provider(self):
2362+
connection = Connection(API_URL)
2363+
connection.authenticate_oidc_access_token(access_token="Th3Tok3n!@#")
2364+
assert isinstance(connection.auth, BearerAuth)
2365+
assert connection.auth.bearer == "oidc/oi/Th3Tok3n!@#"
2366+
2367+
def test_authenticate_oidc_access_token_with_provider(self):
2368+
connection = Connection(API_URL)
2369+
connection.authenticate_oidc_access_token(access_token="Th3Tok3n!@#", provider_id="oi")
2370+
assert isinstance(connection.auth, BearerAuth)
2371+
assert connection.auth.bearer == "oidc/oi/Th3Tok3n!@#"
2372+
2373+
def test_authenticate_oidc_access_token_wrong_provider(self):
2374+
connection = Connection(API_URL)
2375+
with pytest.raises(
2376+
OpenEoClientException,
2377+
match=re.escape("Requested OIDC provider 'nope' not available. Should be one of ['oi']."),
2378+
):
2379+
connection.authenticate_oidc_access_token(access_token="Th3Tok3n!@#", provider_id="nope")
2380+
2381+
23522382
def test_load_collection_arguments_100(requests_mock):
23532383
requests_mock.get(API_URL, json={"api_version": "1.0.0"})
23542384
conn = Connection(API_URL)

0 commit comments

Comments
 (0)