From e63897ee2745ea689870f69755f4db3f0d48e984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Reynard?= Date: Tue, 4 Feb 2025 14:44:31 +0100 Subject: [PATCH] Regarding the API configuration, KeycloakJwtAuthenticationConverter leads to HTTP 500 error When application identity runs a simulation, if principalJwtClaim is set to a user specific claim name, it crashes. We add the same fallback mechanism as SecurityUtils.getCurrentAuthenticatedUserName (used during Cosmo resource creation) --- .../keycloak/KeycloakSecurityConfiguration.kt | 2 + ...KeycloakJwtAuthenticationConverterTests.kt | 114 ++++++++++++++++-- 2 files changed, 103 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/com/cosmotech/api/security/keycloak/KeycloakSecurityConfiguration.kt b/src/main/kotlin/com/cosmotech/api/security/keycloak/KeycloakSecurityConfiguration.kt index 87cbb1d7..3257be9a 100644 --- a/src/main/kotlin/com/cosmotech/api/security/keycloak/KeycloakSecurityConfiguration.kt +++ b/src/main/kotlin/com/cosmotech/api/security/keycloak/KeycloakSecurityConfiguration.kt @@ -183,6 +183,8 @@ class KeycloakJwtAuthenticationConverter(private val csmPlatformProperties: CsmP KeycloakJwtGrantedAuthoritiesConverter(csmPlatformProperties).convert(jwt) val principalClaimValue: String = jwt.getClaimAsString(csmPlatformProperties.authorization.principalJwtClaim) + ?: jwt.getClaimAsString(csmPlatformProperties.authorization.applicationIdJwtClaim) + ?: throw IllegalStateException("User Authentication not found in Security Context") return JwtAuthenticationToken(jwt, authorities, principalClaimValue) } } diff --git a/src/test/kotlin/com/cosmotech/api/security/keycloak/KeycloakJwtAuthenticationConverterTests.kt b/src/test/kotlin/com/cosmotech/api/security/keycloak/KeycloakJwtAuthenticationConverterTests.kt index eb746009..6c4ad03d 100644 --- a/src/test/kotlin/com/cosmotech/api/security/keycloak/KeycloakJwtAuthenticationConverterTests.kt +++ b/src/test/kotlin/com/cosmotech/api/security/keycloak/KeycloakJwtAuthenticationConverterTests.kt @@ -35,12 +35,14 @@ class KeycloakJwtAuthenticationConverterTests { @Test fun `convertRolesToAuthorities with correct values`() { val principalClaimValue = "my.principal@me.com" + val principalClaimName = "email" val claims = mutableMapOf( "claim1" to "10", "claimRoles" to listOf("role1", "role2", "role3"), "claimName" to "myClaimName", - "sub" to principalClaimValue) + "sub" to "123-456-798", + principalClaimName to principalClaimValue) val expectedSimpleGrantedAuthorities = listOf( SimpleGrantedAuthority("role1"), @@ -49,53 +51,139 @@ class KeycloakJwtAuthenticationConverterTests { every { jwt.claims } returns claims every { csmPlatformProperties.authorization.rolesJwtClaim } returns "claimRoles" - every { csmPlatformProperties.authorization.principalJwtClaim } returns principalClaimValue - every { jwt.getClaimAsString(principalClaimValue) } returns principalClaimValue + every { csmPlatformProperties.authorization.principalJwtClaim } returns principalClaimName + every { jwt.getClaimAsString(principalClaimName) } returns principalClaimValue val jwtConverted = keycloakJwtAuthenticationConverter.convert(jwt) assertEquals( - JwtAuthenticationToken(jwt, expectedSimpleGrantedAuthorities, principalClaimValue), + JwtAuthenticationToken(jwt, expectedSimpleGrantedAuthorities, principalClaimName), jwtConverted) } + @Test + fun `convertRolesToAuthorities with correct values when no principalJwtClaim set`() { + val principalClaimName = "unexisting-principal-claim" + val applicationIdClaimValue = "123-456-798" + val applicationIdClaimName = "sub" + val claims = + mutableMapOf( + "claim1" to "10", + "claimRoles" to listOf("role1", "role2", "role3"), + "claimName" to "myClaimName", + applicationIdClaimName to applicationIdClaimValue) + val expectedSimpleGrantedAuthorities = + listOf( + SimpleGrantedAuthority("role1"), + SimpleGrantedAuthority("role2"), + SimpleGrantedAuthority("role3")) + + every { jwt.claims } returns claims + every { csmPlatformProperties.authorization.rolesJwtClaim } returns "claimRoles" + every { csmPlatformProperties.authorization.principalJwtClaim } returns principalClaimName + every { csmPlatformProperties.authorization.applicationIdJwtClaim } returns applicationIdClaimName + every { jwt.getClaimAsString(principalClaimName) } returns null + every { jwt.getClaimAsString(applicationIdClaimName) } returns applicationIdClaimValue + + val jwtConverted = keycloakJwtAuthenticationConverter.convert(jwt) + + assertEquals( + JwtAuthenticationToken(jwt, expectedSimpleGrantedAuthorities, applicationIdClaimValue), + jwtConverted) + } + @Test fun `convertRolesToAuthorities with non-existing role claim values`() { val principalClaimValue = "my.principal@me.com" + val principalClaimName = "email" val claims = mutableMapOf( "claim1" to "10", "claimRoles" to listOf("role1", "role2", "role3"), "claimName" to "myClaimName", - "sub" to principalClaimValue) + "sub" to "123-456-798", + principalClaimName to principalClaimValue) every { jwt.claims } returns claims every { csmPlatformProperties.authorization.rolesJwtClaim } returns "unexisting-role-claim" - every { csmPlatformProperties.authorization.principalJwtClaim } returns principalClaimValue - every { jwt.getClaimAsString(principalClaimValue) } returns principalClaimValue + every { csmPlatformProperties.authorization.principalJwtClaim } returns principalClaimName + every { jwt.getClaimAsString(principalClaimName) } returns principalClaimValue val jwtConverted = keycloakJwtAuthenticationConverter.convert(jwt) - assertEquals(JwtAuthenticationToken(jwt, emptyList(), principalClaimValue), jwtConverted) + assertEquals(JwtAuthenticationToken(jwt, emptyList(), principalClaimName), jwtConverted) } + @Test + fun `convertRolesToAuthorities with non-existing role claim values when no principalJwtClaim set`() { + val principalClaimName = "unexisting-principal-claim" + val applicationIdClaimValue = "123-456-798" + val applicationIdClaimName = "sub" + val claims = + mutableMapOf( + "claim1" to "10", + "claimRoles" to listOf("role1", "role2", "role3"), + "claimName" to "myClaimName", + "sub" to "123-456-798") + + every { jwt.claims } returns claims + every { csmPlatformProperties.authorization.rolesJwtClaim } returns "unexisting-role-claim" + every { csmPlatformProperties.authorization.principalJwtClaim } returns principalClaimName + every { csmPlatformProperties.authorization.applicationIdJwtClaim } returns applicationIdClaimName + every { jwt.getClaimAsString(principalClaimName) } returns null + every { jwt.getClaimAsString(applicationIdClaimName) } returns applicationIdClaimValue + + val jwtConverted = keycloakJwtAuthenticationConverter.convert(jwt) + + assertEquals(JwtAuthenticationToken(jwt, emptyList(), applicationIdClaimName), jwtConverted) + } + @Test fun `convertRolesToAuthorities with existing role claim but no roles defined`() { - val principalClaimValue = "my.principal@me.com" + val principalClaimValue = "my.principal@me.com" + val principalClaimName = "email" val claims = mutableMapOf( "claim1" to "10", "claimRoles" to emptyList(), "claimName" to "myClaimName", - "sub" to principalClaimValue) + "sub" to "123-456-798", + principalClaimName to principalClaimValue) every { jwt.claims } returns claims every { csmPlatformProperties.authorization.rolesJwtClaim } returns "claimRoles" - every { csmPlatformProperties.authorization.principalJwtClaim } returns principalClaimValue - every { jwt.getClaimAsString(principalClaimValue) } returns principalClaimValue + every { csmPlatformProperties.authorization.principalJwtClaim } returns principalClaimName + every { jwt.getClaimAsString(principalClaimName) } returns principalClaimValue val jwtConverted = keycloakJwtAuthenticationConverter.convert(jwt) - assertEquals(JwtAuthenticationToken(jwt, emptyList(), principalClaimValue), jwtConverted) + assertEquals(JwtAuthenticationToken(jwt, emptyList(), principalClaimName), jwtConverted) } + + @Test + fun `convertRolesToAuthorities with existing role claim but no roles defined when no principalJwtClaim set`() { + val principalClaimName = "unexisting-principal-claim" + val applicationIdClaimValue = "123-456-798" + val applicationIdClaimName = "sub" + val claims = + mutableMapOf( + "claim1" to "10", + "claimRoles" to emptyList(), + "claimName" to "myClaimName", + "sub" to "123-456-798") + + every { jwt.claims } returns claims + every { csmPlatformProperties.authorization.rolesJwtClaim } returns "claimRoles" + every { csmPlatformProperties.authorization.principalJwtClaim } returns principalClaimName + every { csmPlatformProperties.authorization.applicationIdJwtClaim } returns applicationIdClaimName + every { jwt.getClaimAsString(principalClaimName) } returns null + every { jwt.getClaimAsString(applicationIdClaimName) } returns applicationIdClaimValue + + val jwtConverted = keycloakJwtAuthenticationConverter.convert(jwt) + + assertEquals(JwtAuthenticationToken(jwt, emptyList(), applicationIdClaimName), jwtConverted) + } + + + }