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:
|
hsadminng:
|
||||||
postgres:
|
postgres:
|
||||||
leakproof:
|
leakproof:
|
||||||
|
cas:
|
||||||
|
server-url: https://cas.example.com/cas
|
||||||
|
service-url: http://localhost:8080/api # TODO.conf: deployment target
|
||||||
|
|
||||||
metrics:
|
metrics:
|
||||||
distribution:
|
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.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
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.http.HttpStatus;
|
||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
@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!
|
// IMPORTANT: To test prod config, do not use test profile!
|
||||||
class WebSecurityConfigIntegrationTest {
|
class WebSecurityConfigIntegrationTest {
|
||||||
|
|
||||||
@ -29,8 +32,18 @@ class WebSecurityConfigIntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldSupportPingEndpoint() {
|
public void shouldSupportPingEndpoint() {
|
||||||
final var result = this.restTemplate.getForEntity(
|
// fake Authorization header
|
||||||
"http://localhost:" + this.serverPort + "/api/ping", String.class);
|
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.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
assertThat(result.getBody()).startsWith("pong");
|
assertThat(result.getBody()).startsWith("pong");
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
|||||||
.GET()
|
.GET()
|
||||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||||
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
||||||
|
.header("Authorization", "test")
|
||||||
.timeout(seconds(10))
|
.timeout(seconds(10))
|
||||||
.build();
|
.build();
|
||||||
final var response = client.send(request, BodyHandlers.ofString());
|
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))
|
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
||||||
|
.header("Authorization", "test")
|
||||||
.timeout(seconds(10))
|
.timeout(seconds(10))
|
||||||
.build();
|
.build();
|
||||||
final var response = client.send(request, BodyHandlers.ofString());
|
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))
|
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
||||||
|
.header("Authorization", "test")
|
||||||
.timeout(seconds(10))
|
.timeout(seconds(10))
|
||||||
.build();
|
.build();
|
||||||
final var response = client.send(request, BodyHandlers.ofString());
|
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))
|
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
.header("current-subject", ScenarioTest.RUN_AS_USER)
|
||||||
|
.header("Authorization", "test")
|
||||||
.timeout(seconds(10))
|
.timeout(seconds(10))
|
||||||
.build();
|
.build();
|
||||||
final var response = client.send(request, BodyHandlers.ofString());
|
final var response = client.send(request, BodyHandlers.ofString());
|
||||||
|
@ -51,3 +51,7 @@ testcontainers:
|
|||||||
network:
|
network:
|
||||||
mode: host
|
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