Commit 632c0749 authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

feat: add handling for RegistrationTokenRejectedError in API client deletion and update tests

parent f4f5ff02
Loading
Loading
Loading
Loading
Loading
+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
@@ -27,6 +27,7 @@ from portal_service.domain.errors import (
    IdentityProviderUnavailableError,
    ProductNotHeldError,
    ProvisionerRejectedError,
    RegistrationTokenRejectedError,
)

api_clients_router = APIRouter(prefix="/api/api-clients", tags=["api-clients"])
@@ -52,6 +53,11 @@ def _answering_refusals() -> Iterator[None]:
            status.HTTP_503_SERVICE_UNAVAILABLE,
            "API client provisioning is misconfigured: ask the operator to check it",
        ) from exc
    except RegistrationTokenRejectedError as exc:
        raise HTTPException(
            status.HTTP_503_SERVICE_UNAVAILABLE,
            "The API client could not be deleted. Ask an administrator to check it.",
        ) from exc
    except IdentityProviderUnavailableError as exc:
        raise HTTPException(
            status.HTTP_503_SERVICE_UNAVAILABLE,
+0 −7
Changes for src/portal_service/application/api_client_service.py: 0 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ from portal_service.domain.errors import (
    ApiClientNotFoundError,
    CreatorNotAllowedError,
    ProductNotHeldError,
    RegistrationTokenRejectedError,
)
from portal_service.domain.models import (
    ApiClient,
@@ -136,13 +135,7 @@ class ApiClientService:
            raise ApiClientNotFoundError("This API client no longer exists")

        token = decrypt(api_client.registration_token, self._encryption_key).decode()
        try:
        await self._registry.delete(api_client.keycloak_client_id, token)
        except RegistrationTokenRejectedError:
            # Disabled or removed in the Keycloak console: nothing more can be done there.
            logger.warning(
                "keycloak client left behind", keycloak_client_id=api_client.keycloak_client_id
            )

        now = datetime.now(UTC)
        for entitlement in await self._entitlements.list_by_client(api_client.id):
+20 −1
Changes for tests/unit/test_api_client_routes.py: 20 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -8,7 +8,11 @@ from fastapi import FastAPI
from fastapi.testclient import TestClient

from portal_service.api.dependencies import get_api_client_service, get_current_user
from portal_service.domain.errors import IdentityProviderUnavailableError, ProvisionerRejectedError
from portal_service.domain.errors import (
    IdentityProviderUnavailableError,
    ProvisionerRejectedError,
    RegistrationTokenRejectedError,
)
from portal_service.domain.models import TokenClaims
from tests.api_clients import PROVISIONING_ENV, Setup
from tests.conftest import _apply_env
@@ -93,6 +97,21 @@ def test_delete_answers_204_and_the_client_leaves_the_list(client: TestClient) -
    assert client.get("/api/api-clients").json() == []


def test_rejected_deletion_is_a_503_and_the_client_stays_listed(
    client: TestClient, s: Setup
) -> None:
    created = client.post("/api/api-clients", json=BODY).json()
    s.registry.delete_error = RegistrationTokenRejectedError("token replaced in the console")

    response = client.delete(f"/api/api-clients/{created['id']}")

    assert response.status_code == 503
    assert "administrator" in response.json()["detail"]
    assert client.get("/api/api-clients").json() == [
        {key: value for key, value in created.items() if key != "client_secret"}
    ]


def test_someone_elses_client_looks_like_no_client(
    client: TestClient, app: FastAPI, s: Setup
) -> None:
+5 −4
Changes for tests/unit/test_api_client_service.py: 5 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -204,14 +204,15 @@ async def test_delete_refuses_unknown_foreign_and_already_deleted_clients(s: Set
    assert s.registry.deleted == [created.keycloak_client_id]


async def test_delete_still_succeeds_when_keycloak_rejects_the_token(s: Setup) -> None:
async def test_delete_changes_nothing_when_keycloak_rejects_the_token(s: Setup) -> None:
    created = await _create(s)
    s.registry.delete_error = RegistrationTokenRejectedError("disabled in the console")
    s.registry.delete_error = RegistrationTokenRejectedError("token replaced in the console")

    with pytest.raises(RegistrationTokenRejectedError):
        await s.service.delete(creator="maria", api_client_id=created.id)

    assert (await _stored(s, created.id)).deleted_at is not None
    assert s.entitlements.rows[0].state is EntitlementState.REVOKED
    assert (await _stored(s, created.id)).deleted_at is None
    assert s.entitlements.rows[0].state is EntitlementState.APPROVED


async def test_delete_changes_nothing_when_keycloak_is_unavailable(s: Setup) -> None: