add CAS authentication #138
@ -0,0 +1,35 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class CasAuthenticationFilter implements Filter {
|
||||
|
||||
@Autowired
|
||||
private CasServiceTicketValidator ticketValidator;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) {
|
||||
final var httpRequest = (HttpServletRequest) request;
|
||||
final var httpResponse = (HttpServletResponse) response;
|
||||
|
||||
final var ticket = httpRequest.getHeader("Authorization");
|
||||
|
||||
if (ticket == null || !ticketValidator.validateTicket(ticket)) {
|
||||
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
return;
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Service
|
||||
public class CasServiceTicketValidator {
|
||||
|
||||
@Value("${hsadminng.cas.server-url}")
|
||||
private String casServerUrl;
|
||||
|
||||
@Value("${hsadminng.cas.service-url}")
|
||||
private String serviceUrl;
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
@SneakyThrows
|
||||
public boolean validateTicket(final String ticket) {
|
||||
if (casServerUrl.equals("fake") && ticket.equals("test")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final var url = casServerUrl + "/p3/serviceValidate" +
|
||||
"?service=" + URLEncoder.encode(serviceUrl, StandardCharsets.UTF_8) +
|
||||
"&ticket=" + URLEncoder.encode(ticket, StandardCharsets.UTF_8);
|
||||
|
||||
final var response = restTemplate.getForObject(url, String.class);
|
||||
|
||||
final var doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
|
||||
.parse(new java.io.ByteArrayInputStream(response.getBytes()));
|
||||
|
||||
return doc.getElementsByTagName("cas:authenticationSuccess").getLength() > 0;
|
||||
}
|
||||
}
|
@ -36,6 +36,9 @@ liquibase:
|
||||
hsadminng:
|
||||
postgres:
|
||||
leakproof:
|
||||
cas:
|
||||
server-url: https://cas.example.com/cas
|
||||
service-url: http://localhost:8080/api # TODO.conf: deployment target
|
||||
|
||||
metrics:
|
||||
distribution:
|
||||
|
@ -0,0 +1,31 @@
|
||||
package net.hostsharing.hsadminng.config;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@TestPropertySource(properties = {"server.port=0"})
|
||||
// IMPORTANT: To test prod config, do not use test profile!
|
||||
class CasAuthenticationFilterIntegrationTest {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int serverPort;
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate restTemplate;
|
||||
|
||||
@Test
|
||||
public void shouldRejectRequest() {
|
||||
final var result = this.restTemplate.getForEntity(
|
||||
"http://localhost:" + this.serverPort + "/api/ping", String.class);
|
||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
}
|
@ -8,13 +8,16 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@TestPropertySource(properties = {"management.port=0", "server.port=0"})
|
||||
@TestPropertySource(properties = {"management.port=0", "server.port=0", "hsadminng.cas.server-url=fake"})
|
||||
// IMPORTANT: To test prod config, do not use test profile!
|
||||
class WebSecurityConfigIntegrationTest {
|
||||
|
||||
@ -29,8 +32,18 @@ class WebSecurityConfigIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void shouldSupportPingEndpoint() {
|
||||
final var result = this.restTemplate.getForEntity(
|
||||
"http://localhost:" + this.serverPort + "/api/ping", String.class);
|
||||
// fake Authorization header
|
||||
final var headers = new HttpHeaders();
|
||||
headers.set("Authorization", "test");
|
||||
|
||||
// http request
|
||||
final var result = restTemplate.exchange(
|
||||
"http://localhost:" + this.serverPort + "/api/ping",
|
||||
HttpMethod.GET,
|
||||
new HttpEntity<Object>(null, headers),
|
||||
String.class
|
||||
);
|
||||
|
||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(result.getBody()).startsWith("pong");
|
||||
}
|
||||
|
@ -160,6 +160,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
.GET()
|
||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
||||
.header("Authorization", "test")
|
||||
.timeout(seconds(10))
|
||||
.build();
|
||||
final var response = client.send(request, BodyHandlers.ofString());
|
||||
@ -175,6 +176,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
||||
.header("Authorization", "test")
|
||||
.timeout(seconds(10))
|
||||
.build();
|
||||
final var response = client.send(request, BodyHandlers.ofString());
|
||||
@ -190,6 +192,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
||||
.header("Authorization", "test")
|
||||
.timeout(seconds(10))
|
||||
.build();
|
||||
final var response = client.send(request, BodyHandlers.ofString());
|
||||
@ -204,6 +207,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
||||
.header("Authorization", "test")
|
||||
.timeout(seconds(10))
|
||||
.build();
|
||||
final var response = client.send(request, BodyHandlers.ofString());
|
||||
|
@ -51,3 +51,7 @@ testcontainers:
|
||||
network:
|
||||
mode: host
|
||||
|
||||
hsadminng:
|
||||
cas:
|
||||
server-url: fake
|
||||
service-url: http://localhost:8080/api # not really used in test config
|
||||
|
Loading…
Reference in New Issue
Block a user