Skip to content

Commit e91f794

Browse files
Route pseudonym operations through PseudonymReferralService (#609)
- Add PseudonymReferralService, which resolves the pseudonym and calls ReferralService - Routers and LocalizationListService no longer resolve pseudonyms themselves - Remove the unused get_referral_service dependency
1 parent 1d1e932 commit e91f794

12 files changed

Lines changed: 228 additions & 91 deletions

File tree

‎app/container.py‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from app.services.fhir.bundle import BundleService
1616
from app.services.fhir.localization_list import LocalizationListService
1717
from app.services.key_info import KeyInfoService, KeyInfoServiceMock
18+
from app.services.pseudonym_referral_service import PseudonymReferralService
1819
from app.services.pseudonym_resolver import PseudonymResolver
1920
from app.services.referral_service import ReferralService
2021
from app.telemetry import create_meter_provider
@@ -55,11 +56,15 @@ def container_config(binder: inject.Binder) -> None:
5556
crypto_client=crypto_client,
5657
key_info_service=key_info_service,
5758
)
58-
binder.bind(PseudonymResolver, pseudonym_resolver)
59+
pseudonym_referral_service = PseudonymReferralService(
60+
referral_service=referral_service,
61+
pseudonym_resolver=pseudonym_resolver,
62+
)
63+
binder.bind(PseudonymReferralService, pseudonym_referral_service)
5964

6065
localization_list_service = LocalizationListService(
6166
referral_service=referral_service,
62-
pseudonym_resolver=pseudonym_resolver,
67+
pseudonym_referral_service=pseudonym_referral_service,
6368
default_page_size=config.pagination.default_page_size,
6469
max_page_size=config.pagination.max_page_size,
6570
)

‎app/dependencies.py‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
from app.services.fhir.bundle import BundleService
1010
from app.services.fhir.localization_list import LocalizationListService
1111
from app.services.key_info import KeyInfoService
12-
from app.services.pseudonym_resolver import PseudonymResolver
13-
from app.services.referral_service import ReferralService
12+
from app.services.pseudonym_referral_service import PseudonymReferralService
1413
from app.utils.load_capability_statement import CapabilityStatement
1514

1615

@@ -22,10 +21,6 @@ def get_database() -> Database:
2221
return inject.instance(Database)
2322

2423

25-
def get_referral_service() -> ReferralService:
26-
return inject.instance(ReferralService)
27-
28-
2924
def get_key_info_service() -> KeyInfoService:
3025
return inject.instance(KeyInfoService)
3126

@@ -34,8 +29,8 @@ def get_crypto_service_api_client() -> CryptoServiceApiClient:
3429
return inject.instance(CryptoServiceApiClient)
3530

3631

37-
def get_pseudonym_resolver() -> PseudonymResolver:
38-
return inject.instance(PseudonymResolver)
32+
def get_pseudonym_referral_service() -> PseudonymReferralService:
33+
return inject.instance(PseudonymReferralService)
3934

4035

4136
def get_auth_header_service() -> AuthHeaderService:

‎app/models/fhir/resources/localization_list/request.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
DEVICE_SYSTEM,
1010
PSEUDONYM_SYSTEM,
1111
)
12+
from app.models.pseudonym import PseudonymToken
1213

1314
logger = logging.getLogger(__name__)
1415

@@ -90,6 +91,9 @@ def empty(self) -> bool:
9091
def is_localize_params(self) -> bool:
9192
return self.subject is not None and self.source is None
9293

94+
def subject_token(self) -> PseudonymToken | None:
95+
return PseudonymToken(self.subject) if self.subject else None
96+
9397

9498
class LocalizationListSearchParams(LocalizationListParams):
9599
"""Search parameters for a List search, which also controls the page that is returned."""

‎app/routers/localize.py‎

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,22 @@
22

33
from fastapi import APIRouter, Body, Depends
44

5-
from app.dependencies import (
6-
get_pseudonym_resolver,
7-
get_referral_service,
8-
)
5+
from app.dependencies import get_pseudonym_referral_service
96
from app.models.auth.context import AuthContext
107
from app.models.auth.data import AuthorizationScope
118
from app.models.registrations import LocalizeRequest, Registration
129
from app.routers.dependencies import require_scope
13-
from app.services.pseudonym_resolver import PseudonymResolver
14-
from app.services.referral_service import ReferralService
10+
from app.services.pseudonym_referral_service import PseudonymReferralService
1511

1612
router = APIRouter(tags=["Localization"], prefix="/localize")
1713

1814

1915
@router.post("")
2016
def localize(
2117
data: Annotated[LocalizeRequest, Body()],
22-
referral_service: Annotated[ReferralService, Depends(get_referral_service)],
23-
pseudonym_resolver: Annotated[PseudonymResolver, Depends(get_pseudonym_resolver)],
18+
pseudonym_referral_service: Annotated[PseudonymReferralService, Depends(get_pseudonym_referral_service)],
2419
ctx: Annotated[AuthContext, Depends(require_scope(AuthorizationScope.LOCALIZE))],
2520
) -> Any:
26-
resolved = pseudonym_resolver.resolve(data.oprf_pseudonym())
27-
28-
_, results = referral_service.localize(resolved.encrypted, ctx.claims.ura_number)
21+
_, results = pseudonym_referral_service.localize(data.oprf_pseudonym(), ctx.claims.ura_number)
2922

3023
return [Registration.from_entity(r) for r in results]

‎app/routers/registrations.py‎

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
from fastapi import APIRouter, Body, Depends, Query, Response
44

5-
from app.dependencies import (
6-
get_pseudonym_resolver,
7-
get_referral_service,
8-
)
5+
from app.dependencies import get_pseudonym_referral_service
96
from app.models.auth.context import AuthContext
107
from app.models.auth.data import AuthorizationScope
118
from app.models.registrations import (
@@ -15,8 +12,7 @@
1512
RegistrationQueryParams,
1613
)
1714
from app.routers.dependencies import require_managing_source, require_scope
18-
from app.services.pseudonym_resolver import PseudonymResolver
19-
from app.services.referral_service import ReferralService
15+
from app.services.pseudonym_referral_service import PseudonymReferralService
2016

2117
router = APIRouter(tags=["Registrations"], prefix="/registrations")
2218

@@ -25,14 +21,11 @@
2521
"",
2622
)
2723
def get_registration(
28-
referral_service: Annotated[ReferralService, Depends(get_referral_service)],
29-
pseudonym_resolver: Annotated[PseudonymResolver, Depends(get_pseudonym_resolver)],
24+
pseudonym_referral_service: Annotated[PseudonymReferralService, Depends(get_pseudonym_referral_service)],
3025
params: Annotated[RegistrationQueryParams, Query()],
3126
ctx: Annotated[AuthContext, Depends(require_scope(AuthorizationScope.READ))],
3227
) -> Any:
33-
resolved = pseudonym_resolver.resolve(params.oprf_pseudonym())
34-
35-
_, results = referral_service.query(ctx.claims.ura_number, encrypted_pseudonym=resolved.encrypted)
28+
_, results = pseudonym_referral_service.query(ctx.claims.ura_number, pseudonym=params.oprf_pseudonym())
3629

3730
return RegistrationList.from_entities(results)
3831

@@ -45,18 +38,14 @@ def get_registration(
4538
def add_registration(
4639
data: Annotated[CreateRegistrationRequest, Body()],
4740
response: Response,
48-
referral_service: Annotated[ReferralService, Depends(get_referral_service)],
49-
pseudonym_resolver: Annotated[PseudonymResolver, Depends(get_pseudonym_resolver)],
41+
pseudonym_referral_service: Annotated[PseudonymReferralService, Depends(get_pseudonym_referral_service)],
5042
ctx: Annotated[AuthContext, Depends(require_scope(AuthorizationScope.CREATE))],
5143
source: Annotated[str, Depends(require_managing_source)],
5244
) -> Any:
53-
resolved = pseudonym_resolver.resolve(data.oprf_pseudonym())
54-
55-
referral, created = referral_service.add_one(
56-
encrypted_pseudonym=resolved.encrypted,
45+
referral, created = pseudonym_referral_service.register(
46+
data.oprf_pseudonym(),
5747
ura_number=ctx.claims.ura_number,
5848
source=source,
59-
key_id=resolved.key_id,
6049
)
6150
if not created:
6251
response.status_code = 200
@@ -66,16 +55,13 @@ def add_registration(
6655
@router.delete("")
6756
def delete_registration(
6857
params: Annotated[RegistrationQueryParams, Query()],
69-
referral_service: Annotated[ReferralService, Depends(get_referral_service)],
70-
pseudonym_resolver: Annotated[PseudonymResolver, Depends(get_pseudonym_resolver)],
58+
pseudonym_referral_service: Annotated[PseudonymReferralService, Depends(get_pseudonym_referral_service)],
7159
ctx: Annotated[AuthContext, Depends(require_scope(AuthorizationScope.DELETE))],
7260
source: Annotated[str, Depends(require_managing_source)],
7361
) -> Any:
74-
resolved = pseudonym_resolver.resolve(params.oprf_pseudonym())
75-
76-
referral_service.delete_many(
77-
ura_number=ctx.claims.ura_number,
78-
encrypted_pseudonym=resolved.encrypted,
62+
pseudonym_referral_service.delete_many(
63+
ctx.claims.ura_number,
64+
pseudonym=params.oprf_pseudonym(),
7965
source=source,
8066
)
8167

‎app/services/fhir/localization_list.py‎

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from app.models.pseudonym import PseudonymToken
1818
from app.models.ura import UraNumber
1919
from app.services.exceptions import UnauthorizedUraError
20-
from app.services.pseudonym_resolver import PseudonymResolver, ResolvedPseudonym
20+
from app.services.pseudonym_referral_service import PseudonymReferralService
2121
from app.services.referral_service import ReferralService
2222

2323
logger = logging.getLogger(__name__)
@@ -27,12 +27,12 @@ class LocalizationListService:
2727
def __init__(
2828
self,
2929
referral_service: ReferralService,
30-
pseudonym_resolver: PseudonymResolver,
30+
pseudonym_referral_service: PseudonymReferralService,
3131
default_page_size: int = 50,
3232
max_page_size: int = 100,
3333
) -> None:
3434
self.referral_service = referral_service
35-
self._pseudonym_resolver = pseudonym_resolver
35+
self.pseudonym_referral_service = pseudonym_referral_service
3636
self.default_page_size = default_page_size
3737
self.max_page_size = max_page_size
3838

@@ -54,13 +54,10 @@ def create(
5454
)
5555
raise UnauthorizedUraError("Registration not linked to the authorized URA")
5656

57-
resolved = self._pseudonym_resolver.resolve(PseudonymToken(data.get_encoded_pseudonym()))
58-
59-
referral, created = self.referral_service.add_one(
57+
referral, created = self.pseudonym_referral_service.register(
58+
PseudonymToken(data.get_encoded_pseudonym()),
6059
ura_number=ura_number,
61-
encrypted_pseudonym=resolved.encrypted,
6260
source=device,
63-
key_id=resolved.key_id,
6461
)
6562

6663
return LocalizationList.from_referral(referral), created
@@ -79,23 +76,16 @@ def query(
7976
8077
When `request_url` is given, the bundle links to the current, next and previous page.
8178
"""
82-
resolved: ResolvedPseudonym | None = None
83-
if params.subject:
84-
resolved = self._pseudonym_resolver.resolve(PseudonymToken(params.subject))
85-
79+
subject = params.subject_token()
8680
page_size = min(params.count or self.default_page_size, self.max_page_size)
8781

88-
if params.is_localize_params() and resolved is not None:
89-
total, referrals = self.referral_service.localize(
90-
resolved.encrypted, authenticated_ura, limit=page_size, offset=params.offset
82+
if params.is_localize_params() and subject is not None:
83+
total, referrals = self.pseudonym_referral_service.localize(
84+
subject, authenticated_ura, limit=page_size, offset=params.offset
9185
)
9286
else:
93-
total, referrals = self.referral_service.query(
94-
authenticated_ura,
95-
encrypted_pseudonym=(resolved.encrypted if resolved else None),
96-
source=params.source,
97-
limit=page_size,
98-
offset=params.offset,
87+
total, referrals = self.pseudonym_referral_service.query(
88+
authenticated_ura, pseudonym=subject, source=params.source, limit=page_size, offset=params.offset
9989
)
10090

10191
bundle = Bundle(
@@ -146,14 +136,10 @@ def delete_by_query(
146136
authenticated_ura: UraNumber,
147137
source: str,
148138
) -> tuple[OperationOutcome, int]:
149-
resolved: ResolvedPseudonym | None = None
150-
if params.subject:
151-
resolved = self._pseudonym_resolver.resolve(PseudonymToken(params.subject))
152-
153-
deleted_count = self.referral_service.delete_many(
154-
encrypted_pseudonym=(resolved.encrypted if resolved else None),
139+
deleted_count = self.pseudonym_referral_service.delete_many(
140+
authenticated_ura,
141+
pseudonym=params.subject_token(),
155142
source=source,
156-
ura_number=authenticated_ura,
157143
)
158144

159145
if deleted_count < 1:
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from collections.abc import Sequence
2+
3+
from app.db.models.referral import ReferralEntity
4+
from app.models.pseudonym import BlindedPseudonym, EncryptedPseudonym
5+
from app.models.ura import UraNumber
6+
from app.services.pseudonym_resolver import PseudonymResolver
7+
from app.services.referral_service import ReferralService
8+
9+
10+
class PseudonymReferralService:
11+
def __init__(self, referral_service: ReferralService, pseudonym_resolver: PseudonymResolver) -> None:
12+
self._referral_service = referral_service
13+
self._pseudonym_resolver = pseudonym_resolver
14+
15+
def register(
16+
self,
17+
pseudonym: BlindedPseudonym,
18+
ura_number: UraNumber,
19+
source: str,
20+
) -> tuple[ReferralEntity, bool]:
21+
resolved = self._pseudonym_resolver.resolve(pseudonym)
22+
return self._referral_service.add_one(resolved.encrypted, ura_number, source, resolved.key_id)
23+
24+
def localize(
25+
self,
26+
pseudonym: BlindedPseudonym,
27+
requesting_ura: UraNumber,
28+
limit: int | None = None,
29+
offset: int = 0,
30+
) -> tuple[int, Sequence[ReferralEntity]]:
31+
encrypted = self._resolve_encrypted(pseudonym)
32+
return self._referral_service.localize(encrypted, requesting_ura, limit=limit, offset=offset)
33+
34+
def query(
35+
self,
36+
ura_number: UraNumber,
37+
pseudonym: BlindedPseudonym | None = None,
38+
source: str | None = None,
39+
limit: int | None = None,
40+
offset: int = 0,
41+
) -> tuple[int, Sequence[ReferralEntity]]:
42+
encrypted = self._resolve_encrypted(pseudonym) if pseudonym else None
43+
return self._referral_service.query(
44+
ura_number, encrypted_pseudonym=encrypted, source=source, limit=limit, offset=offset
45+
)
46+
47+
def delete_many(
48+
self,
49+
ura_number: UraNumber,
50+
pseudonym: BlindedPseudonym | None = None,
51+
source: str | None = None,
52+
) -> int:
53+
encrypted = self._resolve_encrypted(pseudonym) if pseudonym else None
54+
return self._referral_service.delete_many(ura_number, encrypted_pseudonym=encrypted, source=source)
55+
56+
def _resolve_encrypted(self, pseudonym: BlindedPseudonym) -> EncryptedPseudonym:
57+
return self._pseudonym_resolver.resolve(pseudonym).encrypted

‎tests/conftest.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from app.db.models.referral import ReferralEntity
1616
from app.db.repository.key_info_repository import KeyInfoRepository
1717
from app.db.repository.referral_repository import ReferralRepository
18+
from app.debug.crypto_service_api_client_mock import CryptoServiceApiClientMock
1819
from app.logging.context import CLAIM_FIELDS
1920
from app.logging.events import Log
2021
from app.metrics import UsageMetrics
@@ -39,6 +40,8 @@
3940
from app.services.auth.header import AuthHeaderService
4041
from app.services.http import HttpService
4142
from app.services.key_info import KeyInfoService
43+
from app.services.pseudonym_referral_service import PseudonymReferralService
44+
from app.services.pseudonym_resolver import PseudonymResolver
4245
from app.services.referral_service import ReferralService
4346
from tests.test_config import get_test_config
4447

@@ -197,6 +200,14 @@ def key_info_service(database: Database) -> KeyInfoService:
197200
return KeyInfoService(database)
198201

199202

203+
@pytest.fixture
204+
def pseudonym_referral_service(
205+
referral_service: ReferralService, key_info_service: KeyInfoService
206+
) -> PseudonymReferralService:
207+
resolver = PseudonymResolver(crypto_client=CryptoServiceApiClientMock(), key_info_service=key_info_service)
208+
return PseudonymReferralService(referral_service=referral_service, pseudonym_resolver=resolver)
209+
210+
200211
@pytest.fixture()
201212
def auth_header_service() -> AuthHeaderService:
202213
config = get_test_config()

0 commit comments

Comments
 (0)