Skip to content

Commit d186929

Browse files
committed
Raise PseudonymExchangeError when the crypto service rejects a pseudonym
1 parent bd4146b commit d186929

8 files changed

Lines changed: 25 additions & 8 deletions

File tree

‎app/errors/mapping.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
InvalidModelError,
99
NotFoundError,
1010
PseudonymError,
11+
PseudonymExchangeError,
1112
UnauthorizedError,
1213
)
1314

@@ -32,6 +33,7 @@ class ErrorSpec(BaseModel):
3233
InvalidModelError: ErrorSpec(severity="error", fhir_code="structure", http_status=400),
3334
InvalidHeaderPropertyError: ErrorSpec(severity="error", fhir_code="invalid", http_status=401),
3435
PseudonymError: ErrorSpec(severity="error", fhir_code="invalid", http_status=422),
36+
PseudonymExchangeError: ErrorSpec(severity="error", fhir_code="invalid", http_status=400, log=True),
3537
ValueError: ErrorSpec(severity="error", fhir_code="invalid", http_status=400, log=True),
3638
}
3739

‎app/services/crypto_service_api_client.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from app.config import ConfigCryptoServiceApi
77
from app.models.pseudonym import OprfPseudonym, PseudonymResponse
8+
from app.services.exceptions import PseudonymExchangeError
89
from app.services.http import HttpService
910

1011
logger = logging.getLogger(__name__)
@@ -38,7 +39,7 @@ def exchange(self, pseudonym: OprfPseudonym, label: str, mechanism: str) -> Pseu
3839
logger.exception("Error during request to Crypto Service API")
3940
raise ConnectionError("Failed to connect to the Crypto Service API")
4041
except HTTPError:
41-
raise ValueError("Invalid pseudonym or oprf_key")
42+
raise PseudonymExchangeError("Invalid pseudonym or oprf_key")
4243
except (JSONDecodeError, KeyError, ValueError):
4344
logger.exception("Unexpected response from Crypto Service API")
4445
raise RuntimeError("Unexpected response from the Crypto Service API")

‎app/services/exceptions.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,7 @@ class PseudonymError(Exception):
6969
def __init__(self, msg: str | None = None) -> None:
7070
error_msg = "Error occurred while decoding pseudonym" if msg is None else msg
7171
super().__init__(error_msg)
72+
73+
74+
class PseudonymExchangeError(Exception):
75+
pass

‎tests/errors/test_mapping.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
InvalidModelError,
1111
NotFoundError,
1212
PseudonymError,
13+
PseudonymExchangeError,
1314
UnauthorizedManagingRequestError,
1415
UnauthorizedScopeError,
1516
UnauthorizedUraError,
@@ -34,6 +35,7 @@ def _scope_error() -> UnauthorizedScopeError:
3435
pytest.param(InvalidHeaderPropertyError("aud", "x"), _spec("invalid", 401), id="invalid-header-property"),
3536
pytest.param(PseudonymError(), _spec("invalid", 422), id="pseudonym"),
3637
pytest.param(ValueError("boom"), _spec("invalid", 400, log=True), id="value-error"),
38+
pytest.param(PseudonymExchangeError("rejected"), _spec("invalid", 400, log=True), id="pseudonym-exchange"),
3739
pytest.param(InvalidModelError("bad"), _spec("structure", 400), id="invalid-model-outranks-value-error"),
3840
pytest.param(_scope_error(), _spec("security", 403), id="unauthorized-scope-via-mro"),
3941
pytest.param(UnauthorizedManagingRequestError(), _spec("security", 403), id="unauthorized-managing-via-mro"),
@@ -50,6 +52,7 @@ def test_spec_for_resolves_the_expected_spec(exc: Exception, expected: ErrorSpec
5052
[
5153
pytest.param(PseudonymError(), False, id="pseudonym"),
5254
pytest.param(ValueError("boom"), True, id="value-error"),
55+
pytest.param(PseudonymExchangeError("rejected"), True, id="pseudonym-exchange"),
5356
pytest.param(RuntimeError("unexpected"), True, id="unmapped"),
5457
pytest.param(NotFoundError(), False, id="not-found"),
5558
pytest.param(_scope_error(), False, id="unauthorized-scope"),

‎tests/routers/test_localize.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from app.models.auth.context import AuthContext, AuthenticationClaims
1010
from app.models.auth.data import AuthorizationScope
1111
from app.models.ura import UraNumber
12+
from app.services.exceptions import PseudonymExchangeError
1213
from app.services.key_info import KeyInfoService
1314
from app.services.referral_service import ReferralService
1415
from tests.routers.conftest import (
@@ -113,7 +114,9 @@ def test_logs_a_failed_localization_with_the_callers_claims(
113114
monkeypatch: pytest.MonkeyPatch,
114115
) -> None:
115116
key_info_service.add_one("nvi-label", "AES_CBC")
116-
monkeypatch.setattr(crypto_client, "exchange", Mock(side_effect=ValueError("Invalid pseudonym or oprf_key")))
117+
monkeypatch.setattr(
118+
crypto_client, "exchange", Mock(side_effect=PseudonymExchangeError("Invalid pseudonym or oprf_key"))
119+
)
117120

118121
with capture_records() as records:
119122
response = localize_client.post("/localize", json={"pseudonym": "pseu", "oprf_key": "k"})

‎tests/routers/test_registrations.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from app.debug.crypto_service_api_client_mock import CryptoServiceApiClientMock
88
from app.logging.events import Log
99
from app.models.auth.data import AuthorizationScope
10+
from app.services.exceptions import PseudonymExchangeError
1011
from app.services.key_info import KeyInfoService
1112
from app.services.referral_service import ReferralService
1213
from tests.routers.conftest import (
@@ -91,7 +92,9 @@ def test_logs_a_failed_registration_with_the_callers_claims(
9192
monkeypatch: pytest.MonkeyPatch,
9293
) -> None:
9394
key_info_service.add_one("nvi-label", mechanism="AES_CBC")
94-
monkeypatch.setattr(crypto_client, "exchange", Mock(side_effect=ValueError("Invalid pseudonym or oprf_key")))
95+
monkeypatch.setattr(
96+
crypto_client, "exchange", Mock(side_effect=PseudonymExchangeError("Invalid pseudonym or oprf_key"))
97+
)
9598

9699
with capture_records() as records:
97100
response = source_client.post("/registrations", json={"pseudonym": "pseu", "oprf_key": "key1"})

‎tests/services/test_crypto_service_api_client.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from app.models.pseudonym import OprfPseudonym, PseudonymResponse
88
from app.services.crypto_service_api_client import CryptoServiceApiClient
9+
from app.services.exceptions import PseudonymExchangeError
910

1011
PSEUDONYM = OprfPseudonym(pseudonym="jwe", oprf_key="bf")
1112

@@ -82,7 +83,7 @@ def test_exchange_raises_on_missing_key(crypto_client: CryptoServiceApiClient, h
8283
def test_exchange_raises_on_http_error(crypto_client: CryptoServiceApiClient, http_mock: MagicMock) -> None:
8384
http_mock.do_request.side_effect = HTTPError("500")
8485

85-
with pytest.raises(ValueError):
86+
with pytest.raises(PseudonymExchangeError, match="Invalid pseudonym or oprf_key"):
8687
crypto_client.exchange(PSEUDONYM, "some-label", "CBC_AES")
8788

8889

‎tests/services/test_pseudonym_referral_service.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from app.logging.events import Log
99
from app.models.pseudonym import OprfPseudonym, PseudonymResponse
1010
from app.models.ura import UraNumber
11-
from app.services.exceptions import InvalidKeyInfoError
11+
from app.services.exceptions import InvalidKeyInfoError, PseudonymExchangeError
1212
from app.services.key_info import KeyInfoService
1313
from app.services.pseudonym_referral_service import PseudonymReferralService
1414
from app.services.pseudonym_resolver import PseudonymResolver
@@ -28,7 +28,7 @@ def exchange(self, pseudonym: OprfPseudonym, label: str, mechanism: str) -> Pseu
2828

2929
class RejectingCryptoClient(CryptoServiceApiClientMock):
3030
def exchange(self, pseudonym: OprfPseudonym, label: str, mechanism: str) -> PseudonymResponse:
31-
raise ValueError("Invalid pseudonym or oprf_key")
31+
raise PseudonymExchangeError("Invalid pseudonym or oprf_key")
3232

3333

3434
def logged_fields(caplog: pytest.LogCaptureFixture, event: LogEvent) -> list[dict[str, Any]]:
@@ -72,7 +72,7 @@ def test_register_logs_a_failure_when_the_pseudonym_is_invalid(
7272
ura_number: UraNumber,
7373
caplog: pytest.LogCaptureFixture,
7474
) -> None:
75-
with caplog.at_level(logging.INFO), pytest.raises(ValueError):
75+
with caplog.at_level(logging.INFO), pytest.raises(PseudonymExchangeError):
7676
rejecting_service.register(PSEUDONYM, ura_number, SOURCE)
7777

7878
(fields,) = logged_fields(caplog, Log.REFERRAL_REGISTRATION_FAILED)
@@ -107,7 +107,7 @@ def test_localize_logs_a_failure_when_the_pseudonym_is_invalid(
107107
rejecting_service: PseudonymReferralService,
108108
caplog: pytest.LogCaptureFixture,
109109
) -> None:
110-
with caplog.at_level(logging.INFO), pytest.raises(ValueError):
110+
with caplog.at_level(logging.INFO), pytest.raises(PseudonymExchangeError):
111111
rejecting_service.localize(PSEUDONYM, REQUESTING_URA)
112112

113113
(fields,) = logged_fields(caplog, Log.LOCALIZATION_FAILED)

0 commit comments

Comments
 (0)