Commit dc5c9819 authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

feat: add error handling for provisioner credential rejection and update API client routes

parent ee41ec51
Loading
Loading
Loading
Loading
+1 −1
Changes for portal-gui/src/features/api-clients/api-clients.tsx: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -48,7 +48,7 @@ export function ApiClientsPage() {
          />
        ) : (
          <>
            <ApiClientsNotice />
            {unavailable === false && <ApiClientsNotice />}
            <PageState
              loading={loading}
              error={error}
+7 −0
Changes for src/portal_service/adapters/keycloak/provisioner_token.py: 7 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -4,8 +4,12 @@ import time
from collections.abc import Callable

import httpx
import structlog

from portal_service.adapters.keycloak import _http
from portal_service.domain.errors import ProvisionerRejectedError

logger: structlog.BoundLogger = structlog.get_logger(__name__)

_RENEW_BEFORE_EXPIRY_SECONDS = 30
_WHAT = "Provisioner token request"
@@ -45,6 +49,9 @@ class ProvisionerToken:
                    "client_secret": self._client_secret,
                },
            )
            if response.status_code in (400, 401):
                logger.error("provisioner credentials rejected", status=response.status_code)
                raise ProvisionerRejectedError("Keycloak rejected the provisioner credentials")
            _http.ensure_ok(response, what=_WHAT)
            parsed = _http.parse(_http.TokenResponse, response, what=_WHAT)
            self._token = parsed.access_token
+6 −0
Changes for src/portal_service/api/api_clients/routes.py: 6 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ from portal_service.domain.errors import (
    CreatorNotAllowedError,
    IdentityProviderUnavailableError,
    ProductNotHeldError,
    ProvisionerRejectedError,
)

api_clients_router = APIRouter(prefix="/api/api-clients", tags=["api-clients"])
@@ -46,6 +47,11 @@ def _answering_refusals() -> Iterator[None]:
        yield
    except tuple(_REFUSALS) as exc:
        raise HTTPException(_REFUSALS[type(exc)], str(exc)) from exc
    except ProvisionerRejectedError as exc:
        raise HTTPException(
            status.HTTP_503_SERVICE_UNAVAILABLE,
            "API client provisioning is misconfigured: ask the operator to check it",
        ) from exc
    except IdentityProviderUnavailableError as exc:
        raise HTTPException(
            status.HTTP_503_SERVICE_UNAVAILABLE, "The identity provider is unavailable"
+4 −2
Changes for src/portal_service/api/dependencies.py: 4 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -269,10 +269,11 @@ def get_api_client_service(
ApiClientServiceDep = Annotated[ApiClientService, Depends(get_api_client_service)]


def get_dev_user() -> TokenClaims:
def get_dev_user(catalog: ApiProductsDep) -> TokenClaims:
    """
    Stands in for `get_current_user` when AUTH__SECURITY_MODE=none: every request
    acts as this user, with every role, and no login or Keycloak is needed.
    acts as this user, with every role and every API product, and no login or
    Keycloak is needed.
    """
    return TokenClaims(
        sub="dev-user",
@@ -280,5 +281,6 @@ def get_dev_user() -> TokenClaims:
        exp=0,
        iat=0,
        preferred_username="dev",
        scope=" ".join(sorted(catalog.scopes_for(catalog.names))),
        realm_access={"roles": [role.value for role in Role]},
    )
+5 −0
Changes for src/portal_service/domain/errors.py: 5 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -40,6 +40,11 @@ class IdentityProviderUnavailableError(Exception):
    pass


# Keycloak refused the portal's own provisioner credentials: a setting is wrong, not Keycloak down.
class ProvisionerRejectedError(Exception):
    pass


# Keycloak refused a client's registration access token: the client was deleted, disabled in
# the console, or its token was replaced. Keycloak does not say which.
class RegistrationTokenRejectedError(Exception):
Loading