|
|
|
|
@@ -0,0 +1,142 @@
|
|
|
|
|
package com.cloudhandson.vpdbackoffice.config;
|
|
|
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
|
import static org.hamcrest.Matchers.containsString;
|
|
|
|
|
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
|
|
|
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
|
|
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
|
|
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
|
|
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
|
|
|
|
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import org.junit.jupiter.api.AfterEach;
|
|
|
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
|
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
|
|
|
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
|
|
|
import org.springframework.boot.context.properties.bind.Binder;
|
|
|
|
|
import org.springframework.boot.env.YamlPropertySourceLoader;
|
|
|
|
|
import org.springframework.boot.test.util.TestPropertyValues;
|
|
|
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
|
|
import org.springframework.context.annotation.Import;
|
|
|
|
|
import org.springframework.core.env.MapPropertySource;
|
|
|
|
|
import org.springframework.core.env.StandardEnvironment;
|
|
|
|
|
import org.springframework.core.io.ClassPathResource;
|
|
|
|
|
import org.springframework.http.MediaType;
|
|
|
|
|
import org.springframework.mock.web.MockServletContext;
|
|
|
|
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
|
|
|
import org.springframework.test.web.servlet.MockMvc;
|
|
|
|
|
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
|
|
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
|
|
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
|
|
|
|
import org.springframework.web.filter.ForwardedHeaderFilter;
|
|
|
|
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
|
|
|
|
|
|
|
|
|
class TransportSecurityTest {
|
|
|
|
|
|
|
|
|
|
private AnnotationConfigWebApplicationContext context;
|
|
|
|
|
private MockMvc mockMvc;
|
|
|
|
|
|
|
|
|
|
@BeforeEach
|
|
|
|
|
void setUp() {
|
|
|
|
|
context = new AnnotationConfigWebApplicationContext();
|
|
|
|
|
context.setServletContext(new MockServletContext());
|
|
|
|
|
TestPropertyValues.of(
|
|
|
|
|
"backoffice.security.admin-user=admin",
|
|
|
|
|
"backoffice.security.admin-password=test-password",
|
|
|
|
|
"backoffice.security.require-https=true",
|
|
|
|
|
"backoffice.token.max-days=365",
|
|
|
|
|
"backoffice.ords.base-url=https://ords.example.test",
|
|
|
|
|
"backoffice.ords.timeout=10s",
|
|
|
|
|
"backoffice.ai.enabled=false",
|
|
|
|
|
"backoffice.ai.timeout=30s"
|
|
|
|
|
).applyTo(context);
|
|
|
|
|
context.register(TestWebConfig.class);
|
|
|
|
|
context.refresh();
|
|
|
|
|
|
|
|
|
|
mockMvc = MockMvcBuilders.webAppContextSetup(context)
|
|
|
|
|
.addFilters(new ForwardedHeaderFilter())
|
|
|
|
|
.apply(springSecurity())
|
|
|
|
|
.build();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@AfterEach
|
|
|
|
|
void tearDown() {
|
|
|
|
|
context.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void directHttpRequestRedirectsToHttps() throws Exception {
|
|
|
|
|
mockMvc.perform(get("/login"))
|
|
|
|
|
.andExpect(status().is3xxRedirection())
|
|
|
|
|
.andExpect(redirectedUrl("https://localhost/login"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void forwardedHttpsRequestIsAcceptedAndReturnsHsts() throws Exception {
|
|
|
|
|
mockMvc.perform(get("/login")
|
|
|
|
|
.header("X-Forwarded-Proto", "https")
|
|
|
|
|
.header("X-Forwarded-Host", "admin.example.test"))
|
|
|
|
|
.andExpect(status().isOk())
|
|
|
|
|
.andExpect(header().string(
|
|
|
|
|
"Strict-Transport-Security",
|
|
|
|
|
containsString("max-age=31536000")
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void forwardedHostIsUsedForAuthenticationRedirect() throws Exception {
|
|
|
|
|
mockMvc.perform(get("/")
|
|
|
|
|
.accept(MediaType.TEXT_HTML)
|
|
|
|
|
.header("X-Forwarded-Proto", "https")
|
|
|
|
|
.header("X-Forwarded-Host", "admin.example.test"))
|
|
|
|
|
.andExpect(status().is3xxRedirection())
|
|
|
|
|
.andExpect(redirectedUrl("https://admin.example.test/login"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void productionSessionCookieSettingsAreBound() throws Exception {
|
|
|
|
|
var environment = new StandardEnvironment();
|
|
|
|
|
environment.getPropertySources().addFirst(new MapPropertySource("test-overrides", Map.of(
|
|
|
|
|
"BACKOFFICE_FORWARD_HEADERS_STRATEGY", "framework",
|
|
|
|
|
"BACKOFFICE_SESSION_COOKIE_SECURE", "true"
|
|
|
|
|
)));
|
|
|
|
|
var loader = new YamlPropertySourceLoader();
|
|
|
|
|
for (var source : loader.load("application", new ClassPathResource("application.yml"))) {
|
|
|
|
|
environment.getPropertySources().addLast(source);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ServerProperties properties = Binder.get(environment)
|
|
|
|
|
.bind("server", ServerProperties.class)
|
|
|
|
|
.orElseThrow(() -> new AssertionError("server properties were not bound"));
|
|
|
|
|
var cookie = properties.getServlet().getSession().getCookie();
|
|
|
|
|
|
|
|
|
|
assertThat(cookie.getSecure()).isTrue();
|
|
|
|
|
assertThat(cookie.getHttpOnly()).isTrue();
|
|
|
|
|
assertThat(cookie.getSameSite().attributeValue()).isEqualTo("Lax");
|
|
|
|
|
assertThat(properties.getForwardHeadersStrategy().name()).isEqualTo("FRAMEWORK");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Configuration(proxyBeanMethods = false)
|
|
|
|
|
@EnableWebMvc
|
|
|
|
|
@EnableWebSecurity
|
|
|
|
|
@EnableConfigurationProperties(BackofficeProperties.class)
|
|
|
|
|
@Import({SecurityConfig.class, TestController.class})
|
|
|
|
|
static class TestWebConfig {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@RestController
|
|
|
|
|
static class TestController {
|
|
|
|
|
|
|
|
|
|
@GetMapping("/login")
|
|
|
|
|
String login() {
|
|
|
|
|
return "login";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@GetMapping("/")
|
|
|
|
|
String home() {
|
|
|
|
|
return "home";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|