diff --git a/openeo/rest/_testing.py b/openeo/rest/_testing.py index 998874551..95c8ace7e 100644 --- a/openeo/rest/_testing.py +++ b/openeo/rest/_testing.py @@ -14,6 +14,7 @@ Union, ) +from openeo.utils.version import ComparableVersion from openeo import Connection, DataCube from openeo.rest.vectorcube import VectorCube from openeo.utils.http import HTTP_201_CREATED, HTTP_202_ACCEPTED, HTTP_204_NO_CONTENT @@ -189,6 +190,14 @@ def setup_file_format(self, name: str, type: str = "output", gis_data_types: Ite } self._requests_mock.get(self.connection.build_url("/file_formats"), json=self.file_formats) return self + + def _get_conformance(self, request, context): + return { + "conformsTo": build_conformance( + api_version="1.3.0", + stac_version="1.0.0" + ) + } def _handle_post_result(self, request, context): """handler of `POST /result` (synchronous execute)""" @@ -424,6 +433,20 @@ def get_status(job_id: str, current_status: str) -> str: self.job_status_updater = get_status +def build_conformance( + *, + api_version: str = "1.0.0", + stac_version: str = "1.1.0", +) -> list[str]: + conformance = [ + "https://api.openeo.org/{api_version}", + "https://api.stacspec.org/v{stac_version}/core", + "https://api.stacspec.org/v{stac_version}/collections" + ] + if ComparableVersion(api_version) >= ComparableVersion("1.3.0"): + conformance.append(f"https://api.openeo.org/{api_version}/authentication/jwt") + return conformance + def build_capabilities( *, @@ -470,10 +493,15 @@ def build_capabilities( endpoints.extend( [ {"path": "/process_graphs", "methods": ["GET"]}, - {"path": "/process_graphs/{process_graph_id", "methods": ["GET", "PUT", "DELETE"]}, + {"path": "/process_graphs/{process_graph_id}", "methods": ["GET", "PUT", "DELETE"]}, ] ) + conformance = build_conformance( + api_version=api_version, + stac_version=stac_version, + ) + capabilities = { "api_version": api_version, "stac_version": stac_version, @@ -481,6 +509,7 @@ def build_capabilities( "title": "Dummy openEO back-end", "description": "Dummy openeEO back-end", "endpoints": endpoints, + "conformsTo": conformance, "links": [], } return capabilities diff --git a/openeo/rest/auth/auth.py b/openeo/rest/auth/auth.py index 378fbdbc2..cfa3329b4 100644 --- a/openeo/rest/auth/auth.py +++ b/openeo/rest/auth/auth.py @@ -50,3 +50,4 @@ class OidcBearerAuth(BearerAuth): def __init__(self, provider_id: str, access_token: str): super().__init__(bearer="oidc/{p}/{t}".format(p=provider_id, t=access_token)) + diff --git a/openeo/rest/capabilities.py b/openeo/rest/capabilities.py index 768093f6f..42504a4be 100644 --- a/openeo/rest/capabilities.py +++ b/openeo/rest/capabilities.py @@ -1,3 +1,4 @@ +import re from typing import Dict, List, Optional, Union from openeo.internal.jupyter import render_component @@ -7,6 +8,8 @@ __all__ = ["OpenEoCapabilities"] +CONFORMANCE_JWT_BEARER = re.compile(r"https://api\.openeo\.org/[^/]+/authentication/jwt") + class OpenEoCapabilities: """Container of the openEO capabilities document of an openEO backend.""" @@ -37,6 +40,13 @@ def api_version_check(self) -> ComparableVersion: raise ApiVersionException("No API version found") return ComparableVersion(api_version) + def has_conformance(self, uri: str) -> bool: + """Check if backend provides a given conformance string""" + for conformance_uri in self.capabilities.get("conformsTo", []): + if re.fullmatch(uri, conformance_uri): + return True + return False + def supports_endpoint(self, path: str, method="GET") -> bool: """Check if backend supports given endpoint""" return any( diff --git a/openeo/rest/connection.py b/openeo/rest/connection.py index d4d4d5995..8a40c43a8 100644 --- a/openeo/rest/connection.py +++ b/openeo/rest/connection.py @@ -68,7 +68,7 @@ OidcRefreshTokenAuthenticator, OidcResourceOwnerPasswordAuthenticator, ) -from openeo.rest.capabilities import OpenEoCapabilities +from openeo.rest.capabilities import CONFORMANCE_JWT_BEARER, OpenEoCapabilities from openeo.rest.datacube import DataCube, InputDate from openeo.rest.graph_building import CollectionProperty from openeo.rest.job import BatchJob @@ -277,8 +277,15 @@ def authenticate_basic(self, username: Optional[str] = None, password: Optional[ # /credentials/basic is the only endpoint that expects a Basic HTTP auth auth=HTTPBasicAuth(username, password) ).json() + + # check for JWT bearer token conformance + jwt_conformance = self.capabilities().has_conformance(CONFORMANCE_JWT_BEARER) + # Switch to bearer based authentication in further requests. - self.auth = BasicBearerAuth(access_token=resp["access_token"]) + if jwt_conformance: + self.auth = BearerAuth(bearer=resp["access_token"]) + else: + self.auth = BasicBearerAuth(access_token=resp["access_token"]) return self def _get_oidc_provider( @@ -416,7 +423,12 @@ def _authenticate_oidc( ) token = tokens.access_token - self.auth = OidcBearerAuth(provider_id=provider_id, access_token=token) + # check for JWT bearer token conformance + jwt_conformance = self.capabilities().has_conformance(CONFORMANCE_JWT_BEARER) + if jwt_conformance: + self.auth = BearerAuth(bearer=token) + else: + self.auth = OidcBearerAuth(provider_id=provider_id, access_token=token) self._oidc_auth_renewer = oidc_auth_renewer return self diff --git a/tests/rest/conftest.py b/tests/rest/conftest.py index 2255cca85..a84438894 100644 --- a/tests/rest/conftest.py +++ b/tests/rest/conftest.py @@ -19,6 +19,10 @@ def api_version(request): return request.param +@pytest.fixture(params=["1.0.0", "1.3.0"]) +def api_version_authentication_tests(request): + return request.param + class _Sleeper: def __init__(self): self.history = [] @@ -99,6 +103,12 @@ def con120(requests_mock, api_capabilities): con = Connection(API_URL) return con +@pytest.fixture +def con130(requests_mock, api_capabilities): + requests_mock.get(API_URL, json=build_capabilities(api_version="1.3.0", **api_capabilities)) + con = Connection(API_URL) + return con + @pytest.fixture def dummy_backend(requests_mock, con120) -> DummyBackend: diff --git a/tests/rest/test_connection.py b/tests/rest/test_connection.py index 6da731f71..501c1893e 100644 --- a/tests/rest/test_connection.py +++ b/tests/rest/test_connection.py @@ -39,6 +39,7 @@ from openeo.rest.auth.auth import BearerAuth, NullAuth from openeo.rest.auth.oidc import OidcException from openeo.rest.auth.testing import ABSENT, OidcMock, SimpleBasicAuthMocker +from openeo.rest.capabilities import CONFORMANCE_JWT_BEARER from openeo.rest.connection import ( DEFAULT_TIMEOUT, DEFAULT_TIMEOUT_SYNCHRONOUS_EXECUTE, @@ -407,8 +408,8 @@ def test_connect_with_session(): ], "https://oeo.test/openeo/1.1.0/", "1.1.0", - ), - ( + ), + ( [ {"api_version": "0.4.1", "url": "https://oeo.test/openeo/0.4.1/"}, {"api_version": "1.0.0", "url": "https://oeo.test/openeo/1.0.0/"}, @@ -462,8 +463,8 @@ def test_connect_with_session(): ], "https://oeo.test/openeo/1.1.0/", "1.1.0", - ), - ( + ), + ( [ { "api_version": "0.1.0", @@ -721,8 +722,8 @@ def test_api_error_non_json(requests_mock): assert exc.message == "olapola" -def test_create_connection_lazy_auth_config(requests_mock, api_version, basic_auth): - requests_mock.get(API_URL, json={"api_version": api_version, "endpoints": BASIC_ENDPOINTS}) +def test_create_connection_lazy_auth_config(requests_mock, api_version_authentication_tests, basic_auth): + requests_mock.get(API_URL, json={"api_version": api_version_authentication_tests, "endpoints": BASIC_ENDPOINTS}) with mock.patch('openeo.rest.connection.AuthConfig') as AuthConfig: # Don't create default AuthConfig when not necessary @@ -768,8 +769,8 @@ def test_create_connection_lazy_refresh_token_store(requests_mock): ) -def test_list_auth_providers(requests_mock, api_version): - requests_mock.get(API_URL, json=build_capabilities(api_version=api_version)) +def test_list_auth_providers(requests_mock, api_version_authentication_tests): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) requests_mock.get( API_URL + "credentials/oidc", json={ @@ -803,10 +804,10 @@ def test_list_auth_providers(requests_mock, api_version): assert basic["title"] == "Internal" -def test_list_auth_providers_empty(requests_mock, api_version): +def test_list_auth_providers_empty(requests_mock, api_version_authentication_tests): requests_mock.get( API_URL, - json=build_capabilities(api_version=api_version, basic_auth=False, oidc_auth=False), + json=build_capabilities(api_version=api_version_authentication_tests, basic_auth=False, oidc_auth=False), ) conn = Connection(API_URL) @@ -814,8 +815,8 @@ def test_list_auth_providers_empty(requests_mock, api_version): assert len(providers) == 0 -def test_list_auth_providers_invalid(requests_mock, api_version, caplog): - requests_mock.get(API_URL, json=build_capabilities(api_version=api_version, basic_auth=False)) +def test_list_auth_providers_invalid(requests_mock, api_version_authentication_tests, caplog): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests, basic_auth=False)) error_message = "Maintenance ongoing" requests_mock.get( API_URL + "credentials/oidc", @@ -829,8 +830,8 @@ def test_list_auth_providers_invalid(requests_mock, api_version, caplog): assert f"Unable to load the OpenID Connect provider list: {error_message}" in caplog.messages -def test_authenticate_basic_no_support(requests_mock, api_version): - requests_mock.get(API_URL, json={"api_version": api_version, "endpoints": []}) +def test_authenticate_basic_no_support(requests_mock, api_version_authentication_tests): + requests_mock.get(API_URL, json={"api_version": api_version_authentication_tests, "endpoints": []}) conn = Connection(API_URL) assert isinstance(conn.auth, NullAuth) @@ -839,30 +840,41 @@ def test_authenticate_basic_no_support(requests_mock, api_version): assert isinstance(conn.auth, NullAuth) -def test_authenticate_basic(requests_mock, api_version, basic_auth): - requests_mock.get(API_URL, json={"api_version": api_version, "endpoints": BASIC_ENDPOINTS}) +def test_authenticate_basic(requests_mock, api_version_authentication_tests, basic_auth): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) conn = Connection(API_URL) + assert isinstance(conn.auth, NullAuth) conn.authenticate_basic(username=basic_auth.username, password=basic_auth.password) + capabilities = conn.capabilities() assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == "basic//6cc3570k3n" + if api_version_authentication_tests == "1.3.0": + assert capabilities.has_conformance(CONFORMANCE_JWT_BEARER) == True + assert conn.auth.bearer == "6cc3570k3n" + else: + assert conn.auth.bearer == "basic//6cc3570k3n" -def test_authenticate_basic_from_config(requests_mock, api_version, auth_config, basic_auth): - requests_mock.get(API_URL, json={"api_version": api_version, "endpoints": BASIC_ENDPOINTS}) +def test_authenticate_basic_from_config(requests_mock, api_version_authentication_tests, auth_config, basic_auth): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) auth_config.set_basic_auth(backend=API_URL, username=basic_auth.username, password=basic_auth.password) conn = Connection(API_URL) assert isinstance(conn.auth, NullAuth) conn.authenticate_basic() assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == "basic//6cc3570k3n" + if api_version_authentication_tests == "1.3.0": + assert conn.auth.bearer == "6cc3570k3n" + else: + assert conn.auth.bearer == "basic//6cc3570k3n" @pytest.mark.slow -def test_authenticate_oidc_authorization_code_100_single_implicit(requests_mock, caplog): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_authorization_code_100_single_implicit( + requests_mock, api_version_authentication_tests, caplog +): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" requests_mock.get(API_URL + 'credentials/oidc', json={ "providers": [{"id": "fauth", "issuer": "https://fauth.test", "title": "Foo Auth", "scopes": ["openid", "im"]}] @@ -882,12 +894,16 @@ def test_authenticate_oidc_authorization_code_100_single_implicit(requests_mock, assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_authorization_code(client_id=client_id, webbrowser_open=oidc_mock.webbrowser_open) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/fauth/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/fauth/" + oidc_mock.state["access_token"] assert "No OIDC provider given, but only one available: 'fauth'. Using that one." in caplog.text -def test_authenticate_oidc_authorization_code_100_single_wrong_id(requests_mock): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_authorization_code_100_single_wrong_id(requests_mock, api_version_authentication_tests): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" requests_mock.get(API_URL + 'credentials/oidc', json={ "providers": [{"id": "fauth", "issuer": "https://fauth.test", "title": "Foo Auth", "scopes": ["openid", "w"]}] @@ -903,8 +919,10 @@ def test_authenticate_oidc_authorization_code_100_single_wrong_id(requests_mock) @pytest.mark.slow -def test_authenticate_oidc_authorization_code_100_multiple_no_given_id(requests_mock, caplog): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_authorization_code_100_multiple_no_given_id( + requests_mock, api_version_authentication_tests, caplog +): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" requests_mock.get(API_URL + 'credentials/oidc', json={ "providers": [ @@ -927,7 +945,11 @@ def test_authenticate_oidc_authorization_code_100_multiple_no_given_id(requests_ assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_authorization_code(client_id=client_id, webbrowser_open=oidc_mock.webbrowser_open) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/fauth/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/fauth/" + oidc_mock.state["access_token"] assert "No OIDC provider given. Using first provider 'fauth' as advertised by backend." in caplog.text @@ -950,8 +972,8 @@ def test_authenticate_oidc_authorization_code_100_multiple_wrong_id(requests_moc @pytest.mark.slow -def test_authenticate_oidc_authorization_code_100_multiple_success(requests_mock): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_authorization_code_100_multiple_success(requests_mock, api_version_authentication_tests): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" requests_mock.get(API_URL + 'credentials/oidc', json={ "providers": [ @@ -975,7 +997,11 @@ def test_authenticate_oidc_authorization_code_100_multiple_success(requests_mock client_id=client_id, provider_id="bauth", webbrowser_open=oidc_mock.webbrowser_open ) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/bauth/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/bauth/" + oidc_mock.state["access_token"] @pytest.mark.slow @@ -988,8 +1014,10 @@ def test_authenticate_oidc_authorization_code_100_multiple_success(requests_mock (True, ["openid", "email", "offline_access"], "offline_access openid"), ] ) -def test_authenticate_oidc_auth_code_pkce_flow(requests_mock, store_refresh_token, scopes_supported, expected_scope): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_auth_code_pkce_flow( + requests_mock, api_version_authentication_tests, store_refresh_token, scopes_supported, expected_scope +): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" issuer = "https://oidc.test" requests_mock.get(API_URL + 'credentials/oidc', json={ @@ -1012,7 +1040,11 @@ def test_authenticate_oidc_auth_code_pkce_flow(requests_mock, store_refresh_toke client_id=client_id, webbrowser_open=oidc_mock.webbrowser_open, store_refresh_token=store_refresh_token ) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] if store_refresh_token: refresh_token = oidc_mock.state["refresh_token"] assert refresh_token_store.mock_calls == [ @@ -1023,8 +1055,10 @@ def test_authenticate_oidc_auth_code_pkce_flow(requests_mock, store_refresh_toke @pytest.mark.slow -def test_authenticate_oidc_auth_code_pkce_flow_client_from_config(requests_mock, auth_config): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_auth_code_pkce_flow_client_from_config( + requests_mock, api_version_authentication_tests, auth_config +): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" issuer = "https://oidc.test" requests_mock.get(API_URL + 'credentials/oidc', json={ @@ -1046,12 +1080,16 @@ def test_authenticate_oidc_auth_code_pkce_flow_client_from_config(requests_mock, assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_authorization_code(webbrowser_open=oidc_mock.webbrowser_open) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] -def test_authenticate_oidc_client_credentials(requests_mock): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_client_credentials(requests_mock, api_version_authentication_tests): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" client_secret = "$3cr3t" issuer = "https://oidc.test" @@ -1074,17 +1112,27 @@ def test_authenticate_oidc_client_credentials(requests_mock): client_id=client_id, client_secret=client_secret ) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] # Again but store refresh token conn.authenticate_oidc_client_credentials(client_id=client_id, client_secret=client_secret) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] -def test_authenticate_oidc_client_credentials_client_from_config(requests_mock, auth_config): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_client_credentials_client_from_config( + requests_mock, api_version_authentication_tests, auth_config +): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" client_secret = "$3cr3t" issuer = "https://oidc.test" @@ -1108,7 +1156,11 @@ def test_authenticate_oidc_client_credentials_client_from_config(requests_mock, assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_client_credentials() assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] @@ -1121,9 +1173,9 @@ def test_authenticate_oidc_client_credentials_client_from_config(requests_mock, ], ) def test_authenticate_oidc_client_credentials_client_from_env( - requests_mock, monkeypatch, env_provider_id, expected_provider_id + requests_mock, monkeypatch, env_provider_id, expected_provider_id, api_version_authentication_tests ): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" client_secret = "$3cr3t" monkeypatch.setenv("OPENEO_AUTH_CLIENT_ID", client_id) @@ -1153,7 +1205,11 @@ def test_authenticate_oidc_client_credentials_client_from_env( assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_client_credentials() assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == f"oidc/{expected_provider_id}/" + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == f"oidc/{expected_provider_id}/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] @@ -1182,8 +1238,9 @@ def test_authenticate_oidc_client_credentials_client_precedence( env_client_id, arg_client_id, expected_client_id, + api_version_authentication_tests, ): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_secret = "$3cr3t" if env_client_id: monkeypatch.setenv("OPENEO_AUTH_CLIENT_ID", env_client_id) @@ -1215,7 +1272,11 @@ def test_authenticate_oidc_client_credentials_client_precedence( client_id=arg_client_id, client_secret=client_secret if arg_client_id else None, provider_id=arg_provider_id ) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == f"oidc/{expected_provider_id}/" + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == f"oidc/{expected_provider_id}/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] @@ -1232,9 +1293,16 @@ def test_authenticate_oidc_client_credentials_client_precedence( ], ) def test_authenticate_oidc_client_credentials_client_multiple_provider_resolution( - requests_mock, monkeypatch, auth_config, provider_id_arg, provider_id_env, provider_id_conf, expected_provider_id + requests_mock, + monkeypatch, + auth_config, + provider_id_arg, + provider_id_env, + provider_id_conf, + expected_provider_id, + api_version_authentication_tests, ): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" client_secret = "$3cr3t" monkeypatch.setenv("OPENEO_AUTH_CLIENT_ID", client_id) @@ -1278,12 +1346,16 @@ def test_authenticate_oidc_client_credentials_client_multiple_provider_resolutio assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_client_credentials(provider_id=provider_id_arg) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == f"oidc/{expected_provider_id}/" + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == f"oidc/{expected_provider_id}/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] -def test_authenticate_oidc_resource_owner_password_credentials(requests_mock): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_resource_owner_password_credentials(requests_mock, api_version_authentication_tests): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" client_secret = "$3cr3t" username, password = "john", "j0hn" @@ -1309,7 +1381,11 @@ def test_authenticate_oidc_resource_owner_password_credentials(requests_mock): client_id=client_id, username=username, password=password, client_secret=client_secret ) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] # Again but store refresh token conn.authenticate_oidc_resource_owner_password_credentials( @@ -1317,14 +1393,21 @@ def test_authenticate_oidc_resource_owner_password_credentials(requests_mock): store_refresh_token=True ) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [ mock.call.set_refresh_token(client_id=client_id, issuer=issuer, refresh_token=oidc_mock.state["refresh_token"]) ] -def test_authenticate_oidc_resource_owner_password_credentials_client_from_config(requests_mock, auth_config): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_resource_owner_password_credentials_client_from_config( + requests_mock, auth_config, api_version_authentication_tests +): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) + client_id = "myclient" client_secret = "$3cr3t" username, password = "john", "j0hn" @@ -1351,7 +1434,11 @@ def test_authenticate_oidc_resource_owner_password_credentials_client_from_confi assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_resource_owner_password_credentials(username=username, password=password) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] @@ -1365,9 +1452,14 @@ def test_authenticate_oidc_resource_owner_password_credentials_client_from_confi ] ) def test_authenticate_oidc_device_flow_with_secret( - requests_mock, store_refresh_token, scopes_supported, expected_scopes, oidc_device_code_flow_checker + requests_mock, + store_refresh_token, + scopes_supported, + expected_scopes, + oidc_device_code_flow_checker, + api_version_authentication_tests, ): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" client_secret = "$3cr3t" issuer = "https://oidc.test" @@ -1397,7 +1489,11 @@ def test_authenticate_oidc_device_flow_with_secret( client_id=client_id, client_secret=client_secret, store_refresh_token=store_refresh_token ) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] if store_refresh_token: refresh_token = oidc_mock.state["refresh_token"] assert refresh_token_store.mock_calls == [ @@ -1408,9 +1504,9 @@ def test_authenticate_oidc_device_flow_with_secret( def test_authenticate_oidc_device_flow_with_secret_from_config( - requests_mock, auth_config, caplog, oidc_device_code_flow_checker + requests_mock, auth_config, caplog, oidc_device_code_flow_checker, api_version_authentication_tests ): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" client_secret = "$3cr3t" issuer = "https://oidc.test" @@ -1440,15 +1536,19 @@ def test_authenticate_oidc_device_flow_with_secret_from_config( with oidc_device_code_flow_checker(): conn.authenticate_oidc_device() assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] assert "No OIDC provider given, but only one available: 'oi'. Using that one." in caplog.text assert "Using client_id 'myclient' from config (provider 'oi')" in caplog.text @pytest.mark.slow -def test_authenticate_oidc_device_flow_no_support(requests_mock, auth_config): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_device_flow_no_support(requests_mock, auth_config, api_version_authentication_tests): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" client_secret = "$3cr3t" issuer = "https://oidc.test" @@ -1482,10 +1582,16 @@ def test_authenticate_oidc_device_flow_no_support(requests_mock, auth_config): (False, False), ]) def test_authenticate_oidc_device_flow_pkce_multiple_providers_no_given( - requests_mock, auth_config, caplog, use_pkce, expect_pkce, oidc_device_code_flow_checker + requests_mock, + auth_config, + caplog, + use_pkce, + expect_pkce, + oidc_device_code_flow_checker, + api_version_authentication_tests, ): """OIDC device flow + PKCE with multiple OIDC providers and none specified to use.""" - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" requests_mock.get(API_URL + 'credentials/oidc', json={ "providers": [ @@ -1517,7 +1623,11 @@ def test_authenticate_oidc_device_flow_pkce_multiple_providers_no_given( with oidc_device_code_flow_checker(url=f"{oidc_issuer}/dc"): conn.authenticate_oidc_device(client_id=client_id, use_pkce=use_pkce) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/fauth/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/fauth/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] assert "No OIDC provider given. Using first provider 'fauth' as advertised by backend." in caplog.text @@ -1528,10 +1638,16 @@ def test_authenticate_oidc_device_flow_pkce_multiple_providers_no_given( (False, False), ]) def test_authenticate_oidc_device_flow_pkce_multiple_provider_one_config_no_given( - requests_mock, auth_config, caplog, use_pkce, expect_pkce, oidc_device_code_flow_checker + requests_mock, + auth_config, + caplog, + use_pkce, + expect_pkce, + oidc_device_code_flow_checker, + api_version_authentication_tests, ): """OIDC device flow + PKCE with multiple OIDC providers, one in config and none specified to use.""" - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" requests_mock.get(API_URL + 'credentials/oidc', json={ "providers": [ @@ -1564,19 +1680,23 @@ def test_authenticate_oidc_device_flow_pkce_multiple_provider_one_config_no_give with oidc_device_code_flow_checker(url=f"{oidc_issuer}/dc"): conn.authenticate_oidc_device(use_pkce=use_pkce) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/fauth/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/fauth/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] assert "No OIDC provider given, but only one in config (for backend 'https://oeo.test/'): 'fauth'. Using that one." in caplog.text assert "Using client_id 'myclient' from config (provider 'fauth')" in caplog.text def test_authenticate_oidc_device_flow_pkce_multiple_provider_one_config_no_given_default_client( - requests_mock, auth_config, oidc_device_code_flow_checker + requests_mock, auth_config, oidc_device_code_flow_checker, api_version_authentication_tests ): """ OIDC device flow + default_clients + PKCE with multiple OIDC providers, one in config and none specified to use. """ - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) default_client_id = "dadefaultklient" requests_mock.get(API_URL + 'credentials/oidc', json={ "providers": [ @@ -1612,7 +1732,11 @@ def test_authenticate_oidc_device_flow_pkce_multiple_provider_one_config_no_give with oidc_device_code_flow_checker(url=f"{oidc_issuer}/dc"): conn.authenticate_oidc_device() assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/bauth/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/bauth/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] @@ -1637,11 +1761,12 @@ def test_authenticate_oidc_device_flow_pkce_multiple_provider_resolution( provider_id_conf, expected_provider, monkeypatch, + api_version_authentication_tests, ): """ OIDC device flow + default_clients + PKCE with multiple OIDC providers: provider resolution/precedence """ - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "klientid" requests_mock.get( API_URL + "credentials/oidc", @@ -1686,7 +1811,11 @@ def test_authenticate_oidc_device_flow_pkce_multiple_provider_resolution( with oidc_device_code_flow_checker(url=f"{oidc_issuer}/dc"): conn.authenticate_oidc_device(client_id=client_id, provider_id=provider_id_arg) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == f"oidc/{expected_provider}/" + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == f"oidc/{expected_provider}/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] @@ -1701,12 +1830,12 @@ def test_authenticate_oidc_device_flow_pkce_multiple_provider_resolution( ], ) def test_authenticate_oidc_device_flow_pkce_default_client_handling( - requests_mock, grant_types, use_pkce, expect_pkce, oidc_device_code_flow_checker + requests_mock, grant_types, use_pkce, expect_pkce, oidc_device_code_flow_checker, api_version_authentication_tests ): """ OIDC device authn grant + secret/PKCE/neither: default client grant_types handling """ - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) default_client_id = "dadefaultklient" oidc_issuer = "https://auth.test" requests_mock.get( @@ -1749,13 +1878,19 @@ def test_authenticate_oidc_device_flow_pkce_default_client_handling( with oidc_device_code_flow_checker(url=f"{oidc_issuer}/dc"): conn.authenticate_oidc_device(use_pkce=use_pkce) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/auth/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/auth/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [] -def test_authenticate_oidc_device_flow_pkce_store_refresh_token(requests_mock, oidc_device_code_flow_checker): +def test_authenticate_oidc_device_flow_pkce_store_refresh_token( + requests_mock, oidc_device_code_flow_checker, api_version_authentication_tests +): """OIDC device authn grant + PKCE + refresh token storage""" - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) default_client_id = "dadefaultklient" requests_mock.get(API_URL + 'credentials/oidc', json={ "providers": [ @@ -1790,7 +1925,11 @@ def test_authenticate_oidc_device_flow_pkce_store_refresh_token(requests_mock, o with oidc_device_code_flow_checker(url=f"{oidc_issuer}/dc"): conn.authenticate_oidc_device(store_refresh_token=True) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/auth/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/auth/" + oidc_mock.state["access_token"] assert refresh_token_store.mock_calls == [ mock.call.set_refresh_token( client_id=default_client_id, issuer="https://auth.test", @@ -1799,8 +1938,8 @@ def test_authenticate_oidc_device_flow_pkce_store_refresh_token(requests_mock, o ] -def test_authenticate_oidc_refresh_token(requests_mock): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_refresh_token(requests_mock, api_version_authentication_tests): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" refresh_token = "r3fr35h!" issuer = "https://oidc.test" @@ -1821,11 +1960,15 @@ def test_authenticate_oidc_refresh_token(requests_mock): assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_refresh_token(refresh_token=refresh_token, client_id=client_id) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] -def test_authenticate_oidc_refresh_token_expired(requests_mock): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_refresh_token_expired(requests_mock, api_version_authentication_tests): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" issuer = "https://oidc.test" requests_mock.get(API_URL + 'credentials/oidc', json={ @@ -1861,10 +2004,17 @@ def test_authenticate_oidc_refresh_token_expired(requests_mock): ], ) def test_authenticate_oidc_refresh_token_multiple_provider_resolution( - requests_mock, auth_config, provider_id_arg, provider_id_env, provider_id_conf, expected_provider, monkeypatch + requests_mock, + auth_config, + provider_id_arg, + provider_id_env, + provider_id_conf, + expected_provider, + monkeypatch, + api_version_authentication_tests, ): """Multiple OIDC Providers: provider resolution/precedence""" - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" refresh_token = "r3fr35h!" requests_mock.get( @@ -1907,12 +2057,18 @@ def test_authenticate_oidc_refresh_token_multiple_provider_resolution( assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_refresh_token(refresh_token=refresh_token, client_id=client_id, provider_id=provider_id_arg) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == f"oidc/{expected_provider}/" + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == f"oidc/{expected_provider}/" + oidc_mock.state["access_token"] @pytest.mark.parametrize("store_refresh_token", [True, False]) -def test_authenticate_oidc_auto_with_existing_refresh_token(requests_mock, refresh_token_store, store_refresh_token): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_auto_with_existing_refresh_token( + requests_mock, refresh_token_store, store_refresh_token, api_version_authentication_tests +): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" orig_refresh_token = "r3fr35h!" issuer = "https://oidc.test" @@ -1933,7 +2089,11 @@ def test_authenticate_oidc_auto_with_existing_refresh_token(requests_mock, refre assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc(client_id=client_id, store_refresh_token=store_refresh_token) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] new_refresh_token = refresh_token_store.get_refresh_token(issuer=issuer, client_id=client_id) assert new_refresh_token == orig_refresh_token @@ -1949,9 +2109,14 @@ def test_authenticate_oidc_auto_with_existing_refresh_token(requests_mock, refre ], ) def test_authenticate_oidc_auto_no_existing_refresh_token( - requests_mock, refresh_token_store, use_pkce, expect_pkce, oidc_device_code_flow_checker + requests_mock, + refresh_token_store, + use_pkce, + expect_pkce, + oidc_device_code_flow_checker, + api_version_authentication_tests, ): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" issuer = "https://oidc.test" requests_mock.get(API_URL + 'credentials/oidc', json={ @@ -1978,7 +2143,11 @@ def test_authenticate_oidc_auto_no_existing_refresh_token( with oidc_device_code_flow_checker(): conn.authenticate_oidc(client_id=client_id, use_pkce=use_pkce) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] assert [r["grant_type"] for r in oidc_mock.grant_request_history] == [ "urn:ietf:params:oauth:grant-type:device_code" ] @@ -1993,9 +2162,14 @@ def test_authenticate_oidc_auto_no_existing_refresh_token( ], ) def test_authenticate_oidc_auto_expired_refresh_token( - requests_mock, refresh_token_store, use_pkce, expect_pkce, oidc_device_code_flow_checker + requests_mock, + refresh_token_store, + use_pkce, + expect_pkce, + oidc_device_code_flow_checker, + api_version_authentication_tests, ): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" issuer = "https://oidc.test" requests_mock.get(API_URL + 'credentials/oidc', json={ @@ -2023,7 +2197,10 @@ def test_authenticate_oidc_auto_expired_refresh_token( with oidc_device_code_flow_checker(): conn.authenticate_oidc(client_id=client_id, use_pkce=use_pkce) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] assert [r["grant_type"] for r in oidc_mock.grant_request_history] == [ "refresh_token", "urn:ietf:params:oauth:grant-type:device_code", @@ -2040,7 +2217,7 @@ def test_authenticate_oidc_auto_expired_refresh_token( ], ) def test_authenticate_oidc_method_client_credentials_from_env( - requests_mock, monkeypatch, env_provider_id, expected_provider_id + requests_mock, monkeypatch, env_provider_id, expected_provider_id, api_version_authentication_tests ): client_id = "myclient" client_secret = "$3cr3t!" @@ -2049,7 +2226,7 @@ def test_authenticate_oidc_method_client_credentials_from_env( monkeypatch.setenv("OPENEO_AUTH_CLIENT_SECRET", client_secret) if env_provider_id: monkeypatch.setenv("OPENEO_AUTH_PROVIDER_ID", env_provider_id) - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) requests_mock.get( API_URL + "credentials/oidc", json={ @@ -2072,7 +2249,10 @@ def test_authenticate_oidc_method_client_credentials_from_env( assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc() assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == f"oidc/{expected_provider_id}/" + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == f"oidc/{expected_provider_id}/" + oidc_mock.state["access_token"] def _setup_get_me_handler(requests_mock, oidc_mock: OidcMock, token_invalid_status_code: int = 403): @@ -2105,9 +2285,9 @@ def get_me(request: requests.Request, context): ], ) def test_authenticate_oidc_auto_renew_expired_access_token_initial_refresh_token( - requests_mock, refresh_token_store, invalidate, token_invalid_status_code, caplog + requests_mock, refresh_token_store, invalidate, token_invalid_status_code, caplog, api_version_authentication_tests ): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" initial_refresh_token = "r3fr35h!" oidc_issuer = "https://oidc.test" @@ -2143,7 +2323,10 @@ def test_authenticate_oidc_auto_renew_expired_access_token_initial_refresh_token refresh_token=initial_refresh_token, client_id=client_id, store_refresh_token=True ) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] # Just one "refresh_token" auth request so far assert [h["grant_type"] for h in oidc_mock.grant_request_history] == ["refresh_token"] access_token1 = oidc_mock.state["access_token"] @@ -2192,9 +2375,15 @@ def test_authenticate_oidc_auto_renew_expired_access_token_initial_refresh_token ], ) def test_authenticate_oidc_auto_renew_expired_access_token_initial_device_code( - requests_mock, refresh_token_store, invalidate, token_invalid_status_code, caplog, oidc_device_code_flow_checker + requests_mock, + refresh_token_store, + invalidate, + token_invalid_status_code, + caplog, + oidc_device_code_flow_checker, + api_version_authentication_tests, ): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" oidc_issuer = "https://oidc.test" requests_mock.get( @@ -2234,7 +2423,11 @@ def test_authenticate_oidc_auto_renew_expired_access_token_initial_device_code( with oidc_device_code_flow_checker(): conn.authenticate_oidc_device(client_id=client_id, use_pkce=True, store_refresh_token=True) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] # Just one "refresh_token" auth request so far assert [h["grant_type"] for h in oidc_mock.grant_request_history] == [ "urn:ietf:params:oauth:grant-type:device_code" @@ -2290,9 +2483,14 @@ def test_authenticate_oidc_auto_renew_expired_access_token_initial_device_code( ], ) def test_authenticate_oidc_auto_renew_expired_access_token_invalid_refresh_token( - requests_mock, refresh_token_store, caplog, oidc_device_code_flow_checker, token_invalid_status_code + requests_mock, + refresh_token_store, + caplog, + oidc_device_code_flow_checker, + token_invalid_status_code, + api_version_authentication_tests, ): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" oidc_issuer = "https://oidc.test" requests_mock.get( @@ -2332,7 +2530,10 @@ def test_authenticate_oidc_auto_renew_expired_access_token_invalid_refresh_token with oidc_device_code_flow_checker(): conn.authenticate_oidc_device(client_id=client_id, use_pkce=True, store_refresh_token=True) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] # Just one "refresh_token" auth request so far assert [h["grant_type"] for h in oidc_mock.grant_request_history] == [ "urn:ietf:params:oauth:grant-type:device_code" @@ -2363,8 +2564,10 @@ def test_authenticate_oidc_auto_renew_expired_access_token_invalid_refresh_token assert "Failed to obtain new access token (grant 'refresh_token')" in caplog.text -def test_authenticate_oidc_auto_renew_expired_access_token_other_errors(requests_mock, caplog): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) +def test_authenticate_oidc_auto_renew_expired_access_token_other_errors( + requests_mock, caplog, api_version_authentication_tests +): + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" initial_refresh_token = "r3fr35h!" oidc_issuer = "https://oidc.test" @@ -2404,7 +2607,10 @@ def get_me(request: requests.Request, context): assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_refresh_token(refresh_token=initial_refresh_token, client_id=client_id) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == 'oidc/oi/' + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] # Do request that will fail with "Internal" error with pytest.raises(OpenEoApiError, match=re.escape("[500] Internal: Something's not right.")): @@ -2422,9 +2628,9 @@ def get_me(request: requests.Request, context): ], ) def test_authenticate_oidc_auto_renew_expired_access_token_initial_client_credentials( - requests_mock, refresh_token_store, invalidate, token_invalid_status_code, caplog + requests_mock, refresh_token_store, invalidate, token_invalid_status_code, caplog, api_version_authentication_tests ): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" client_secret = "$3cr3t" oidc_issuer = "https://oidc.test" @@ -2450,7 +2656,11 @@ def test_authenticate_oidc_auto_renew_expired_access_token_initial_client_creden assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_client_credentials(client_id=client_id, client_secret=client_secret) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] # Just one "client_credentials" auth request so far assert [h["grant_type"] for h in oidc_mock.grant_request_history] == ["client_credentials"] @@ -2500,9 +2710,9 @@ def test_authenticate_oidc_auto_renew_expired_access_token_initial_client_creden ], ) def test_authenticate_oidc_auto_renew_expired_access_token_initial_client_credentials_blocked( - requests_mock, refresh_token_store, caplog, token_invalid_status_code + requests_mock, refresh_token_store, caplog, token_invalid_status_code, api_version_authentication_tests ): - requests_mock.get(API_URL, json={"api_version": "1.0.0"}) + requests_mock.get(API_URL, json=build_capabilities(api_version=api_version_authentication_tests)) client_id = "myclient" client_secret = "$3cr3t" issuer = "https://oidc.test" @@ -2528,7 +2738,11 @@ def test_authenticate_oidc_auto_renew_expired_access_token_initial_client_creden assert isinstance(conn.auth, NullAuth) conn.authenticate_oidc_client_credentials(client_id=client_id, client_secret=client_secret) assert isinstance(conn.auth, BearerAuth) - assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] + if api_version_authentication_tests == "1.3.0": + # TODO: migth require future tests for the issuer encoded in the jwt + assert conn.auth.bearer == oidc_mock.state["access_token"] + else: + assert conn.auth.bearer == "oidc/oi/" + oidc_mock.state["access_token"] # Just one "client_credentials" auth request so far assert [h["grant_type"] for h in oidc_mock.grant_request_history] == ["client_credentials"] access_token1 = oidc_mock.state["access_token"] @@ -5039,6 +5253,7 @@ def auto_validate(self, request) -> bool: @pytest.fixture def connection(self, api_version, requests_mock, api_capabilities, auto_validate) -> Connection: requests_mock.get(API_URL, json=build_capabilities(api_version=api_version, **api_capabilities)) + con = Connection(API_URL, **dict_no_none(auto_validate=auto_validate)) return con diff --git a/tests/rest/test_testing.py b/tests/rest/test_testing.py index 589dda3dc..1049d1af6 100644 --- a/tests/rest/test_testing.py +++ b/tests/rest/test_testing.py @@ -11,6 +11,11 @@ def dummy_backend(requests_mock, con120): return DummyBackend(requests_mock=requests_mock, connection=con120) +@pytest.fixture +def dummy_backend130(requests_mock, con130): + return DummyBackend(requests_mock=requests_mock, connection=con130) + + DUMMY_PG_ADD35 = { "add35": {"process_id": "add", "arguments": {"x": 3, "y": 5}, "result": True}, } @@ -104,3 +109,20 @@ def test_setup_job_start_failure(self, dummy_backend): with pytest.raises(OpenEoApiError, match=re.escape("[500] Internal: No job starting for you, buddy")): job.start() assert job.status() == "error" + + # for better distinction within the following tests + dummy_backend120 = dummy_backend + + def test_version(self, dummy_backend120, dummy_backend130): + capabilities120 = dummy_backend120.connection.capabilities() + capabilities130 = dummy_backend130.connection.capabilities() + + assert capabilities120.api_version() == "1.2.0" + assert capabilities130.api_version() == "1.3.0" + + def test_jwt_conformance(self, dummy_backend120, dummy_backend130): + capabilities120 = dummy_backend120.connection.capabilities() + capabilities130 = dummy_backend130.connection.capabilities() + + assert capabilities120.has_conformance("https://api.openeo.org/*/authentication/jwt") == False + assert capabilities130.has_conformance("https://api.openeo.org/*/authentication/jwt") == True