diff --git a/.gitignore b/.gitignore index 5133b408045b4490147ccf3ca8c43db90d1fa667..fccb8082923cbdf2088055e1489862884abd02c3 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ docs/build docs/vale/styles/* !docs/vale/styles/config/ !docs/vale/styles/custom/ +.DS_Store .idea .venv diff --git a/docs/source/module/auth/index.rst b/docs/source/module/auth/index.rst index 0ec5cd1fad1966607dbfaf3797235286bd08503c..5d818a853cd02ffe3b7e4ca1a8b43528c2c2a003 100644 --- a/docs/source/module/auth/index.rst +++ b/docs/source/module/auth/index.rst @@ -1,5 +1,5 @@ -``gso.products`` -================ +``gso.auth`` +============ .. automodule:: gso.auth :members: diff --git a/docs/source/modules.rst b/docs/source/modules.rst index 36fb6ff1775e08a69e3a706bb289e146b8ff99d7..ab1adef70f18ef8a4021143bd71d53bd3eae4f86 100644 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -19,6 +19,7 @@ Subpackages :titlesonly: module/api/index + module/auth/index module/cli/index module/products/index module/schedules/index diff --git a/gso/auth/oidc_policy_helper.py b/gso/auth/oidc_policy_helper.py index 945b7496d36f457574140dd191437af179b95f95..3d384b5284e9df74fbca67cd5e4747f19b6399ae 100644 --- a/gso/auth/oidc_policy_helper.py +++ b/gso/auth/oidc_policy_helper.py @@ -16,8 +16,9 @@ from json import JSONDecodeError from typing import Any, ClassVar, cast from fastapi.exceptions import HTTPException -from fastapi.param_functions import Depends +from fastapi.param_functions import Depends, Security from fastapi.requests import Request +from fastapi.security import APIKeyHeader from fastapi.security.http import HTTPBearer from httpx import AsyncClient, NetworkError from pydantic import BaseModel @@ -30,6 +31,18 @@ logger = get_logger(__name__) HTTPX_SSL_CONTEXT = ssl.create_default_context() # https://github.com/encode/httpx/issues/838 +_CALLBACK_STEP_API_URL_PATTERN = re.compile( + ( + r"^/api/processes/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})" + r"/callback/([0-9a-zA-Z\-_]+)$" + ) +) + + +def _is_callback_step_endpoint(request: Request) -> bool: + """Check if the request is a callback step API call.""" + return re.match(_CALLBACK_STEP_API_URL_PATTERN, request.url.path) is not None + class InvalidScopeValueError(ValueError): """Exception raised for invalid scope values in OIDC.""" @@ -176,13 +189,13 @@ class OIDCUser(HTTPBearer): resource_server_secret: str def __init__( - self, - openid_url: str, - resource_server_id: str, - resource_server_secret: str, - *, - auto_error: bool = True, - scheme_name: str | None = None, + self, + openid_url: str, + resource_server_id: str, + resource_server_secret: str, + *, + auto_error: bool = True, + scheme_name: str | None = None, ): """Set up OIDCUser with specified OpenID Connect configurations and credentials.""" super().__init__(auto_error=auto_error) @@ -192,7 +205,7 @@ class OIDCUser(HTTPBearer): self.scheme_name = scheme_name or self.__class__.__name__ async def __call__( # type: ignore[override] - self, request: Request, token: str | None = None + self, request: Request, token: str | None = None ) -> OIDCUserModel | None: """Return the OIDC user from OIDC introspect endpoint. @@ -219,6 +232,10 @@ class OIDCUser(HTTPBearer): if not credentials: return None token = credentials.credentials + elif _is_callback_step_endpoint(request): + logger.debug("callback step endpoint is called. verification will be done by endpoint itself.", + url=request.url) + return None intercepted_token = await self.introspect_token(async_request, token) @@ -354,11 +371,11 @@ def _evaluate_decision(decision: OPAResult, *, auto_error: bool, **context: dict def opa_decision( - opa_url: str, - oidc_security: OIDCUser, - *, - auto_error: bool = True, - opa_kwargs: Mapping[str, str] | None = None, + opa_url: str, + oidc_security: OIDCUser, + *, + auto_error: bool = True, + opa_kwargs: Mapping[str, str] | None = None, ) -> Callable[[Request, OIDCUserModel, AsyncClient], Awaitable[bool | None]]: """Create a decision function for Open Policy Agent (OPA) authorization checks. @@ -379,9 +396,9 @@ def opa_decision( """ async def _opa_decision( - request: Request, - user_info: OIDCUserModel = Depends(oidc_security), # noqa: B008 - async_request: AsyncClient = Depends(_make_async_client), # noqa: B008 + request: Request, + user_info: OIDCUserModel = Depends(oidc_security), # noqa: B008 + async_request: AsyncClient = Depends(_make_async_client), # noqa: B008 ) -> bool | None: """Check OIDCUserModel against the OPA policy. @@ -394,6 +411,7 @@ def opa_decision( user_info: The OIDCUserModel object that will be checked async_request: The :term:`httpx` client. """ + return None if not (oauth2lib_settings.OAUTH2_ACTIVE and oauth2lib_settings.OAUTH2_AUTHORIZATION_ACTIVE): return None