From 1ac5d7c254d393f80953eb8a31a3d5e984cf8952 Mon Sep 17 00:00:00 2001 From: Fabrice Bibonne Date: Tue, 8 Oct 2024 17:30:15 +0200 Subject: [PATCH] Fix cors config (#777) * fix(bauhaus cors config) * test (cors : tests skeletons) * test (cors : tests ok) --------- Co-authored-by: Bibonne Fabrice --- .../security/CommonSecurityConfiguration.java | 37 ++++++----- .../resources/bauhaus-local-dev.properties | 4 +- ...DefaultToAppHostPropertyForOriginTest.java | 64 +++++++++++++++++++ .../rmes/integration/CorsConfigTest.java | 63 ++++++++++++++++++ 4 files changed, 150 insertions(+), 18 deletions(-) create mode 100644 src/test/java/fr/insee/rmes/integration/CorsConfigDefaultToAppHostPropertyForOriginTest.java create mode 100644 src/test/java/fr/insee/rmes/integration/CorsConfigTest.java diff --git a/src/main/java/fr/insee/rmes/config/auth/security/CommonSecurityConfiguration.java b/src/main/java/fr/insee/rmes/config/auth/security/CommonSecurityConfiguration.java index df1a59e6c..81ff21915 100644 --- a/src/main/java/fr/insee/rmes/config/auth/security/CommonSecurityConfiguration.java +++ b/src/main/java/fr/insee/rmes/config/auth/security/CommonSecurityConfiguration.java @@ -9,16 +9,14 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.util.List; import java.util.Optional; import static fr.insee.rmes.config.PropertiesKeys.CORS_ALLOWED_ORIGIN; -@Configuration +@Configuration(proxyBeanMethods = false) @EnableWebSecurity @EnableMethodSecurity(securedEnabled = true) public class CommonSecurityConfiguration { @@ -27,25 +25,32 @@ public class CommonSecurityConfiguration { public static final String DEFAULT_ROLE_PREFIX = "" ; private final Optional allowedOrigin ; + private final String appHost; - public CommonSecurityConfiguration(@Value("${"+CORS_ALLOWED_ORIGIN+"}") Optional allowedOrigin){ + public CommonSecurityConfiguration(@Value("${"+CORS_ALLOWED_ORIGIN+"}") Optional allowedOrigin, @Value("${fr.insee.rmes.bauhaus.appHost}") String appHost) { this.allowedOrigin=allowedOrigin; + this.appHost = appHost; } @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - logger.info("Allowed origins : {}", allowedOrigin); - configuration.setAllowedOrigins(List.of(allowedOrigin.orElse("*"))); - configuration.setAllowedMethods(List.of("*")); - configuration.setAllowedHeaders(List.of("*")); - UrlBasedCorsConfigurationSource source = new - UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); - return source; + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + + @Override + public void addCorsMappings(CorsRegistry registry) { + logger.info("Allowed origins : {}", allowedOrigin); + registry.addMapping("/**") + .allowedOrigins(allowedOrigin.orElse(appHost)) + .allowedMethods("*") + .allowedHeaders("*") + .maxAge(3600); + } + + }; } + @Bean public StampFromPrincipal stampFromPrincipal(UserDecoder userDecoder){ return principal -> { diff --git a/src/main/resources/bauhaus-local-dev.properties b/src/main/resources/bauhaus-local-dev.properties index e4600a2df..5a626d055 100644 --- a/src/main/resources/bauhaus-local-dev.properties +++ b/src/main/resources/bauhaus-local-dev.properties @@ -1,8 +1,8 @@ # Default value for cors filter : empty means no CORS allowed : -fr.insee.rmes.bauhaus.cors.allowedOrigin=* +fr.insee.rmes.bauhaus.cors.allowedOrigin=http://localhost:3000/,http://localhost:8080/ # Properties Front -fr.insee.rmes.bauhaus.appHost = https://localhost:3000/#/ +fr.insee.rmes.bauhaus.appHost = http://localhost:3000/#/ # Env (local --> NoAuth | qf, pre-prod, prod --> PROD) fr.insee.rmes.bauhaus.env = NoAuth diff --git a/src/test/java/fr/insee/rmes/integration/CorsConfigDefaultToAppHostPropertyForOriginTest.java b/src/test/java/fr/insee/rmes/integration/CorsConfigDefaultToAppHostPropertyForOriginTest.java new file mode 100644 index 000000000..10428bfca --- /dev/null +++ b/src/test/java/fr/insee/rmes/integration/CorsConfigDefaultToAppHostPropertyForOriginTest.java @@ -0,0 +1,64 @@ +package fr.insee.rmes.integration; + +import fr.insee.rmes.config.BaseConfigForMvcTests; +import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; +import fr.insee.rmes.external.services.authentication.stamps.StampsService; +import fr.insee.rmes.webservice.PublicResources; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = PublicResources.class, properties = { + "fr.insee.rmes.bauhaus.cors.allowedOrigin=", + "fr.insee.rmes.bauhaus.appHost=http://localhost:80", + "logging.level.org.springframework.web=debug"}) +@Import({BaseConfigForMvcTests.class, CommonSecurityConfiguration.class}) +class CorsConfigDefaultToAppHostPropertyForOriginTest { + + @Autowired + // Perform mock request on http://localhost:80 + private MockMvc mockMvc; + + @MockBean + private StampsService stampsService; + + @ValueSource(strings = { + "http://bauhaus", + "https://localhost:80", + "http://localhost:8080" + }) + @ParameterizedTest + void whenPerformCorsRequest_shouldBeDenied(String origin) throws Exception { + mockMvc.perform(get("/stamps") + .header("Accept", "application/json") + .header("Origin", origin) + ).andExpect(status().isForbidden()) + .andExpect(content().string("Invalid CORS request")); + + } + + @Test + void whenPerformNoCorsRequest_shouldBeOK() throws Exception { + mockMvc.perform(get("/stamps") + .header("Accept", "application/json") + ).andExpect(status().isOk()); + } + + @Test + void whenHeaderOriginIsOK_shouldBeOK() throws Exception { + mockMvc.perform(get("/stamps") + .header("Accept", "application/json") + .header("Origin", "http://localhost:80") + ).andExpect(status().isOk()); + } + +} diff --git a/src/test/java/fr/insee/rmes/integration/CorsConfigTest.java b/src/test/java/fr/insee/rmes/integration/CorsConfigTest.java new file mode 100644 index 000000000..45f98b2f9 --- /dev/null +++ b/src/test/java/fr/insee/rmes/integration/CorsConfigTest.java @@ -0,0 +1,63 @@ +package fr.insee.rmes.integration; + +import fr.insee.rmes.config.BaseConfigForMvcTests; +import fr.insee.rmes.config.auth.security.CommonSecurityConfiguration; +import fr.insee.rmes.external.services.authentication.stamps.StampsService; +import fr.insee.rmes.webservice.PublicResources; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = PublicResources.class, properties = { + "fr.insee.rmes.bauhaus.cors.allowedOrigin=http://localhost:80", + "fr.insee.rmes.bauhaus.appHost=http://myfront:80", + "logging.level.org.springframework.web=debug"}) +@Import({BaseConfigForMvcTests.class, CommonSecurityConfiguration.class}) +class CorsConfigTest { + + @Autowired + // Perform mock request on http://localhost:80 + private MockMvc mockMvc; + + @MockBean + private StampsService stampsService; + + @ValueSource(strings = { + "http://bauhaus", + "https://localhost:80", + "http://localhost:8080" + }) + @ParameterizedTest + void whenPerformCorsRequest_shouldBeDenied(String origin) throws Exception { + mockMvc.perform(get("/stamps") + .header("Accept", "application/json") + .header("Origin", origin) + ).andExpect(status().isForbidden()) + .andExpect(content().string("Invalid CORS request")); + } + + @Test + void whenPerformNoCorsRequest_shouldBeOK() throws Exception { + mockMvc.perform(get("/stamps") + .header("Accept", "application/json") + ).andExpect(status().isOk()); + } + + @Test + void whenHeaderOriginIsOK_shouldBeOK() throws Exception { + mockMvc.perform(get("/stamps") + .header("Accept", "application/json") + .header("Origin", "http://localhost:80") + ).andExpect(status().isOk()); + } + +}