Loading src/federation_manager/adapters/database/partner_repo.py +1 −0 Original line number Diff line number Diff line Loading @@ -22,4 +22,5 @@ class PostgresPartnerRepo: our_client_id=row.our_client_id, our_client_secret_ref=row.our_client_secret_ref, token_endpoint=row.token_endpoint, base_url=row.base_url, ) src/federation_manager/adapters/database/tables.py +1 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ partner_ops = Table( Column("id", PGUUID(as_uuid=True), primary_key=True, server_default=func.gen_random_uuid()), Column("mcc_mnc", String(10), nullable=False, unique=True), Column("oauth2_client_id", String(255), nullable=False, unique=True), Column("base_url", Text, nullable=False), Column("our_client_id", String(255)), Column("our_client_secret_ref", Text), Column("token_endpoint", Text), Loading src/federation_manager/adapters/http/__init__.py 0 → 100644 +0 −0 Empty file added. src/federation_manager/adapters/http/ewbi_client.py 0 → 100644 +68 −0 Original line number Diff line number Diff line from uuid import UUID import httpx from federation_manager.domain.errors import ( PartnerEndpointConfigurationError, PartnerRequestFailed, ) from federation_manager.domain.models import EwbiResponse, PartnerOP from federation_manager.domain.ports import PartnerTokenProviderPort class HttpxEwbiClient: def __init__( self, client: httpx.AsyncClient, token_provider: PartnerTokenProviderPort, ) -> None: self._client = client self._token_provider = token_provider async def post( self, partner: PartnerOP, path: str, payload: dict[str, object], federation_correlation_id: UUID, ) -> EwbiResponse: url = self._url(partner, path) token = await self._token_provider.token_for(partner, scope="fed-mgmt") try: response = await self._client.post( url, headers={ "Accept": "application/json", "Authorization": f"Bearer {token}", "X-Federation-Correlation-ID": str(federation_correlation_id), }, json=payload, ) except httpx.HTTPError: raise PartnerRequestFailed(partner.id) from None if not response.content: body: object | None = None else: try: body = response.json() except ValueError: raise PartnerRequestFailed(partner.id) from None return EwbiResponse(status_code=response.status_code, body=body) @staticmethod def _url(partner: PartnerOP, path: str) -> str: if not path.startswith("/") or httpx.URL(path).is_absolute_url: raise ValueError("EWBI path must be an absolute path without a host") if not partner.base_url: raise PartnerEndpointConfigurationError(partner.id) try: base_url = httpx.URL(partner.base_url) except httpx.InvalidURL: raise PartnerEndpointConfigurationError(partner.id) from None if base_url.scheme != "https" or not base_url.host: raise PartnerEndpointConfigurationError(partner.id) return f"{partner.base_url.rstrip('/')}/{path.lstrip('/')}" src/federation_manager/dependencies.py +6 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from federation_manager.adapters.database.partner_repo import PostgresPartnerRepo from federation_manager.application.authentication import PartnerAuthenticator from federation_manager.domain.ports import ( EwbiClientPort, JwtValidatorPort, PartnerRepositoryPort, PartnerTokenProviderPort, Loading Loading @@ -34,6 +35,11 @@ def get_partner_token_provider(request: Request) -> PartnerTokenProviderPort: return provider def get_ewbi_client(request: Request) -> EwbiClientPort: client: EwbiClientPort = request.app.state.ewbi_client return client def get_partner_authenticator( repo: Annotated[PartnerRepositoryPort, Depends(get_partner_repo)], jwt_validator: Annotated[JwtValidatorPort, Depends(get_jwt_validator)], Loading Loading
src/federation_manager/adapters/database/partner_repo.py +1 −0 Original line number Diff line number Diff line Loading @@ -22,4 +22,5 @@ class PostgresPartnerRepo: our_client_id=row.our_client_id, our_client_secret_ref=row.our_client_secret_ref, token_endpoint=row.token_endpoint, base_url=row.base_url, )
src/federation_manager/adapters/database/tables.py +1 −0 Original line number Diff line number Diff line Loading @@ -10,6 +10,7 @@ partner_ops = Table( Column("id", PGUUID(as_uuid=True), primary_key=True, server_default=func.gen_random_uuid()), Column("mcc_mnc", String(10), nullable=False, unique=True), Column("oauth2_client_id", String(255), nullable=False, unique=True), Column("base_url", Text, nullable=False), Column("our_client_id", String(255)), Column("our_client_secret_ref", Text), Column("token_endpoint", Text), Loading
src/federation_manager/adapters/http/ewbi_client.py 0 → 100644 +68 −0 Original line number Diff line number Diff line from uuid import UUID import httpx from federation_manager.domain.errors import ( PartnerEndpointConfigurationError, PartnerRequestFailed, ) from federation_manager.domain.models import EwbiResponse, PartnerOP from federation_manager.domain.ports import PartnerTokenProviderPort class HttpxEwbiClient: def __init__( self, client: httpx.AsyncClient, token_provider: PartnerTokenProviderPort, ) -> None: self._client = client self._token_provider = token_provider async def post( self, partner: PartnerOP, path: str, payload: dict[str, object], federation_correlation_id: UUID, ) -> EwbiResponse: url = self._url(partner, path) token = await self._token_provider.token_for(partner, scope="fed-mgmt") try: response = await self._client.post( url, headers={ "Accept": "application/json", "Authorization": f"Bearer {token}", "X-Federation-Correlation-ID": str(federation_correlation_id), }, json=payload, ) except httpx.HTTPError: raise PartnerRequestFailed(partner.id) from None if not response.content: body: object | None = None else: try: body = response.json() except ValueError: raise PartnerRequestFailed(partner.id) from None return EwbiResponse(status_code=response.status_code, body=body) @staticmethod def _url(partner: PartnerOP, path: str) -> str: if not path.startswith("/") or httpx.URL(path).is_absolute_url: raise ValueError("EWBI path must be an absolute path without a host") if not partner.base_url: raise PartnerEndpointConfigurationError(partner.id) try: base_url = httpx.URL(partner.base_url) except httpx.InvalidURL: raise PartnerEndpointConfigurationError(partner.id) from None if base_url.scheme != "https" or not base_url.host: raise PartnerEndpointConfigurationError(partner.id) return f"{partner.base_url.rstrip('/')}/{path.lstrip('/')}"
src/federation_manager/dependencies.py +6 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from federation_manager.adapters.database.partner_repo import PostgresPartnerRepo from federation_manager.application.authentication import PartnerAuthenticator from federation_manager.domain.ports import ( EwbiClientPort, JwtValidatorPort, PartnerRepositoryPort, PartnerTokenProviderPort, Loading Loading @@ -34,6 +35,11 @@ def get_partner_token_provider(request: Request) -> PartnerTokenProviderPort: return provider def get_ewbi_client(request: Request) -> EwbiClientPort: client: EwbiClientPort = request.app.state.ewbi_client return client def get_partner_authenticator( repo: Annotated[PartnerRepositoryPort, Depends(get_partner_repo)], jwt_validator: Annotated[JwtValidatorPort, Depends(get_jwt_validator)], Loading