Skip to content

Commit

Permalink
fix: allow multiple app roles for same oidc token role (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
davdarras authored Nov 28, 2024
1 parent 2a6ff53 commit 596b8c1
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
@AllArgsConstructor
public class GrantedAuthorityConverter implements Converter<Jwt, Collection<GrantedAuthority>> {

private final Map<String, SimpleGrantedAuthority> grantedRoles;
private final Map<String, List<SimpleGrantedAuthority>> roles;
ApplicationConfig applicationConfig;

public GrantedAuthorityConverter(ApplicationConfig applicationConfig) {
this.applicationConfig = applicationConfig;
this.grantedRoles = new HashMap<>();
this.roles = new HashMap<>();
fillGrantedRoles(applicationConfig.getRoleAdmin(), AuthorityRoleEnum.ADMIN);
fillGrantedRoles(applicationConfig.getRoleRespondent(), AuthorityRoleEnum.RESPONDENT);
fillGrantedRoles(applicationConfig.getRoleInternalUser(), AuthorityRoleEnum.INTERNAL_USER);
Expand All @@ -31,23 +31,37 @@ public GrantedAuthorityConverter(ApplicationConfig applicationConfig) {
@Override
public Collection<GrantedAuthority> convert(@NonNull Jwt jwt) {
Map<String, Object> claims = jwt.getClaims();
List<String> roles = (List<String>) claims.get(applicationConfig.getRoleClaim());
List<String> userRoles = (List<String>) claims.get(applicationConfig.getRoleClaim());

return roles.stream()
if(userRoles == null) {
return new ArrayList<>();
}

return userRoles.stream()
.filter(Objects::nonNull)
.filter(role -> !role.isBlank())
.filter(grantedRoles::containsKey)
.map(grantedRoles::get)
.filter(roles::containsKey)
.map(roles::get)
.flatMap(Collection::stream)
.distinct()
.collect(Collectors.toCollection(ArrayList::new));
}

private void fillGrantedRoles(List<String> listRoles, AuthorityRoleEnum roleEnum) {
private void fillGrantedRoles(List<String> configRoles, AuthorityRoleEnum authorityRole) {

for (String role : listRoles ) {
this.grantedRoles.putIfAbsent(role,
new SimpleGrantedAuthority(roleEnum.securityRole()));
}
for (String configRole : configRoles ) {
if(configRole == null || configRole.isBlank()) {
return;
}

this.roles.compute(configRole, (key, grantedAuthorities) -> {
if(grantedAuthorities == null) {
grantedAuthorities = new ArrayList<>();
}
grantedAuthorities.add(new SimpleGrantedAuthority(authorityRole.securityRole()));
return grantedAuthorities;
});
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package fr.insee.survey.datacollectionmanagement.configuration.auth.security;

import fr.insee.survey.datacollectionmanagement.configuration.ApplicationConfig;
import fr.insee.survey.datacollectionmanagement.constants.AuthorityRoleEnum;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;

import java.time.Instant;
import java.util.*;
import java.util.stream.Stream;

import static org.assertj.core.api.Assertions.assertThat;

class GrantedAuthorityConverterTest {

private GrantedAuthorityConverter converter;

private ApplicationConfig applicationConfig;

private static final List<String> JWT_ROLE_INTERNAL_USER = List.of("internal_user", "intern_user");
private static final List<String> JWT_ROLE_RESPONDENT = List.of("respondent", "resp");
private static final List<String> JWT_ROLE_ADMIN = List.of("admin", "adm");
private static final List<String> JWT_ROLE_WEBCLIENT = List.of("webclient","webcli");

@BeforeEach
void init() {
applicationConfig = new ApplicationConfig();
}

@Test
@DisplayName("Given a JWT, when converting null or empty JWT role, then converting ignore these roles")
void testConverter01() {
applicationConfig.setRoleAdmin(List.of(""));
applicationConfig.setRoleInternalUser(List.of());
applicationConfig.setRoleWebClient(List.of(""));
List<String> nullList = new ArrayList<>();
nullList.add(null);
applicationConfig.setRoleRespondent(nullList);
converter = new GrantedAuthorityConverter(applicationConfig);
List<String> tokenRoles = new ArrayList<>();
tokenRoles.add(null);
tokenRoles.add("");

Jwt jwt = createJwt(tokenRoles);
Collection<GrantedAuthority> authorities = converter.convert(jwt);
assertThat(authorities).isEmpty();
}

@Test
@DisplayName("Given a JWT, when converting roles, then convert only JWT roles matching roles in role properties")
void testConverter02() {
applicationConfig.setRoleAdmin(JWT_ROLE_ADMIN);
applicationConfig.setRoleInternalUser(JWT_ROLE_INTERNAL_USER);
applicationConfig.setRoleWebClient(List.of("webclient", "adm"));
applicationConfig.setRoleRespondent(JWT_ROLE_RESPONDENT);
converter = new GrantedAuthorityConverter(applicationConfig);
List<String> tokenRoles = List.of("dummyRole1", "internal_user", "dummyRole2", "webclient", "adm");

Jwt jwt = createJwt(tokenRoles);
Collection<GrantedAuthority> authorities = converter.convert(jwt);
assertThat(authorities)
.hasSize(3)
.containsExactlyInAnyOrder(
new SimpleGrantedAuthority(AuthorityRoleEnum.INTERNAL_USER.securityRole()),
new SimpleGrantedAuthority(AuthorityRoleEnum.ADMIN.securityRole()),
new SimpleGrantedAuthority(AuthorityRoleEnum.WEB_CLIENT.securityRole()));
}

@Test
@DisplayName("Given a JWT, when converting roles, then accept a config role can be used for multiple app roles")
void testConverter03() {
String dummyRole = "dummyRole";
String dummyRole2 = "dummyRole2";
applicationConfig.setRoleAdmin(List.of(dummyRole));
applicationConfig.setRoleInternalUser(List.of(dummyRole2));
applicationConfig.setRoleWebClient(List.of(dummyRole));
applicationConfig.setRoleRespondent(List.of(""));
converter = new GrantedAuthorityConverter(applicationConfig);

List<String> tokenRoles = List.of(dummyRole, "role-not-used", dummyRole2, "role-not-used-2");
Jwt jwt = createJwt(tokenRoles);

Collection<GrantedAuthority> authorities = converter.convert(jwt);
assertThat(authorities)
.hasSize(3)
.contains(
new SimpleGrantedAuthority(AuthorityRoleEnum.INTERNAL_USER.securityRole()),
new SimpleGrantedAuthority(AuthorityRoleEnum.ADMIN.securityRole()),
new SimpleGrantedAuthority(AuthorityRoleEnum.WEB_CLIENT.securityRole()));
}

@ParameterizedTest
@MethodSource("provideJWTRoleWithAppRoleAssociated")
@DisplayName("Given a JWT, when converting roles, then assure each JWT role is converted to equivalent app role")
void testConverter04(List<String> jwtRoles, AuthorityRoleEnum appRole) {
applicationConfig.setRoleAdmin(JWT_ROLE_ADMIN);
applicationConfig.setRoleInternalUser(JWT_ROLE_INTERNAL_USER);
applicationConfig.setRoleWebClient(JWT_ROLE_WEBCLIENT);
applicationConfig.setRoleRespondent(JWT_ROLE_RESPONDENT);
converter = new GrantedAuthorityConverter(applicationConfig);

Jwt jwt = createJwt(jwtRoles);
Collection<GrantedAuthority> authorities = converter.convert(jwt);
assertThat(authorities)
.hasSize(1)
.contains(new SimpleGrantedAuthority(appRole.securityRole()));
}

private static Stream<Arguments> provideJWTRoleWithAppRoleAssociated() {
return Stream.of(
Arguments.of(JWT_ROLE_INTERNAL_USER, AuthorityRoleEnum.INTERNAL_USER),
Arguments.of(JWT_ROLE_ADMIN, AuthorityRoleEnum.ADMIN),
Arguments.of(JWT_ROLE_WEBCLIENT, AuthorityRoleEnum.WEB_CLIENT),
Arguments.of(JWT_ROLE_RESPONDENT, AuthorityRoleEnum.RESPONDENT));
}

private Jwt createJwt(List<String> tokenRoles) {
Map<String, Object> jwtHeaders = new HashMap<>();
jwtHeaders.put("header", "headerValue");

Map<String, Object> claims = new HashMap<>();
claims.put(applicationConfig.getRoleClaim(), tokenRoles);

return new Jwt("user-id", Instant.now(), Instant.MAX, jwtHeaders, claims);
}
}
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<module>platine-management-api</module>
</modules>
<properties>
<revision>2.7.1</revision>
<revision>2.7.2</revision>
<changelist></changelist>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
Expand Down

0 comments on commit 596b8c1

Please sign in to comment.