UPDATE
This commit is contained in:
+130
-15
@@ -7,11 +7,16 @@ from datetime import date
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
from config.proxy import apply_requests_proxy, build_proxy_url_from_proxychains_env
|
||||
|
||||
from .models import AnalysisGridCell
|
||||
|
||||
|
||||
DEFAULT_OPENEO_BACKEND_URL = "https://openeofed.dataspace.copernicus.eu"
|
||||
DEFAULT_OPENEO_PROVIDER = "openeo"
|
||||
DEFAULT_OPENEO_PROXY_URL = "socks5h://host.docker.internal:10808"
|
||||
|
||||
SENTINEL2_COLLECTION = "SENTINEL2_L2A"
|
||||
SENTINEL3_LST_COLLECTION = "SENTINEL3_SLSTR_L2_LST"
|
||||
@@ -42,22 +47,39 @@ class OpenEOExecutionError(OpenEOServiceError):
|
||||
"""Raised when a metric process graph can not be executed successfully."""
|
||||
|
||||
|
||||
class TimeoutOverrideSession(requests.Session):
|
||||
"""Requests session that enforces a minimum timeout for all outbound calls."""
|
||||
|
||||
def __init__(self, timeout_seconds: float):
|
||||
super().__init__()
|
||||
self.timeout_seconds = timeout_seconds
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
timeout = kwargs.get("timeout")
|
||||
if timeout is None or timeout < self.timeout_seconds:
|
||||
kwargs["timeout"] = self.timeout_seconds
|
||||
return super().request(method, url, **kwargs)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OpenEOConnectionSettings:
|
||||
backend_url: str = DEFAULT_OPENEO_BACKEND_URL
|
||||
auth_method: str = "client_credentials"
|
||||
timeout_seconds: float = 60.0
|
||||
client_id: str = ""
|
||||
client_secret: str = ""
|
||||
provider_id: str = ""
|
||||
username: str = ""
|
||||
password: str = ""
|
||||
allow_interactive_oidc: bool = False
|
||||
proxy_url: str = ""
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> "OpenEOConnectionSettings":
|
||||
return cls(
|
||||
backend_url=os.environ.get("OPENEO_BACKEND_URL", DEFAULT_OPENEO_BACKEND_URL).strip(),
|
||||
auth_method=os.environ.get("OPENEO_AUTH_METHOD", "client_credentials").strip().lower(),
|
||||
timeout_seconds=float(os.environ.get("OPENEO_TIMEOUT_SECONDS", "60").strip() or "60"),
|
||||
client_id=os.environ.get("OPENEO_AUTH_CLIENT_ID", "").strip(),
|
||||
client_secret=os.environ.get("OPENEO_AUTH_CLIENT_SECRET", "").strip(),
|
||||
provider_id=os.environ.get("OPENEO_AUTH_PROVIDER_ID", "").strip(),
|
||||
@@ -65,9 +87,40 @@ class OpenEOConnectionSettings:
|
||||
password=os.environ.get("OPENEO_PASSWORD", "").strip(),
|
||||
allow_interactive_oidc=os.environ.get("OPENEO_ALLOW_INTERACTIVE_OIDC", "0").strip().lower()
|
||||
in {"1", "true", "yes", "on"},
|
||||
proxy_url=_resolve_openeo_proxy_url_from_env(),
|
||||
)
|
||||
|
||||
|
||||
def _resolve_openeo_proxy_url_from_env() -> str:
|
||||
configured_proxy_url = os.environ.get("OPENEO_PROXY_URL", DEFAULT_OPENEO_PROXY_URL).strip()
|
||||
if configured_proxy_url and configured_proxy_url != DEFAULT_OPENEO_PROXY_URL:
|
||||
return configured_proxy_url
|
||||
|
||||
# Keep openEO traffic proxied even when process-wide proxychains is disabled.
|
||||
derived_proxy_url = build_proxy_url_from_proxychains_env(require_enabled=False)
|
||||
if derived_proxy_url:
|
||||
return derived_proxy_url
|
||||
|
||||
return configured_proxy_url
|
||||
|
||||
|
||||
def is_openeo_auth_configured(settings: OpenEOConnectionSettings | None = None) -> bool:
|
||||
settings = settings or OpenEOConnectionSettings.from_env()
|
||||
|
||||
if settings.auth_method == "client_credentials":
|
||||
return bool(settings.client_id and settings.client_secret)
|
||||
if settings.auth_method == "password":
|
||||
return bool(settings.username and settings.password)
|
||||
if settings.auth_method == "oidc":
|
||||
return settings.allow_interactive_oidc
|
||||
return False
|
||||
|
||||
|
||||
def build_openeo_requests_session(settings: OpenEOConnectionSettings) -> requests.Session:
|
||||
session = TimeoutOverrideSession(settings.timeout_seconds)
|
||||
return apply_requests_proxy(session, settings.proxy_url)
|
||||
|
||||
|
||||
def connect_openeo(settings: OpenEOConnectionSettings | None = None):
|
||||
"""
|
||||
Build an authenticated openEO connection using environment-driven configuration.
|
||||
@@ -77,36 +130,98 @@ def connect_openeo(settings: OpenEOConnectionSettings | None = None):
|
||||
settings = settings or OpenEOConnectionSettings.from_env()
|
||||
try:
|
||||
import openeo
|
||||
from openeo.rest.auth.oidc import (
|
||||
OidcClientCredentialsAuthenticator,
|
||||
OidcClientInfo,
|
||||
OidcProviderInfo,
|
||||
OidcResourceOwnerPasswordAuthenticator,
|
||||
)
|
||||
except ImportError as exc: # pragma: no cover - runtime dependency guard
|
||||
raise OpenEOServiceError("The `openeo` Python client is required for remote sensing jobs.") from exc
|
||||
|
||||
connection = openeo.connect(settings.backend_url)
|
||||
session = build_openeo_requests_session(settings)
|
||||
connection = openeo.connect(
|
||||
settings.backend_url,
|
||||
session=session,
|
||||
default_timeout=settings.timeout_seconds,
|
||||
)
|
||||
|
||||
def resolve_oidc_context(
|
||||
provider_id: str | None,
|
||||
client_id: str | None,
|
||||
client_secret: str | None,
|
||||
) -> tuple[str, OidcClientInfo]:
|
||||
resolved_provider_id, _ = connection._get_oidc_provider(provider_id, parse_info=False)
|
||||
providers_payload = connection.get("/credentials/oidc", expected_status=200).json()
|
||||
provider_map = {provider["id"]: provider for provider in providers_payload["providers"]}
|
||||
provider_data = provider_map.get(resolved_provider_id)
|
||||
if not provider_data:
|
||||
raise OpenEOAuthenticationError(
|
||||
f"OIDC provider metadata for {resolved_provider_id!r} was not returned by the backend."
|
||||
)
|
||||
provider_info = OidcProviderInfo(
|
||||
provider_id=provider_data["id"],
|
||||
title=provider_data["title"],
|
||||
issuer=provider_data["issuer"],
|
||||
scopes=provider_data.get("scopes"),
|
||||
default_clients=provider_data.get("default_clients"),
|
||||
requests_session=session,
|
||||
)
|
||||
|
||||
if not client_id:
|
||||
raise OpenEOAuthenticationError(
|
||||
"OPENEO_AUTH_CLIENT_ID must be configured for this openEO auth flow."
|
||||
)
|
||||
|
||||
return resolved_provider_id, OidcClientInfo(
|
||||
client_id=client_id,
|
||||
client_secret=client_secret,
|
||||
provider=provider_info,
|
||||
)
|
||||
|
||||
try:
|
||||
if settings.auth_method == "client_credentials":
|
||||
if not settings.client_id or not settings.client_secret:
|
||||
raise OpenEOAuthenticationError(
|
||||
"OPENEO_AUTH_CLIENT_ID and OPENEO_AUTH_CLIENT_SECRET must be configured."
|
||||
)
|
||||
auth_kwargs = {
|
||||
"client_id": settings.client_id,
|
||||
"client_secret": settings.client_secret,
|
||||
}
|
||||
if settings.provider_id:
|
||||
auth_kwargs["provider_id"] = settings.provider_id
|
||||
return connection.authenticate_oidc_client_credentials(**auth_kwargs)
|
||||
provider_id, client_info = resolve_oidc_context(
|
||||
settings.provider_id or None,
|
||||
settings.client_id,
|
||||
settings.client_secret,
|
||||
)
|
||||
authenticator = OidcClientCredentialsAuthenticator(
|
||||
client_info=client_info,
|
||||
requests_session=session,
|
||||
)
|
||||
return connection._authenticate_oidc(
|
||||
authenticator,
|
||||
provider_id=provider_id,
|
||||
store_refresh_token=False,
|
||||
oidc_auth_renewer=authenticator,
|
||||
)
|
||||
|
||||
if settings.auth_method == "password":
|
||||
if not settings.username or not settings.password:
|
||||
raise OpenEOAuthenticationError(
|
||||
"OPENEO_USERNAME and OPENEO_PASSWORD must be configured for password auth."
|
||||
)
|
||||
auth_kwargs = {
|
||||
"username": settings.username,
|
||||
"password": settings.password,
|
||||
}
|
||||
if settings.provider_id:
|
||||
auth_kwargs["provider_id"] = settings.provider_id
|
||||
return connection.authenticate_oidc_resource_owner_password_credentials(**auth_kwargs)
|
||||
provider_id, client_info = resolve_oidc_context(
|
||||
settings.provider_id or None,
|
||||
settings.client_id or None,
|
||||
settings.client_secret or None,
|
||||
)
|
||||
authenticator = OidcResourceOwnerPasswordAuthenticator(
|
||||
client_info=client_info,
|
||||
username=settings.username,
|
||||
password=settings.password,
|
||||
requests_session=session,
|
||||
)
|
||||
return connection._authenticate_oidc(
|
||||
authenticator,
|
||||
provider_id=provider_id,
|
||||
store_refresh_token=False,
|
||||
)
|
||||
|
||||
if settings.auth_method == "oidc":
|
||||
if not settings.allow_interactive_oidc:
|
||||
|
||||
Reference in New Issue
Block a user