Loading keycloak/realm-import.json +28 −0 Changes for keycloak/realm-import.json: 28 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -39,6 +39,20 @@ ], "optionalClientScopes": [ "fed-mgmt-notif" ], "protocolMappers": [ { "name": "audience-federation-manager", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.custom.audience": "federation-manager", "access.token.claim": "true", "introspection.token.claim": "true", "id.token.claim": "false" } } ] }, { Loading @@ -60,6 +74,20 @@ ], "optionalClientScopes": [ "fed-mgmt-notif" ], "protocolMappers": [ { "name": "audience-federation-manager", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.custom.audience": "federation-manager", "access.token.claim": "true", "introspection.token.claim": "true", "id.token.claim": "false" } } ] } ] Loading src/federation_manager/adapters/security/keycloak_validator.py +4 −2 Changes for src/federation_manager/adapters/security/keycloak_validator.py: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -5,8 +5,9 @@ from federation_manager.domain.models import ValidatedClaims class KeycloakJwtValidator: def __init__(self, issuer: str, jwks_url: str | None = None) -> None: def __init__(self, issuer: str, audience: str, jwks_url: str | None = None) -> None: self._issuer = issuer self._audience = audience self._jwks_client = jwt.PyJWKClient(jwks_url or f"{issuer}/protocol/openid-connect/certs") async def validate(self, token: str) -> ValidatedClaims: Loading @@ -17,7 +18,8 @@ class KeycloakJwtValidator: signing_key.key, algorithms=["RS256"], issuer=self._issuer, options={"require": ["exp", "iss"], "verify_aud": False}, audience=self._audience, options={"require": ["exp", "iss", "aud"]}, ) except jwt.PyJWTError as exc: raise AuthenticationFailed from exc Loading src/federation_manager/core/config.py +3 −0 Changes for src/federation_manager/core/config.py: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -31,6 +31,9 @@ class Settings(BaseSettings): # The issuer is the public URL partners' tokens carry; FM may only reach Keycloak on an # internal address. Unset, the keys are fetched from the issuer. keycloak_jwks_url: str | None = None # Partner tokens must name us in `aud`; our Keycloak's audience mapper puts it there, so a # token minted for another service in the same realm is refused. keycloak_audience: str = "federation-manager" # Our own OPG.04 identity, sent on outbound CreateFederation. federation_id: str = "oop-local" Loading src/federation_manager/main.py +3 −1 Changes for src/federation_manager/main.py: 3 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -83,7 +83,9 @@ async def default_lifespan(app: FastAPI) -> AsyncIterator[None]: await run_migrations(engine) app.state.session_maker = build_session_maker(engine) app.state.jwt_validator = KeycloakJwtValidator( settings.keycloak_issuer, jwks_url=settings.keycloak_jwks_url settings.keycloak_issuer, audience=settings.keycloak_audience, jwks_url=settings.keycloak_jwks_url, ) key = settings.partner_secret_key_bytes cipher = None if key is None else AesGcmSecretCipher(key) Loading tests/fixtures/keycloak/federation-realm.json +17 −1 Changes for tests/fixtures/keycloak/federation-realm.json: 17 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -18,7 +18,23 @@ "serviceAccountsEnabled": true, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "defaultClientScopes": ["fed-mgmt"] "defaultClientScopes": [ "fed-mgmt" ], "protocolMappers": [ { "name": "audience-federation-manager", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.custom.audience": "federation-manager", "access.token.claim": "true", "introspection.token.claim": "true", "id.token.claim": "false" } } ] } ] } Loading
keycloak/realm-import.json +28 −0 Changes for keycloak/realm-import.json: 28 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -39,6 +39,20 @@ ], "optionalClientScopes": [ "fed-mgmt-notif" ], "protocolMappers": [ { "name": "audience-federation-manager", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.custom.audience": "federation-manager", "access.token.claim": "true", "introspection.token.claim": "true", "id.token.claim": "false" } } ] }, { Loading @@ -60,6 +74,20 @@ ], "optionalClientScopes": [ "fed-mgmt-notif" ], "protocolMappers": [ { "name": "audience-federation-manager", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.custom.audience": "federation-manager", "access.token.claim": "true", "introspection.token.claim": "true", "id.token.claim": "false" } } ] } ] Loading
src/federation_manager/adapters/security/keycloak_validator.py +4 −2 Changes for src/federation_manager/adapters/security/keycloak_validator.py: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -5,8 +5,9 @@ from federation_manager.domain.models import ValidatedClaims class KeycloakJwtValidator: def __init__(self, issuer: str, jwks_url: str | None = None) -> None: def __init__(self, issuer: str, audience: str, jwks_url: str | None = None) -> None: self._issuer = issuer self._audience = audience self._jwks_client = jwt.PyJWKClient(jwks_url or f"{issuer}/protocol/openid-connect/certs") async def validate(self, token: str) -> ValidatedClaims: Loading @@ -17,7 +18,8 @@ class KeycloakJwtValidator: signing_key.key, algorithms=["RS256"], issuer=self._issuer, options={"require": ["exp", "iss"], "verify_aud": False}, audience=self._audience, options={"require": ["exp", "iss", "aud"]}, ) except jwt.PyJWTError as exc: raise AuthenticationFailed from exc Loading
src/federation_manager/core/config.py +3 −0 Changes for src/federation_manager/core/config.py: 3 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -31,6 +31,9 @@ class Settings(BaseSettings): # The issuer is the public URL partners' tokens carry; FM may only reach Keycloak on an # internal address. Unset, the keys are fetched from the issuer. keycloak_jwks_url: str | None = None # Partner tokens must name us in `aud`; our Keycloak's audience mapper puts it there, so a # token minted for another service in the same realm is refused. keycloak_audience: str = "federation-manager" # Our own OPG.04 identity, sent on outbound CreateFederation. federation_id: str = "oop-local" Loading
src/federation_manager/main.py +3 −1 Changes for src/federation_manager/main.py: 3 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -83,7 +83,9 @@ async def default_lifespan(app: FastAPI) -> AsyncIterator[None]: await run_migrations(engine) app.state.session_maker = build_session_maker(engine) app.state.jwt_validator = KeycloakJwtValidator( settings.keycloak_issuer, jwks_url=settings.keycloak_jwks_url settings.keycloak_issuer, audience=settings.keycloak_audience, jwks_url=settings.keycloak_jwks_url, ) key = settings.partner_secret_key_bytes cipher = None if key is None else AesGcmSecretCipher(key) Loading
tests/fixtures/keycloak/federation-realm.json +17 −1 Changes for tests/fixtures/keycloak/federation-realm.json: 17 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -18,7 +18,23 @@ "serviceAccountsEnabled": true, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "defaultClientScopes": ["fed-mgmt"] "defaultClientScopes": [ "fed-mgmt" ], "protocolMappers": [ { "name": "audience-federation-manager", "protocol": "openid-connect", "protocolMapper": "oidc-audience-mapper", "consentRequired": false, "config": { "included.custom.audience": "federation-manager", "access.token.claim": "true", "introspection.token.claim": "true", "id.token.claim": "false" } } ] } ] }