From 7e16001afbc0afee179cd521a915713eaa6675f0 Mon Sep 17 00:00:00 2001 From: Alice Lambois Date: Tue, 13 May 2025 11:48:46 +0200 Subject: [PATCH 1/7] Add multi issuers authentication --- .../auth/security/OIDCSecurityConfig.java | 41 +++++++++++++++++-- src/main/resources/application.properties | 4 +- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/main/java/fr/insee/genesis/configuration/auth/security/OIDCSecurityConfig.java b/src/main/java/fr/insee/genesis/configuration/auth/security/OIDCSecurityConfig.java index ac9b5c3ea..224780789 100644 --- a/src/main/java/fr/insee/genesis/configuration/auth/security/OIDCSecurityConfig.java +++ b/src/main/java/fr/insee/genesis/configuration/auth/security/OIDCSecurityConfig.java @@ -4,13 +4,15 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpMethod; -import org.springframework.security.config.Customizer; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -18,12 +20,16 @@ import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; +import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,8 +49,14 @@ public class OIDCSecurityConfig { private final RoleConfiguration roleConfiguration; private final SecurityTokenProperties inseeSecurityTokenProperties; + @Value("${fr.insee.genesis.security.resourceserver.jwt.issuer-uri}") + String issuerUri; + + @Value("${fr.insee.genesis.security.resourceserver.dmz.jwt.issuer-uri}") + String issuerUriDmz; + @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + public SecurityFilterChain filterChain(HttpSecurity http, JwtIssuerAuthenticationManagerResolver authenticationManagerResolver) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); @@ -62,10 +74,13 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers(HttpMethod.GET,"/campaigns/**").hasRole(String.valueOf(ApplicationRole.READER)) .anyRequest().authenticated() ) - .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); + // .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); + .oauth2ResourceServer( + oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver)); return http.build(); } + @Bean JwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); @@ -75,6 +90,26 @@ JwtAuthenticationConverter jwtAuthenticationConverter() { } + + @Bean + public JwtIssuerAuthenticationManagerResolver authenticationManagerResolver() { + final List issuers = List.of(issuerUri,issuerUriDmz); + Map authenticationManagers = new HashMap<>(); + + for (String issuer : issuers) { + NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder + .withJwkSetUri(issuer + "/protocol/openid-connect/certs") + .build(); + + JwtAuthenticationProvider provider = new JwtAuthenticationProvider(jwtDecoder); + provider.setJwtAuthenticationConverter(jwtAuthenticationConverter()); + + AuthenticationManager manager = new ProviderManager(provider); + authenticationManagers.put(issuer, manager); + } + return new JwtIssuerAuthenticationManagerResolver(authenticationManagers::get); + } + Converter> jwtGrantedAuthoritiesConverter() { return new Converter>() { @Override diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 055977d23..15d29f604 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,7 +26,9 @@ server.forward-headers-strategy=framework #-------------------------------------------------------------------------- fr.insee.genesis.security.token.oidc-claim-role=realm_access.roles fr.insee.genesis.security.token.oidc-claim-username=name -spring.security.oauth2.resourceserver.jwt.issuer-uri=${fr.insee.genesis.oidc.auth-server-url}/realms/${fr.insee.genesis.oidc.realm} +fr.insee.genesis.security.resourceserver.jwt.issuer-uri=${fr.insee.genesis.oidc.auth-server-url}/realms/${fr.insee.genesis.oidc.realm} +fr.insee.genesis.security.resourceserver.dmz.jwt.issuer-uri=${fr.insee.genesis.oidc.dmz.auth-server-url}/realms/${fr.insee.genesis.oidc.dmz.realm} + fr.insee.genesis.security.whitelist-matchers=/v3/api-docs/**,/swagger-ui/**,/swagger-ui.html,/actuator/**,/error,/,/health-check/** springdoc.swagger-ui.oauth.scopes=openid,profile,roles From 7809f2cdfc7721d75f93248642d6b06b52cd350d Mon Sep 17 00:00:00 2001 From: Alice Lambois Date: Tue, 13 May 2025 13:55:04 +0200 Subject: [PATCH 2/7] fix: add tetst properties --- src/main/resources/application-test.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index 932289c53..046e1fcf1 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -15,5 +15,7 @@ fr.insee.genesis.oidc.auth-server-url=https://organisation.server.auth/auth fr.insee.genesis.oidc.realm=test-realm springdoc.swagger-ui.oauth.client-id=client-id-test +fr.insee.genesis.oidc.dmz.auth-server-url=https://organisation.server.auth/auth +fr.insee.genesis.oidc.dmz.realm=test-realm-dmz fr.insee.genesis.authentication = OIDC From baed0f3ddcb5caf3e4c6e6a4c4de94301dd9c8e7 Mon Sep 17 00:00:00 2001 From: Alice Lambois Date: Tue, 13 May 2025 14:31:16 +0200 Subject: [PATCH 3/7] fix one test --- .../controller/rest/ControllerAccessTest.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java b/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java index cffb10f5c..9bfb579d2 100644 --- a/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java +++ b/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java @@ -22,6 +22,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.test.context.ActiveProfiles; @@ -37,6 +38,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -114,6 +116,17 @@ void admin_should_access_reader_allowed_services(String endpointURI) throws Exce .andExpect(status().is(oneOf(200,404))); } + @Test + @DisplayName("Reader should access schedule/all endpoint") + void reader_should_access_schedules_services() throws Exception{ + mockMvc.perform( + get("/schedule/all").with(jwt() + .authorities(new SimpleGrantedAuthority("ROLE_READER"))) + ) + .andExpect(status().is(oneOf(200,404))); + } + + /** * Tests that users with the "USER_KRAFTWERK" role can access read-only endpoints. */ @@ -166,17 +179,7 @@ void invalid_user_should_not_access_reader_allowed_services(String endpointURI) .andExpect(status().isForbidden()); } - /** - * Test that reader can access the schedule/all endpoint. - */ - @Test - @DisplayName("Reader should access schedule/all endpoint") - void reader_should_access_schedules_services() throws Exception{ - Jwt jwt = generateJwt(List.of("lecteur_traiter"), READER); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get("/schedule/all").header("Authorization", "bearer token_blabla")) - .andExpect(status().is(oneOf(200,404))); - } + /** * Test that reader can not access other schedule endpoints. @@ -203,6 +206,8 @@ void kraftwerk_users_should_not_access_schedules_services() throws Exception{ .andExpect(status().is(oneOf(200,404))); } + + /** * Test that admins can access the schedule endpoints. */ From 8f8fac74d2332cf39f863011579476800d1f3617 Mon Sep 17 00:00:00 2001 From: Alice Lambois Date: Tue, 13 May 2025 14:45:51 +0200 Subject: [PATCH 4/7] fix: tests --- .../controller/rest/ControllerAccessTest.java | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java b/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java index 9bfb579d2..7b2afc87d 100644 --- a/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java +++ b/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java @@ -37,7 +37,6 @@ import static org.hamcrest.Matchers.oneOf; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.when; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -110,9 +109,10 @@ private static Stream endpointsReader(){ @MethodSource("endpointsReader") @DisplayName("Admins should access reader-allowed services") void admin_should_access_reader_allowed_services(String endpointURI) throws Exception{ - Jwt jwt = generateJwt(List.of("administrateur_traiter"), ADMIN); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get(endpointURI).header("Authorization", "bearer token_blabla")) + mockMvc.perform( + get(endpointURI) + .with(jwt().authorities(new SimpleGrantedAuthority("ROLE_ADMIN"))) + ) .andExpect(status().is(oneOf(200,404))); } @@ -120,8 +120,8 @@ void admin_should_access_reader_allowed_services(String endpointURI) throws Exce @DisplayName("Reader should access schedule/all endpoint") void reader_should_access_schedules_services() throws Exception{ mockMvc.perform( - get("/schedule/all").with(jwt() - .authorities(new SimpleGrantedAuthority("ROLE_READER"))) + get("/schedule/all") + .with(jwt().authorities(new SimpleGrantedAuthority("ROLE_READER"))) ) .andExpect(status().is(oneOf(200,404))); } @@ -134,9 +134,9 @@ void reader_should_access_schedules_services() throws Exception{ @MethodSource("endpointsReader") @DisplayName("Kraftwerk users should access reader-allowed services") void kraftwerk_users_should_access_reader_allowed_services(String endpointURI) throws Exception{ - Jwt jwt = generateJwt(List.of("utilisateur_Kraftwerk"), USER_KRAFTWERK); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get(endpointURI).header("Authorization", "bearer token_blabla")) + mockMvc.perform(get(endpointURI) + .with(jwt().authorities(new SimpleGrantedAuthority("ROLE_USER_KRAFTWERK"))) + ) .andExpect(status().is(oneOf(200,404))); } @@ -147,9 +147,9 @@ void kraftwerk_users_should_access_reader_allowed_services(String endpointURI) t @MethodSource("endpointsReader") @DisplayName("Platine users should access reader-allowed services") void platine_users_should_access_reader_allowed_services(String endpointURI) throws Exception{ - Jwt jwt = generateJwt(List.of("utilisateur_Platine"), USER_PLATINE); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get(endpointURI).header("Authorization", "bearer token_blabla")) + mockMvc.perform(get(endpointURI) + .with(jwt().authorities(new SimpleGrantedAuthority("ROLE_USER_PLATINE"))) + ) .andExpect(status().is(oneOf(200,404))); } @@ -160,9 +160,9 @@ void platine_users_should_access_reader_allowed_services(String endpointURI) thr @MethodSource("endpointsReader") @DisplayName("Readers should access reader-allowed services") void reader_should_access_reader_allowed_services(String endpointURI) throws Exception{ - Jwt jwt = generateJwt(List.of("lecteur_traiter"), "reader"); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get(endpointURI).header("Authorization", "bearer token_blabla")) + mockMvc.perform(get(endpointURI) + .with(jwt().authorities(new SimpleGrantedAuthority("ROLE_READER"))) + ) .andExpect(status().is(oneOf(200,404))); } @@ -173,9 +173,7 @@ void reader_should_access_reader_allowed_services(String endpointURI) throws Exc @MethodSource("endpointsReader") @DisplayName("User with invalid roles should not access reader-allowed services") void invalid_user_should_not_access_reader_allowed_services(String endpointURI) throws Exception{ - Jwt jwt = generateJwt(List.of("toto"), "invalid_role"); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get(endpointURI).header("Authorization", "bearer token_blabla")) + mockMvc.perform(get(endpointURI) .with(jwt().authorities(new SimpleGrantedAuthority("ROLE_INVALID")))) .andExpect(status().isForbidden()); } @@ -188,9 +186,8 @@ void invalid_user_should_not_access_reader_allowed_services(String endpointURI) @DisplayName("Reader should not access other schedule endpoints") void reader_should_not_access_other_schedules_services() throws Exception{ doNothing().when(scheduleApiPort).deleteSchedule(anyString()); - Jwt jwt = generateJwt(List.of("lecteur_traiter"), READER); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(delete("/schedule/delete?surveyName=ENQ_TEST").header("Authorization", "bearer token_blabla")) + mockMvc.perform(delete("/schedule/delete?surveyName=ENQ_TEST") + .with(jwt().authorities(new SimpleGrantedAuthority("ROLE_READER")))) .andExpect(status().isForbidden()); } @@ -200,9 +197,8 @@ void reader_should_not_access_other_schedules_services() throws Exception{ @Test @DisplayName("Kraftwerk users should access schedules service") void kraftwerk_users_should_not_access_schedules_services() throws Exception{ - Jwt jwt = generateJwt(List.of("utilisateur_Kraftwerk"), USER_KRAFTWERK); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get("/schedule/all").header("Authorization", "bearer token_blabla")) + mockMvc.perform(get("/schedule/all") + .with(jwt().authorities(new SimpleGrantedAuthority("ROLE_USER_KRAFTWERK")))) .andExpect(status().is(oneOf(200,404))); } @@ -214,9 +210,9 @@ void kraftwerk_users_should_not_access_schedules_services() throws Exception{ @Test @DisplayName("Admins should access schedules service") void admins_should_access_schedules_services() throws Exception{ - Jwt jwt = generateJwt(List.of("administrateur_traiter"), ADMIN); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get("/schedule/all").header("Authorization", "bearer token_blabla")) + + mockMvc.perform(get("/schedule/all") + .with(jwt().authorities(new SimpleGrantedAuthority("ROLE_ADMIN")))) .andExpect(status().is(oneOf(200,404))); } @@ -226,9 +222,7 @@ void admins_should_access_schedules_services() throws Exception{ @Test @DisplayName("Invalid roles should not access schedules service") void invalid_roles_should_access_schedules_services() throws Exception{ - Jwt jwt = generateJwt(List.of("invalid_role"), "invalid_role"); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get("/schedule/all").header("Authorization", "bearer token_blabla")) + mockMvc.perform(get("/schedule/all") .with(jwt().authorities(new SimpleGrantedAuthority("ROLE_INVALID")))) .andExpect(status().isForbidden()); } From e09472bdfa7e63798d529a5d3ff3ff80b3f342cf Mon Sep 17 00:00:00 2001 From: Alice Lambois Date: Wed, 16 Jul 2025 11:23:45 +0200 Subject: [PATCH 5/7] Fix tests with multitenant auth --- .../controller/rest/ControllerAccessTest.java | 131 +++++++----------- 1 file changed, 53 insertions(+), 78 deletions(-) diff --git a/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java b/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java index 4be72a65c..2e0a1152a 100644 --- a/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java +++ b/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java @@ -15,7 +15,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; @@ -23,55 +22,43 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.http.HttpMethod; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import java.util.Date; -import java.util.List; -import java.util.Map; import java.util.stream.Stream; import static org.hamcrest.Matchers.oneOf; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.when; import static org.springframework.http.HttpMethod.GET; import static org.springframework.http.HttpMethod.POST; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + @SpringBootTest @AutoConfigureMockMvc @ActiveProfiles("test") @EnableAutoConfiguration(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) class ControllerAccessTest { - // Constants for user roles - private static final String USER_KRAFTWERK = "USER_KRAFTWERK"; - private static final String USER_PLATINE = "USER_PLATINE"; - private static final String ADMIN = "ADMIN"; - private static final String READER = "READER"; - // JWT claim properties loaded from application properties - @Value("${fr.insee.genesis.security.token.oidc-claim-role}") - private String claimRoleDotRoles; - @Value("${fr.insee.genesis.security.token.oidc-claim-username}") - private String claimName; @Autowired private MockMvc mockMvc; // Simulates HTTP requests to the REST endpoints + @MockitoBean - private JwtDecoder jwtDecoder; + private DataProcessingContextApiPort dataProcessingContextApiPort; + +/** MOCKS for initializing context, not used **/ @MockitoBean private MongoTemplate mongoTemplate; @MockitoBean - private DataProcessingContextApiPort dataProcessingContextApiPort; - @MockitoBean private SurveyUnitApiPort surveyUnitApiPort; @MockitoBean private LunaticJsonRawDataApiPort lunaticJsonRawDataApiPort; @@ -122,9 +109,10 @@ private static Stream responseEndpoint() { @MethodSource("endpointsReader") @DisplayName("Admins should access reader-allowed services") void admin_should_access_reader_allowed_services(String endpointURI) throws Exception{ - Jwt jwt = generateJwt(List.of("administrateur_traiter"), ADMIN); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get(endpointURI).header("Authorization", "bearer token_blabla")) + mockMvc.perform( + get(endpointURI).with( + jwt().authorities(new SimpleGrantedAuthority("ROLE_ADMIN"))) + ) .andExpect(status().is(oneOf(200,404))); } @@ -135,10 +123,11 @@ void admin_should_access_reader_allowed_services(String endpointURI) throws Exce @MethodSource("endpointsReader") @DisplayName("Kraftwerk users should access reader-allowed services") void kraftwerk_users_should_access_reader_allowed_services(String endpointURI) throws Exception { - Jwt jwt = generateJwt(List.of("utilisateur_Kraftwerk"), USER_KRAFTWERK); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get(endpointURI).header("Authorization", "bearer token_blabla")) - .andExpect(status().is(oneOf(200, 404))); + mockMvc.perform( + get(endpointURI).with( + jwt().authorities(new SimpleGrantedAuthority("ROLE_USER_KRAFTWERK"))) + ) + .andExpect(status().is(oneOf(200,404))); } /** @@ -148,10 +137,11 @@ void kraftwerk_users_should_access_reader_allowed_services(String endpointURI) t @MethodSource("endpointsReader") @DisplayName("Platine users should access reader-allowed services") void platine_users_should_access_reader_allowed_services(String endpointURI) throws Exception { - Jwt jwt = generateJwt(List.of("utilisateur_Platine"), USER_PLATINE); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get(endpointURI).header("Authorization", "bearer token_blabla")) - .andExpect(status().is(oneOf(200, 404))); + mockMvc.perform( + get(endpointURI).with( + jwt().authorities(new SimpleGrantedAuthority("ROLE_USER_PLATINE"))) + ) + .andExpect(status().is(oneOf(200,404))); } /** @@ -161,10 +151,11 @@ void platine_users_should_access_reader_allowed_services(String endpointURI) thr @MethodSource("endpointsReader") @DisplayName("Readers should access reader-allowed services") void reader_should_access_reader_allowed_services(String endpointURI) throws Exception { - Jwt jwt = generateJwt(List.of("lecteur_traiter"), "reader"); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get(endpointURI).header("Authorization", "bearer token_blabla")) - .andExpect(status().is(oneOf(200, 404))); + mockMvc.perform( + get(endpointURI).with( + jwt().authorities(new SimpleGrantedAuthority("ROLE_READER"))) + ) + .andExpect(status().is(oneOf(200,404))); } /** @@ -174,9 +165,10 @@ void reader_should_access_reader_allowed_services(String endpointURI) throws Exc @MethodSource("endpointsReader") @DisplayName("User with invalid roles should not access reader-allowed services") void invalid_user_should_not_access_reader_allowed_services(String endpointURI) throws Exception { - Jwt jwt = generateJwt(List.of("toto"), "invalid_role"); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get(endpointURI).header("Authorization", "bearer token_blabla")) + mockMvc.perform( + get(endpointURI).with( + jwt().authorities(new SimpleGrantedAuthority("ROLE_invalid"))) + ) .andExpect(status().isForbidden()); } @@ -185,11 +177,12 @@ void invalid_user_should_not_access_reader_allowed_services(String endpointURI) */ @Test @DisplayName("Reader should access schedule/all endpoint") - void reader_should_access_schedules_services() throws Exception { - Jwt jwt = generateJwt(List.of("lecteur_traiter"), READER); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get("/context/schedules/all").header("Authorization", "bearer token_blabla")) - .andExpect(status().is(oneOf(200, 404))); + void reader_should_access_schedules_services() throws Exception{ + mockMvc.perform( + get("/schedule/all").with(jwt() + .authorities(new SimpleGrantedAuthority("ROLE_READER"))) + ) + .andExpect(status().is(oneOf(200,404))); } /** @@ -199,9 +192,10 @@ void reader_should_access_schedules_services() throws Exception { @DisplayName("Reader should not access other schedule endpoints") void reader_should_not_access_other_schedules_services() throws Exception { doNothing().when(dataProcessingContextApiPort).deleteSchedules(anyString()); - Jwt jwt = generateJwt(List.of("lecteur_traiter"), READER); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(delete("/context/schedules?partitionId=ENQ_TEST").header("Authorization", "bearer token_blabla")) + mockMvc.perform( + delete("/context/schedules?partitionId=ENQ_TEST").with( + jwt().authorities(new SimpleGrantedAuthority("ROLE_READER"))) + ) .andExpect(status().isForbidden()); } @@ -213,8 +207,6 @@ void reader_should_not_access_other_schedules_services() throws Exception { @MethodSource("responseEndpoint") @DisplayName("Reader should not access /responses endpoints") void reader_should_not_access_response_services(HttpMethod method,String endpointURI) throws Exception { - Jwt jwt = generateJwt(List.of("lecteur_traiter"), READER); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); MockHttpServletRequestBuilder requestBuilder; if (method == HttpMethod.GET) { requestBuilder = get(endpointURI); @@ -228,8 +220,11 @@ void reader_should_not_access_response_services(HttpMethod method,String endpoin throw new IllegalArgumentException("Unsupported HTTP method: " + method); } - mockMvc.perform(requestBuilder.header("Authorization", "bearer token_blabla")) - .andExpect(status().is(oneOf(200, 404))); + mockMvc.perform( + requestBuilder.with( + jwt().authorities(new SimpleGrantedAuthority("ROLE_READER"))) + ) + .andExpect(status().is(oneOf(200, 404))); } @@ -239,9 +234,10 @@ void reader_should_not_access_response_services(HttpMethod method,String endpoin @Test @DisplayName("Kraftwerk users should access schedules service") void kraftwerk_users_should_not_access_schedules_services() throws Exception { - Jwt jwt = generateJwt(List.of("utilisateur_Kraftwerk"), USER_KRAFTWERK); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get("/context/schedules/all").header("Authorization", "bearer token_blabla")) + mockMvc.perform( + get("/context/schedules/all").with( + jwt().authorities(new SimpleGrantedAuthority("ROLE_USER_KRAFTWERK")) + )) .andExpect(status().is(oneOf(200, 404))); } @@ -251,9 +247,8 @@ void kraftwerk_users_should_not_access_schedules_services() throws Exception { @Test @DisplayName("Admins should access schedules service") void admins_should_access_schedules_services() throws Exception { - Jwt jwt = generateJwt(List.of("administrateur_traiter"), ADMIN); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get("/context/schedules/all").header("Authorization", "bearer token_blabla")) + mockMvc.perform(get("/context/schedules/all").with( + jwt().authorities(new SimpleGrantedAuthority("ROLE_ADMIN")))) .andExpect(status().is(oneOf(200, 404))); } @@ -263,30 +258,10 @@ void admins_should_access_schedules_services() throws Exception { @Test @DisplayName("Invalid roles should not access schedules service") void invalid_roles_should_access_schedules_services() throws Exception { - Jwt jwt = generateJwt(List.of("invalid_role"), "invalid_role"); - when(jwtDecoder.decode(anyString())).thenReturn(jwt); - mockMvc.perform(get("/context/schedules").header("Authorization", "bearer token_blabla")) + mockMvc.perform(get("/context/schedules").with( + jwt().authorities(new SimpleGrantedAuthority("ROLE_invalid")))) .andExpect(status().isForbidden()); } - /** - * Generates a mock JWT token with specified roles and username. - * - * @param roles List of roles assigned to the user. - * @param name Username for the JWT. - * @return A mock Jwt object. - */ - public Jwt generateJwt(List roles, String name) { - Date issuedAt = new Date(); - Date expiresAT = Date.from((new Date()).toInstant().plusSeconds(100)); - var claimRole = claimRoleDotRoles.split("\\.")[0]; - var attributRole = claimRoleDotRoles.split("\\.")[1]; - return new Jwt("token", issuedAt.toInstant(), expiresAT.toInstant(), - Map.of("alg", "RS256", "typ", "JWT"), - Map.of(claimRole, Map.of(attributRole, roles), - claimName, name - ) - ); - } } From 14395a1c710ac665ce96509218bda65e5b7358d5 Mon Sep 17 00:00:00 2001 From: Alice Lambois Date: Wed, 16 Jul 2025 13:40:07 +0200 Subject: [PATCH 6/7] Fix spring security deprecated AntPathRequestMatcher --- .../auth/security/OIDCSecurityConfig.java | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/main/java/fr/insee/genesis/configuration/auth/security/OIDCSecurityConfig.java b/src/main/java/fr/insee/genesis/configuration/auth/security/OIDCSecurityConfig.java index 224780789..4c579249e 100644 --- a/src/main/java/fr/insee/genesis/configuration/auth/security/OIDCSecurityConfig.java +++ b/src/main/java/fr/insee/genesis/configuration/auth/security/OIDCSecurityConfig.java @@ -25,7 +25,6 @@ import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import java.util.Collection; import java.util.Collections; @@ -59,22 +58,20 @@ public class OIDCSecurityConfig { public SecurityFilterChain filterChain(HttpSecurity http, JwtIssuerAuthenticationManagerResolver authenticationManagerResolver) throws Exception { http .csrf(AbstractHttpConfigurer::disable) - .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); - for (var pattern : whitelistMatchers) { - http.authorizeHttpRequests(authorize -> - authorize - .requestMatchers(AntPathRequestMatcher.antMatcher(pattern)).permitAll() - ); - } - http - .authorizeHttpRequests(configure -> configure + .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(authorize -> authorize + // Whitelisted paths sans auth + .requestMatchers(whitelistMatchers).permitAll() + + // Secured reader-access paths .requestMatchers(HttpMethod.GET,"/questionnaires/**").hasRole(String.valueOf(ApplicationRole.READER)) .requestMatchers(HttpMethod.GET,"/modes/**").hasRole(String.valueOf(ApplicationRole.READER)) .requestMatchers(HttpMethod.GET,"/interrogations/**").hasRole(String.valueOf(ApplicationRole.READER)) .requestMatchers(HttpMethod.GET,"/campaigns/**").hasRole(String.valueOf(ApplicationRole.READER)) + + //All others require authentication .anyRequest().authenticated() ) - // .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); .oauth2ResourceServer( oauth2 -> oauth2.authenticationManagerResolver(authenticationManagerResolver)); return http.build(); From 7ef55270950564232eb80da3e380d74ae61d2fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Henninger?= Date: Tue, 22 Jul 2025 10:04:12 +0200 Subject: [PATCH 7/7] test: fix raw response test --- .../controller/rest/ControllerAccessTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java b/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java index b4153b0ae..cc423d269 100644 --- a/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java +++ b/src/test/java/fr/insee/genesis/controller/rest/ControllerAccessTest.java @@ -99,10 +99,10 @@ private static Stream endpointsReader() { private static Stream responseEndpoint() { return Stream.of( - Arguments.of(GET,"/response/lunatic-json/get/unprocessed"), - Arguments.of(GET,"/response//lunatic-json/get/by-interrogation-mode-and-campaign"), - Arguments.of(POST,"/response//lunatic-json/process"), - Arguments.of(GET,"/response//lunatic-json/campaignId=TOTO") + Arguments.of(GET,"/responses/raw/lunatic-json/get/unprocessed"), + Arguments.of(GET,"/responses/raw/lunatic-json/get/by-interrogation-mode-and-campaign?interrogationId=test&campaignName=test&mode=WEB"), + Arguments.of(GET,"/responses/raw/lunatic-json/campaignId=TOTO"), + Arguments.of(POST,"/responses/raw/lunatic-json/process?campaignName=test&questionnaireId=idTest") ); } @@ -211,11 +211,12 @@ void reader_should_not_access_other_schedules_services() throws Exception { @MethodSource("responseEndpoint") @DisplayName("Reader should not access /responses endpoints") void reader_should_not_access_response_services(HttpMethod method,String endpointURI) throws Exception { + String requestBody = "[\"id1\"]"; MockHttpServletRequestBuilder requestBuilder; if (method == HttpMethod.GET) { requestBuilder = get(endpointURI); } else if (method == HttpMethod.POST) { - requestBuilder = post(endpointURI); + requestBuilder = post(endpointURI).contentType("application/json").content(requestBody); } else if (method == HttpMethod.PUT) { requestBuilder = put(endpointURI); } else if (method == HttpMethod.DELETE) { @@ -228,7 +229,7 @@ void reader_should_not_access_response_services(HttpMethod method,String endpoin requestBuilder.with( jwt().authorities(new SimpleGrantedAuthority("ROLE_READER"))) ) - .andExpect(status().is(oneOf(200, 404))); + .andExpect(status().isForbidden()); }