Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
26422dc
add jwt conformance to DummyBackend
niebl Feb 5, 2026
cde00d6
add further conformance
niebl Feb 6, 2026
b0a66a5
add conformance checking to basic auth
niebl Feb 6, 2026
d4d5dad
fix has_conformance
niebl Feb 6, 2026
1e75abe
add jwt conformant bearer token support to oidc auth
niebl Feb 6, 2026
6566959
Add tests for jwt conformance
niebl Feb 9, 2026
208b729
add tests for basic authentication
niebl Feb 9, 2026
39958bf
fix bearer token formatting
niebl Feb 9, 2026
65eff77
fix basic auth test
niebl Feb 9, 2026
4273d74
refactor requests_mock
niebl Feb 9, 2026
84a82ab
use comparableVersion for cofnormance determination
niebl Feb 9, 2026
ce742e3
Update openeo/rest/auth/auth.py
niebl Feb 10, 2026
d05271f
Update openeo/rest/_testing.py
niebl Feb 10, 2026
5166eda
Update openeo/rest/_testing.py
niebl Feb 10, 2026
9884bf8
refactor to use get
niebl Feb 10, 2026
299a079
indentation
niebl Feb 10, 2026
71a4503
refactor conformance string
niebl Feb 10, 2026
d8dda41
fix: OidcBearerAuth
niebl Feb 10, 2026
7107497
Update tests/rest/test_connection.py
niebl Feb 10, 2026
d925094
use re in has_conformance
niebl Feb 10, 2026
7a92e8f
add oidc tests for jwt bearer token
niebl Feb 10, 2026
9317759
Apply suggestion from @m-mohr
niebl Feb 10, 2026
dc89970
line breaks
niebl Feb 10, 2026
6040a9c
Update openeo/rest/_testing.py
niebl Feb 18, 2026
8b72876
bump stack version in build conformance
niebl Feb 18, 2026
85ff1b0
keep bearer auth simple
niebl Feb 18, 2026
240c18d
formatting
niebl Feb 18, 2026
94fe914
change import location of jwt bearer uri template
niebl Feb 18, 2026
e6230a3
revert test_testing.py to af55fd68312c6e0b1bf7c819d576d7d0ec32e959
niebl Feb 18, 2026
c2c5766
re-add tests for jwt conformance
niebl Feb 18, 2026
82c00f4
parametrize basic auth tests
niebl Feb 18, 2026
1d5c20f
parametrize oidc tests
niebl Feb 18, 2026
8f960e8
use compiled regex to for has_conformance
niebl Feb 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion openeo/rest/_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)"""
Expand Down Expand Up @@ -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(
*,
Expand Down Expand Up @@ -470,17 +493,23 @@ 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,
"id": "dummy",
"title": "Dummy openEO back-end",
"description": "Dummy openeEO back-end",
"endpoints": endpoints,
"conformsTo": conformance,
"links": [],
}
return capabilities
1 change: 1 addition & 0 deletions openeo/rest/auth/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

10 changes: 10 additions & 0 deletions openeo/rest/capabilities.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from typing import Dict, List, Optional, Union

from openeo.internal.jupyter import render_component
Expand All @@ -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."""
Expand Down Expand Up @@ -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(
Expand Down
18 changes: 15 additions & 3 deletions openeo/rest/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions tests/rest/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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:
Expand Down
Loading
Loading