From 7de313b51f688456eb0ecbb6732567522651be9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn-Andre=20Skaar?= <31540110+bjornandre@users.noreply.github.com> Date: Mon, 18 Mar 2024 15:51:04 +0100 Subject: [PATCH] When using isAuthenticated() the JWT token must be signed by a trusted issuer (#103) --- conf/application-local.yml | 5 ++ .../service/security/CustomRolesFinder.java | 13 ++- .../service/security/StaticRolesConfig.java | 1 + .../security/CustomRolesFinderTest.java | 89 +++++++++++++++++++ 4 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 src/test/java/no/ssb/dlp/pseudo/service/security/CustomRolesFinderTest.java diff --git a/conf/application-local.yml b/conf/application-local.yml index 447d624..7e6d831 100644 --- a/conf/application-local.yml +++ b/conf/application-local.yml @@ -40,6 +40,8 @@ micronaut: jwks: keycloak-staging: url: 'https://keycloak.staging-bip-app.ssb.no/auth/realms/ssb/protocol/openid-connect/certs' + google: + url: 'https://www.googleapis.com/oauth2/v3/certs' http: services: @@ -117,6 +119,9 @@ pseudo.secrets: type: TINK_WDEK app-roles: + # When using isAuthenticated() the JWT token must be signed by one of the trusted-issuers + trusted-issuers: + - https://keycloak.staging-bip-app.ssb.no/auth/realms/ssb users: - isAuthenticated() admins: diff --git a/src/main/java/no/ssb/dlp/pseudo/service/security/CustomRolesFinder.java b/src/main/java/no/ssb/dlp/pseudo/service/security/CustomRolesFinder.java index 73054fb..199b867 100644 --- a/src/main/java/no/ssb/dlp/pseudo/service/security/CustomRolesFinder.java +++ b/src/main/java/no/ssb/dlp/pseudo/service/security/CustomRolesFinder.java @@ -1,5 +1,6 @@ package no.ssb.dlp.pseudo.service.security; +import com.nimbusds.jwt.JWTClaimNames; import io.micronaut.context.annotation.Replaces; import io.micronaut.context.annotation.Requirements; import io.micronaut.context.annotation.Requires; @@ -37,11 +38,13 @@ public List resolveRoles(Map attributes) { List roles = new ArrayList<>(); Object username = attributes.get(tokenConfiguration.getNameKey()); - if (rolesConfig.getAdmins().contains(SecurityRule.IS_AUTHENTICATED) - ||rolesConfig.getAdmins().contains(username)) { + boolean trustedIssuer = isTrustedIssuer(attributes); + log.debug("User {} has a trusted issuer? {}", username, trustedIssuer); + if (rolesConfig.getAdmins().contains(SecurityRule.IS_AUTHENTICATED) && trustedIssuer + || rolesConfig.getAdmins().contains(username)) { roles.add(PseudoServiceRole.ADMIN); } - if (rolesConfig.getUsers().contains(SecurityRule.IS_AUTHENTICATED) + if (rolesConfig.getUsers().contains(SecurityRule.IS_AUTHENTICATED) && trustedIssuer || rolesConfig.getUsers().contains(username)) { roles.add(PseudoServiceRole.USER); } @@ -63,4 +66,8 @@ public List resolveRoles(Map attributes) { log.debug("Resolved roles {} for user {}", roles, username); return roles; } + + private boolean isTrustedIssuer(Map attributes) { + return rolesConfig.getTrustedIssuers().contains(String.valueOf(attributes.get(JWTClaimNames.ISSUER))); + } } diff --git a/src/main/java/no/ssb/dlp/pseudo/service/security/StaticRolesConfig.java b/src/main/java/no/ssb/dlp/pseudo/service/security/StaticRolesConfig.java index 2f2b24a..2d5c1c2 100644 --- a/src/main/java/no/ssb/dlp/pseudo/service/security/StaticRolesConfig.java +++ b/src/main/java/no/ssb/dlp/pseudo/service/security/StaticRolesConfig.java @@ -10,6 +10,7 @@ @ConfigurationProperties("app-roles") @Data public class StaticRolesConfig { + private List trustedIssuers = new ArrayList<>(); private List users = new ArrayList<>(); private List admins = new ArrayList<>(); diff --git a/src/test/java/no/ssb/dlp/pseudo/service/security/CustomRolesFinderTest.java b/src/test/java/no/ssb/dlp/pseudo/service/security/CustomRolesFinderTest.java new file mode 100644 index 0000000..809cc31 --- /dev/null +++ b/src/test/java/no/ssb/dlp/pseudo/service/security/CustomRolesFinderTest.java @@ -0,0 +1,89 @@ +package no.ssb.dlp.pseudo.service.security; + +import com.nimbusds.jwt.JWTClaimNames; +import io.micronaut.security.rules.SecurityRule; +import io.micronaut.security.token.config.TokenConfiguration; +import io.micronaut.security.token.config.TokenConfigurationProperties; +import no.ssb.dlp.pseudo.service.accessgroups.CloudIdentityService; +import no.ssb.dlp.pseudo.service.accessgroups.EntityKey; +import no.ssb.dlp.pseudo.service.accessgroups.Membership; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class CustomRolesFinderTest { + + CloudIdentityService cloudIdentityService = mock(CloudIdentityService.class); + StaticRolesConfig rolesConfig = mock(StaticRolesConfig.class); + TokenConfiguration tokenConfig = new TokenConfigurationProperties(); + CustomRolesFinder sut = new CustomRolesFinder(tokenConfig, rolesConfig, cloudIdentityService); + + @Test + void single_user_gets_no_roles() { + final String email = "john.doe@ssb.no"; + assertIterableEquals(List.of(), sut.resolveRoles(Map.of(tokenConfig.getNameKey(), email))); + } + @Test + void single_user_get_user_role() { + final String email = "john.doe@ssb.no"; + when(rolesConfig.getUsers()).thenReturn(List.of(email)); + assertIterableEquals(List.of(PseudoServiceRole.USER), sut.resolveRoles(Map.of(tokenConfig.getNameKey(), email))); + } + + @Test + void single_user_get_admin_role() { + final String email = "john.doe@ssb.no"; + when(rolesConfig.getAdmins()).thenReturn(List.of(email)); + assertIterableEquals(List.of(PseudoServiceRole.ADMIN), sut.resolveRoles(Map.of(tokenConfig.getNameKey(), email))); + } + + @Test + void single_user_get_multiple_roles() { + final String email = "john.doe@ssb.no"; + when(rolesConfig.getUsers()).thenReturn(List.of(email)); + when(rolesConfig.getAdmins()).thenReturn(List.of(email)); + assertIterableEquals(List.of(PseudoServiceRole.ADMIN, PseudoServiceRole.USER), sut.resolveRoles(Map.of(tokenConfig.getNameKey(), email))); + } + + @Test + void group_membership_user_role() { + final String email = "john.doe@ssb.no"; + final String user_group = "user-group@ssb.no"; + when(rolesConfig.getUsersGroup()).thenReturn(Optional.of(user_group)); + when(cloudIdentityService.listMembers(eq(user_group))) + .thenReturn(List.of(new Membership("John Doe", new EntityKey(email, "ssb")))); + assertIterableEquals(List.of(PseudoServiceRole.USER), sut.resolveRoles(Map.of(tokenConfig.getNameKey(), email))); + } + + @Test + void group_membership_admin_role() { + final String email = "john.doe@ssb.no"; + final String user_group = "user-group@ssb.no"; + when(rolesConfig.getAdminsGroup()).thenReturn(Optional.of(user_group)); + when(cloudIdentityService.listMembers(eq(user_group))) + .thenReturn(List.of(new Membership("John Doe", new EntityKey(email, "ssb")))); + assertIterableEquals(List.of(PseudoServiceRole.ADMIN), sut.resolveRoles(Map.of(tokenConfig.getNameKey(), email))); + } + + @Test + void authenticated_user_gets_no_roles_when_issuer_not_trusted() { + final String email = "john.doe@ssb.no"; + when(rolesConfig.getUsers()).thenReturn(List.of(SecurityRule.IS_AUTHENTICATED)); + assertIterableEquals(List.of(), sut.resolveRoles(Map.of(tokenConfig.getNameKey(), email))); + } + + @Test + void authenticated_user_get_user_role_when_issuer_is_trusted() { + final String email = "john.doe@ssb.no"; + final String trusted_issuer = "some-issuer-url/auth/realm"; + when(rolesConfig.getUsers()).thenReturn(List.of(SecurityRule.IS_AUTHENTICATED)); + when(rolesConfig.getTrustedIssuers()).thenReturn(List.of(trusted_issuer)); + assertIterableEquals(List.of(PseudoServiceRole.USER), + sut.resolveRoles(Map.of(tokenConfig.getNameKey(), email, JWTClaimNames.ISSUER, trusted_issuer))); + } +} \ No newline at end of file