diff --git a/ehealthid-rp/src/main/java/com/oviva/ehealthid/relyingparty/testenv/Environment.java b/ehealthid-rp/src/main/java/com/oviva/ehealthid/relyingparty/testenv/Environment.java index 34c6ac9..4eb2e7a 100644 --- a/ehealthid-rp/src/main/java/com/oviva/ehealthid/relyingparty/testenv/Environment.java +++ b/ehealthid-rp/src/main/java/com/oviva/ehealthid/relyingparty/testenv/Environment.java @@ -4,6 +4,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Properties; +import java.util.function.Function; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,19 +13,23 @@ public class Environment { private static Logger logger = LoggerFactory.getLogger(Environment.class); + // for easier mocking + static Function getenv = System::getenv; + static Supplier envPath = () -> Path.of("./env.properties"); + private Environment() {} public static String gematikAuthHeader() { // for testing in TU & RU var name = "GEMATIK_AUTH_HEADER"; - var header = System.getenv(name); + var header = getenv.apply(name); if (header != null && !header.isBlank()) { return header; } var prop = new Properties(); - try (var br = Files.newBufferedReader(Path.of("./env.properties"))) { + try (var br = Files.newBufferedReader(envPath.get())) { prop.load(br); return prop.getProperty(name); } catch (IOException e) { diff --git a/ehealthid-rp/src/main/java/com/oviva/ehealthid/relyingparty/testenv/GematikHeaderDecoratorHttpClient.java b/ehealthid-rp/src/main/java/com/oviva/ehealthid/relyingparty/testenv/GematikHeaderDecoratorHttpClient.java index 54cb5a4..cdc5a54 100644 --- a/ehealthid-rp/src/main/java/com/oviva/ehealthid/relyingparty/testenv/GematikHeaderDecoratorHttpClient.java +++ b/ehealthid-rp/src/main/java/com/oviva/ehealthid/relyingparty/testenv/GematikHeaderDecoratorHttpClient.java @@ -6,6 +6,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Currently Gematik's IdP is not publicly accessible and needs a special header. For simplicity, we + * just inject the header if the host matches. + */ public class GematikHeaderDecoratorHttpClient implements HttpClient { private static final Logger logger = diff --git a/ehealthid-rp/src/test/java/com/oviva/ehealthid/relyingparty/testenv/EnvironmentTest.java b/ehealthid-rp/src/test/java/com/oviva/ehealthid/relyingparty/testenv/EnvironmentTest.java new file mode 100644 index 0000000..bc078c9 --- /dev/null +++ b/ehealthid-rp/src/test/java/com/oviva/ehealthid/relyingparty/testenv/EnvironmentTest.java @@ -0,0 +1,62 @@ +package com.oviva.ehealthid.relyingparty.testenv; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class EnvironmentTest { + + @Test + void test_envVariable() { + + var authHeader = "mySpecialS3cr3t"; + var env = Map.of("GEMATIK_AUTH_HEADER", authHeader); + + Environment.getenv = env::get; + + var got = Environment.gematikAuthHeader(); + + assertEquals(authHeader, got); + } + + @Test + void test_envFile(@TempDir Path tempDir) throws IOException { + + var authHeader = "t0k3n"; + + var f = tempDir.resolve("env.properties"); + Files.write( + f, + """ + GEMATIK_AUTH_HEADER=%s + """ + .formatted(authHeader) + .getBytes(StandardCharsets.UTF_8)); + + Environment.getenv = s -> null; + Environment.envPath = () -> f; + + var got = Environment.gematikAuthHeader(); + + assertEquals(authHeader, got); + } + + @Test + void test_envFile_notFound(@TempDir Path tempDir) throws IOException { + + var f = tempDir.resolve("does_not_exist"); + + Environment.getenv = s -> null; + Environment.envPath = () -> f; + + var got = Environment.gematikAuthHeader(); + + assertNull(got); + } +} diff --git a/ehealthid-rp/src/test/java/com/oviva/ehealthid/relyingparty/testenv/GematikHeaderDecoratorHttpClientTest.java b/ehealthid-rp/src/test/java/com/oviva/ehealthid/relyingparty/testenv/GematikHeaderDecoratorHttpClientTest.java new file mode 100644 index 0000000..18df494 --- /dev/null +++ b/ehealthid-rp/src/test/java/com/oviva/ehealthid/relyingparty/testenv/GematikHeaderDecoratorHttpClientTest.java @@ -0,0 +1,108 @@ +package com.oviva.ehealthid.relyingparty.testenv; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.oviva.ehealthid.fedclient.api.HttpClient; +import java.net.URI; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.ArgumentCaptor; + +class GematikHeaderDecoratorHttpClientTest { + + @Test + void noDecoration() { + + var env = Map.of("GEMATIK_AUTH_HEADER", "myheader"); + Environment.getenv = env::get; + + var httpClient = mock(HttpClient.class); + var client = new GematikHeaderDecoratorHttpClient(httpClient); + + var uri = URI.create("https://example.com"); + var req = new HttpClient.Request(uri, "GET", List.of(), null); + + var res = client.call(req); + + var captor = ArgumentCaptor.forClass(HttpClient.Request.class); + verify(httpClient).call(captor.capture()); + + var decoratedReq = captor.getValue(); + + var hasAuthHeader = + decoratedReq.headers().stream().anyMatch(h -> "X-Authorization".equals(h.name())); + + assertFalse(hasAuthHeader); + } + + @Test + void worksIfHeaderMissing() { + + var env = Map.of("GEMATIK_AUTH_HEADER", ""); + Environment.getenv = env::get; + + var httpClient = mock(HttpClient.class); + var client = new GematikHeaderDecoratorHttpClient(httpClient); + + var uri = URI.create("https://gsi-ref-mtls.dev.gematik.solutions/PAR_Auth"); + var req = new HttpClient.Request(uri, "GET", List.of(), null); + + // when + var res = client.call(req); + + // then + var captor = ArgumentCaptor.forClass(HttpClient.Request.class); + verify(httpClient).call(captor.capture()); + + var decoratedReq = captor.getValue(); + + var hasAuthHeader = + decoratedReq.headers().stream().anyMatch(h -> "X-Authorization".equals(h.name())); + + assertFalse(hasAuthHeader); + } + + @ParameterizedTest + @ValueSource( + strings = { + "https://gsi-ref.dev.gematik.solutions/.well-known/openid-federation", + "https://gsi-ref-mtls.dev.gematik.solutions/PAR_Auth", + "https://gsi.dev.gematik.solutions/.well-known/openid-federation", + }) + void decoratesRequest(String rawUri) { + + var authHeader = "mySpecialS3cr3t"; + var env = Map.of("GEMATIK_AUTH_HEADER", authHeader); + Environment.getenv = env::get; + + var mockRes = mock(HttpClient.Response.class); + var httpClient = mock(HttpClient.class); + when(httpClient.call(any())).thenReturn(mockRes); + var client = new GematikHeaderDecoratorHttpClient(httpClient); + + var uri = URI.create(rawUri); + var req = new HttpClient.Request(uri, "GET", List.of(), null); + + // when + var res = client.call(req); + + // then + var captor = ArgumentCaptor.forClass(HttpClient.Request.class); + verify(httpClient).call(captor.capture()); + + var decoratedReq = captor.getValue(); + + var header = + decoratedReq.headers().stream() + .filter(h -> "X-Authorization".equals(h.name())) + .findFirst() + .orElseThrow(); + + assertEquals(authHeader, header.value()); + assertEquals(mockRes, res); + } +}