diff --git a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/AbstractIntegrationTest.java b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/AbstractIntegrationTest.java index 0d1e1766c..4676c760b 100644 --- a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/AbstractIntegrationTest.java +++ b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/AbstractIntegrationTest.java @@ -5,9 +5,6 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.net.URI; import java.nio.channels.ServerSocketChannel; import java.nio.charset.StandardCharsets; @@ -42,10 +39,6 @@ import org.eclipse.jetty.server.ServerConnector; import org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Endpoint; -import org.hl7.fhir.r4.model.Extension; -import org.hl7.fhir.r4.model.Organization; -import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Subscription; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -64,7 +57,6 @@ import de.hsheilbronn.mi.utils.test.PostgreSqlContainerLiquibaseTemplateClassRule; import de.hsheilbronn.mi.utils.test.PostgresTemplateRule; import dev.dsf.bpe.dao.AbstractDbTest; -import dev.dsf.bpe.integration.X509Certificates.CertificateAndPrivateKey; import dev.dsf.common.auth.ClientCertificateAuthenticator; import dev.dsf.common.auth.DelegatingAuthenticator; import dev.dsf.common.auth.DsfLoginService; @@ -115,12 +107,11 @@ public abstract class AbstractIntegrationTest extends AbstractDbTest private static final Path EMPTY_PROCESS_DIRECTORY = Paths.get("target", UUID.randomUUID().toString()); private static final List DIRECTORIES_TO_DELETE = List.of(EMPTY_PROCESS_DIRECTORY); - private static final Path FHIR_BUNDLE_FILE = Paths.get("target", UUID.randomUUID().toString() + ".xml"); private static final Path ALLOWED_BPE_CLASSES_LIST_FILE_V1 = Paths.get("target", UUID.randomUUID().toString() + ".list"); private static final Path ALLOWED_BPE_CLASSES_LIST_FILE_V2 = Paths.get("target", UUID.randomUUID().toString() + ".list"); - private static final List FILES_TO_DELETE = List.of(FHIR_BUNDLE_FILE, ALLOWED_BPE_CLASSES_LIST_FILE_V1, + private static final List FILES_TO_DELETE = List.of(ALLOWED_BPE_CLASSES_LIST_FILE_V1, ALLOWED_BPE_CLASSES_LIST_FILE_V2); protected static final FhirContext fhirContext = FhirContext.forR4(); @@ -141,10 +132,6 @@ public static void beforeClass() throws Exception ServerSocketChannel fhirStatusConnectorChannel = JettyServer.serverSocketChannel("127.0.0.1"); ServerSocketChannel fhirApiConnectorChannel = JettyServer.serverSocketChannel("127.0.0.1"); - logger.info("Creating FHIR Bundle ..."); - createTestBundle(certificates.getClientCertificate(), certificates.getExternalClientCertificate(), - fhirApiConnectorChannel.socket().getLocalPort()); - String fhirBaseUrl = "https://localhost:" + fhirApiConnectorChannel.socket().getLocalPort() + FHIR_CONTEXT_PATH; logger.info("Creating webservice client ..."); @@ -239,11 +226,6 @@ private static WebsocketClient createWebsocketClient(int fhirApiPort, KeyStore t keyStorePassword, null, null, null, "Integration Test Client", subscriptionIdPart); } - protected static IParser newXmlParser() - { - return newParser(fhirContext::newXmlParser); - } - protected static IParser newJsonParser() { return newParser(fhirContext::newJsonParser); @@ -258,58 +240,6 @@ private static IParser newParser(Supplier supplier) return p; } - private static void createTestBundle(CertificateAndPrivateKey clientCertificate, - CertificateAndPrivateKey externalClientCertificate, int fhirApiPort) - { - Path testBundleTemplateFile = Paths.get("src/test/resources/integration/test-bundle.xml"); - - Bundle testBundle = readBundle(testBundleTemplateFile, newXmlParser()); - - Organization organization = (Organization) testBundle.getEntry().get(0).getResource(); - Extension thumbprintExtension = organization - .getExtensionByUrl("http://dsf.dev/fhir/StructureDefinition/extension-certificate-thumbprint"); - thumbprintExtension.setValue(new StringType(clientCertificate.certificateSha512ThumbprintHex())); - - Endpoint endpoint = (Endpoint) testBundle.getEntry().get(1).getResource(); - endpoint.setAddress("https://localhost:" + fhirApiPort + "/fhir"); - - Organization externalOrganization = (Organization) testBundle.getEntry().get(2).getResource(); - Extension externalThumbprintExtension = externalOrganization - .getExtensionByUrl("http://dsf.dev/fhir/StructureDefinition/extension-certificate-thumbprint"); - externalThumbprintExtension - .setValue(new StringType(externalClientCertificate.certificateSha512ThumbprintHex())); - - writeBundle(FHIR_BUNDLE_FILE, testBundle); - } - - private static Bundle readBundle(Path bundleTemplateFile, IParser parser) - { - try (InputStream in = Files.newInputStream(bundleTemplateFile)) - { - Bundle bundle = parser.parseResource(Bundle.class, in); - return referenceCleaner.cleanReferenceResourcesIfBundle(bundle); - } - catch (IOException e) - { - logger.error("Error while reading bundle from {}", bundleTemplateFile.toString(), e); - throw new RuntimeException(e); - } - } - - private static void writeBundle(Path bundleFile, Bundle bundle) - { - try (OutputStream out = Files.newOutputStream(bundleFile); - OutputStreamWriter writer = new OutputStreamWriter(out)) - { - newXmlParser().encodeResourceToWriter(bundle, writer); - } - catch (IOException e) - { - logger.error("Error while writing bundle to {}", bundleFile.toString(), e); - throw new RuntimeException(e); - } - } - private static JettyServer startFhirServer(ServerSocketChannel statusConnectorChannel, ServerSocketChannel apiConnectorChannel, String baseUrl) throws Exception { @@ -328,7 +258,7 @@ private static JettyServer startFhirServer(ServerSocketChannel statusConnectorCh initParameters.put("dev.dsf.fhir.server.base.url", baseUrl); initParameters.put("dev.dsf.fhir.server.organization.identifier.value", "Test_Organization"); - initParameters.put("dev.dsf.fhir.server.init.bundle", FHIR_BUNDLE_FILE.toString()); + initParameters.put("dev.dsf.fhir.server.init.bundle", "src/test/resources/integration/test-bundle.xml"); initParameters.put("dev.dsf.fhir.client.trust.server.certificate.cas", certificates.getCaCertificateFile().toString()); @@ -364,6 +294,14 @@ private static JettyServer startFhirServer(ServerSocketChannel statusConnectorCh """, certificates.getDicUserClientCertificate().certificateSha512ThumbprintHex(), certificates.getUacUserClientCertificate().certificateSha512ThumbprintHex())); + initParameters.put("dev.dsf.fhir.server.organization.thumbprint", + certificates.getClientCertificate().certificateSha512ThumbprintHex()); + initParameters.put("dev.dsf.fhir.server.endpoint.address", + "https://localhost:" + apiConnectorChannel.socket().getLocalPort() + "/fhir"); + initParameters.put("dev.dsf.fhir.server.organization.thumbprint.external", + certificates.getExternalClientCertificate().certificateSha512ThumbprintHex()); + initParameters.put("dev.dsf.fhir.server.endpoint.address.external", "https://localhost:80010/fhir"); + KeyStore clientCertificateTrustStore = KeyStoreCreator .jksForTrustedCertificates(certificates.getCaCertificate()); KeyStore fhirServerCertificateKeyStore = certificates.getFhirServerCertificate().keyStore(); diff --git a/dsf-bpe/dsf-bpe-server/src/test/resources/integration/test-bundle.xml b/dsf-bpe/dsf-bpe-server/src/test/resources/integration/test-bundle.xml index 729a45976..138cf961c 100644 --- a/dsf-bpe/dsf-bpe-server/src/test/resources/integration/test-bundle.xml +++ b/dsf-bpe/dsf-bpe-server/src/test/resources/integration/test-bundle.xml @@ -14,7 +14,7 @@ - + @@ -66,7 +66,7 @@ -
+
@@ -86,7 +86,7 @@ - + @@ -138,7 +138,7 @@ -
+
diff --git a/dsf-docker-test-setup-3dic-ttp/docker-compose.yml b/dsf-docker-test-setup-3dic-ttp/docker-compose.yml index 78f22570f..fbc74c84e 100644 --- a/dsf-docker-test-setup-3dic-ttp/docker-compose.yml +++ b/dsf-docker-test-setup-3dic-ttp/docker-compose.yml @@ -165,7 +165,6 @@ services: DEV_DSF_FHIR_DB_USER_PERMANENT_DELETE_USERNAME: dic1_fhir_server_permanent_delete_user DEV_DSF_FHIR_SERVER_BASE_URL: https://dic1/fhir DEV_DSF_FHIR_SERVER_ORGANIZATION_IDENTIFIER_VALUE: Test_DIC_1 - DEV_DSF_FHIR_SERVER_ORGANIZATION_THUMBPRINT: ${DIC1_BUNDLE_USER_THUMBPRINT} DEV_DSF_FHIR_SERVER_ROLECONFIG: | - webbrowser_test_user: thumbprint: ${WEBBROWSER_TEST_USER_THUMBPRINT} @@ -252,7 +251,6 @@ services: DEV_DSF_FHIR_DB_USER_PERMANENT_DELETE_USERNAME: dic2_fhir_server_permanent_delete_user DEV_DSF_FHIR_SERVER_BASE_URL: https://dic2/fhir DEV_DSF_FHIR_SERVER_ORGANIZATION_IDENTIFIER_VALUE: Test_DIC_2 - DEV_DSF_FHIR_SERVER_ORGANIZATION_THUMBPRINT: ${DIC2_BUNDLE_USER_THUMBPRINT} DEV_DSF_FHIR_SERVER_ROLECONFIG: | - webbrowser_test_user: thumbprint: ${WEBBROWSER_TEST_USER_THUMBPRINT} @@ -334,7 +332,6 @@ services: DEV_DSF_FHIR_DB_USER_PERMANENT_DELETE_USERNAME: dic3_fhir_server_permanent_delete_user DEV_DSF_FHIR_SERVER_BASE_URL: https://dic3/fhir DEV_DSF_FHIR_SERVER_ORGANIZATION_IDENTIFIER_VALUE: Test_DIC_3 - DEV_DSF_FHIR_SERVER_ORGANIZATION_THUMBPRINT: ${DIC3_BUNDLE_USER_THUMBPRINT} DEV_DSF_FHIR_SERVER_ROLECONFIG: | - webbrowser_test_user: thumbprint: ${WEBBROWSER_TEST_USER_THUMBPRINT} @@ -418,10 +415,9 @@ services: DEV_DSF_FHIR_DB_USER_PERMANENT_DELETE_USERNAME: ttp_fhir_server_permanent_delete_user DEV_DSF_FHIR_SERVER_BASE_URL: https://ttp/fhir DEV_DSF_FHIR_SERVER_ORGANIZATION_IDENTIFIER_VALUE: Test_TTP - DEV_DSF_FHIR_SERVER_ORGANIZATION_THUMBPRINT: ${TTP_BUNDLE_USER_THUMBPRINT} - DEV_DSF_FHIR_SERVER_ORGANIZATION_THUMBPRINT_DIC1: ${DIC1_BUNDLE_USER_THUMBPRINT} - DEV_DSF_FHIR_SERVER_ORGANIZATION_THUMBPRINT_DIC2: ${DIC2_BUNDLE_USER_THUMBPRINT} - DEV_DSF_FHIR_SERVER_ORGANIZATION_THUMBPRINT_DIC3: ${DIC3_BUNDLE_USER_THUMBPRINT} + DEV_DSF_FHIR_SERVER_ORGANIZATION_THUMBPRINT_DIC1: ${DIC1_THUMBPRINT} + DEV_DSF_FHIR_SERVER_ORGANIZATION_THUMBPRINT_DIC2: ${DIC2_THUMBPRINT} + DEV_DSF_FHIR_SERVER_ORGANIZATION_THUMBPRINT_DIC3: ${DIC3_THUMBPRINT} DEV_DSF_FHIR_SERVER_ROLECONFIG: | - webbrowser_test_user: thumbprint: ${WEBBROWSER_TEST_USER_THUMBPRINT} diff --git a/dsf-docker-test-setup/fhir/docker-compose.yml b/dsf-docker-test-setup/fhir/docker-compose.yml index 0fc5f7176..40dd026b9 100755 --- a/dsf-docker-test-setup/fhir/docker-compose.yml +++ b/dsf-docker-test-setup/fhir/docker-compose.yml @@ -68,7 +68,6 @@ services: DEV_DSF_FHIR_SERVER_UI_THEME: dev DEV_DSF_FHIR_SERVER_BASE_URL: https://fhir/fhir DEV_DSF_FHIR_SERVER_ORGANIZATION_IDENTIFIER_VALUE: Test_Organization - DEV_DSF_FHIR_SERVER_ORGANIZATION_THUMBPRINT: ${BUNDLE_USER_THUMBPRINT} DEV_DSF_FHIR_SERVER_ROLECONFIG: | - webbrowser_test_user: thumbprint: ${WEBBROWSER_TEST_USER_THUMBPRINT} diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java index e5872fb30..e36f21c73 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java @@ -1,14 +1,20 @@ package dev.dsf.fhir.spring.config; +import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.Path; import java.nio.file.Paths; import java.security.KeyStore; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.Duration; +import java.util.HexFormat; import java.util.List; import java.util.Properties; @@ -169,6 +175,7 @@ public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderCon new DockerSecretsPropertySourceFactory(environment).readDockerSecretsAndAddPropertiesToEnvironment(); injectEndpointProperties(environment); + computeOrganizationThumbprintPropertyIfPossible(environment); return new PropertySourcesPlaceholderConfigurer(); } @@ -185,11 +192,38 @@ private static void injectEndpointProperties(ConfigurableEnvironment environment properties.put("dev.dsf.fhir.server.endpoint.address", baseUrl.toString()); properties.put("dev.dsf.fhir.server.endpoint.identifier.value", baseUrl.getHost()); - environment.getPropertySources().addFirst(new PropertiesPropertySource("enpoint-properties", properties)); + environment.getPropertySources().addFirst(new PropertiesPropertySource("endpoint-properties", properties)); } catch (MalformedURLException | IllegalStateException | URISyntaxException e) { - throw new RuntimeException(e); + logger.warn("Exception while injecting endpoint properties", e); + } + } + + private static void computeOrganizationThumbprintPropertyIfPossible(ConfigurableEnvironment environment) + { + try + { + String organizationThumbprint = environment.getProperty("dev.dsf.fhir.server.organization.thumbprint"); + + if (organizationThumbprint == null) + { + Path clientCertPath = Paths.get(environment.getRequiredProperty("dev.dsf.fhir.client.certificate")); + X509Certificate clientCert = PemReader.readCertificate(clientCertPath); + MessageDigest md = MessageDigest.getInstance("SHA-512"); + HexFormat hexFormat = HexFormat.of(); + String thumbprint = hexFormat.formatHex(md.digest(clientCert.getEncoded())).toLowerCase(); + + Properties properties = new Properties(); + properties.put("dev.dsf.fhir.server.organization.thumbprint", thumbprint); + + environment.getPropertySources() + .addFirst(new PropertiesPropertySource("organization-thumbprint-properties", properties)); + } + } + catch (IOException | NoSuchAlgorithmException | CertificateEncodingException e) + { + logger.warn("Exception while computing organization thumbprint property", e); } } diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/AbstractIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/AbstractIntegrationTest.java index 84f3236fe..a45751def 100644 --- a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/AbstractIntegrationTest.java +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/AbstractIntegrationTest.java @@ -7,19 +7,15 @@ import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.net.URI; import java.nio.channels.ServerSocketChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.security.KeyStore; import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; @@ -35,9 +31,6 @@ import org.eclipse.jetty.server.ServerConnector; import org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer; import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Extension; -import org.hl7.fhir.r4.model.Organization; -import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Subscription; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -70,7 +63,6 @@ import dev.dsf.fhir.client.WebsocketClient; import dev.dsf.fhir.client.WebsocketClientTyrus; import dev.dsf.fhir.dao.AbstractDbTest; -import dev.dsf.fhir.integration.X509Certificates.CertificateAndPrivateKey; import dev.dsf.fhir.service.ReferenceCleaner; import dev.dsf.fhir.service.ReferenceCleanerImpl; import dev.dsf.fhir.service.ReferenceExtractorImpl; @@ -100,9 +92,6 @@ public abstract class AbstractIntegrationTest extends AbstractDbTest protected static final String CONTEXT_PATH = "/fhir"; - private static final Path FHIR_BUNDLE_FILE = Paths.get("target", UUID.randomUUID().toString() + ".xml"); - private static final List FILES_TO_DELETE = List.of(FHIR_BUNDLE_FILE); - protected static final FhirContext fhirContext = FhirContext.forR4(); protected static final ReadAccessHelper readAccessHelper = new ReadAccessHelperImpl(); protected static final ProcessAuthorizationHelper processAuthorizationHelper = new ProcessAuthorizationHelperImpl(); @@ -124,9 +113,6 @@ public static void beforeClass() throws Exception liquibaseRule.getDatabaseName()); defaultDataSource.unwrap(BasicDataSource.class).start(); - logger.info("Creating Bundle ..."); - createTestBundle(certificates.getClientCertificate(), certificates.getExternalClientCertificate()); - ServerSocketChannel statusConnectorChannel = JettyServer.serverSocketChannel("127.0.0.1"); ServerSocketChannel apiConnectorChannel = JettyServer.serverSocketChannel("127.0.0.1"); @@ -202,7 +188,7 @@ private static JettyServer startFhirServer(ServerSocketChannel statusConnectorCh initParameters.put("dev.dsf.fhir.server.base.url", baseUrl); initParameters.put("dev.dsf.fhir.server.organization.identifier.value", "Test_Organization"); - initParameters.put("dev.dsf.fhir.server.init.bundle", FHIR_BUNDLE_FILE.toString()); + initParameters.put("dev.dsf.fhir.server.init.bundle", "src/test/resources/integration/test-bundle.xml"); initParameters.put("dev.dsf.fhir.client.trust.server.certificate.cas", certificates.getCaCertificateFile().toString()); @@ -247,6 +233,14 @@ private static JettyServer startFhirServer(ServerSocketChannel statusConnectorCh certificates.getMinimalClientCertificate().certificateSha512ThumbprintHex())); initParameters.put("dev.dsf.fhir.debug.log.message.dbStatement", "true"); + initParameters.put("dev.dsf.fhir.server.organization.thumbprint", + certificates.getClientCertificate().certificateSha512ThumbprintHex()); + initParameters.put("dev.dsf.fhir.server.endpoint.address", + "https://localhost:" + apiConnectorChannel.socket().getLocalPort() + "/fhir"); + initParameters.put("dev.dsf.fhir.server.organization.thumbprint.external", + certificates.getExternalClientCertificate().certificateSha512ThumbprintHex()); + initParameters.put("dev.dsf.fhir.server.endpoint.address.external", "https://localhost:80010/fhir"); + KeyStore clientCertificateTrustStore = KeyStoreCreator .jksForTrustedCertificates(certificates.getCaCertificate()); KeyStore serverCertificateKeyStore = certificates.getServerCertificate().keyStore(); @@ -298,20 +292,6 @@ protected static Bundle readBundle(Path bundleTemplateFile, IParser parser) } } - private static void writeBundle(Path bundleFile, Bundle bundle) - { - try (OutputStream out = Files.newOutputStream(bundleFile); - OutputStreamWriter writer = new OutputStreamWriter(out)) - { - newXmlParser().encodeResourceToWriter(bundle, writer); - } - catch (IOException e) - { - logger.error("Error while writing bundle to {}", bundleFile.toString(), e); - throw new RuntimeException(e); - } - } - protected static IParser newXmlParser() { return newParser(fhirContext::newXmlParser); @@ -331,29 +311,6 @@ private static IParser newParser(Supplier supplier) return p; } - private static void createTestBundle(CertificateAndPrivateKey clientCertificate, - CertificateAndPrivateKey externalClientCertificate) - { - Path testBundleTemplateFile = Paths.get("src/test/resources/integration/test-bundle.xml"); - - Bundle testBundle = readBundle(testBundleTemplateFile, newXmlParser()); - - Organization organization = (Organization) testBundle.getEntry().get(0).getResource(); - Extension thumbprintExtension = organization - .getExtensionByUrl("http://dsf.dev/fhir/StructureDefinition/extension-certificate-thumbprint"); - - thumbprintExtension.setValue(new StringType(clientCertificate.certificateSha512ThumbprintHex())); - - Organization externalOrganization = (Organization) testBundle.getEntry().get(2).getResource(); - Extension externalThumbprintExtension = externalOrganization - .getExtensionByUrl("http://dsf.dev/fhir/StructureDefinition/extension-certificate-thumbprint"); - - externalThumbprintExtension - .setValue(new StringType(externalClientCertificate.certificateSha512ThumbprintHex())); - - writeBundle(FHIR_BUNDLE_FILE, testBundle); - } - @AfterClass public static void afterClass() throws Exception { @@ -371,21 +328,6 @@ public static void afterClass() throws Exception } defaultDataSource.unwrap(BasicDataSource.class).close(); - - logger.info("Deleting files {} ...", FILES_TO_DELETE); - FILES_TO_DELETE.forEach(AbstractIntegrationTest::deleteFile); - } - - private static void deleteFile(Path file) - { - try - { - Files.delete(file); - } - catch (IOException e) - { - logger.error("Error while deleting test file {}, error: {}", file.toString(), e.toString()); - } } protected AnnotationConfigWebApplicationContext getSpringWebApplicationContext() diff --git a/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationThumbprintIntegrationTest.java b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationThumbprintIntegrationTest.java new file mode 100644 index 000000000..a077c33a9 --- /dev/null +++ b/dsf-fhir/dsf-fhir-server/src/test/java/dev/dsf/fhir/integration/OrganizationThumbprintIntegrationTest.java @@ -0,0 +1,319 @@ +package dev.dsf.fhir.integration; + +import static org.junit.Assert.assertEquals; + +import java.nio.channels.ServerSocketChannel; +import java.security.KeyStore; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import javax.sql.DataSource; + +import org.apache.commons.dbcp2.BasicDataSource; +import org.eclipse.jetty.ee10.servlet.SessionHandler; +import org.eclipse.jetty.ee10.webapp.WebAppContext; +import org.eclipse.jetty.ee10.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; +import org.eclipse.jetty.security.SecurityHandler; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer; +import org.hl7.fhir.r4.model.Organization; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.SpringServletContainerInitializer; +import org.testcontainers.utility.DockerImageName; + +import ca.uhn.fhir.context.FhirContext; +import de.hsheilbronn.mi.utils.crypto.keystore.KeyStoreCreator; +import de.hsheilbronn.mi.utils.test.PostgreSqlContainerLiquibaseTemplateClassRule; +import de.hsheilbronn.mi.utils.test.PostgresTemplateRule; +import dev.dsf.common.auth.ClientCertificateAuthenticator; +import dev.dsf.common.auth.DelegatingAuthenticator; +import dev.dsf.common.auth.DsfLoginService; +import dev.dsf.common.auth.DsfSecurityHandler; +import dev.dsf.common.auth.StatusPortAuthenticator; +import dev.dsf.common.jetty.JettyServer; +import dev.dsf.fhir.authorization.read.ReadAccessHelper; +import dev.dsf.fhir.authorization.read.ReadAccessHelperImpl; +import dev.dsf.fhir.client.FhirWebserviceClient; +import dev.dsf.fhir.client.FhirWebserviceClientJersey; +import dev.dsf.fhir.dao.AbstractDbTest; +import dev.dsf.fhir.service.ReferenceCleaner; +import dev.dsf.fhir.service.ReferenceCleanerImpl; +import dev.dsf.fhir.service.ReferenceExtractorImpl; +import jakarta.servlet.ServletContainerInitializer; + +public class OrganizationThumbprintIntegrationTest extends AbstractDbTest +{ + @ClassRule + public static final X509Certificates certificates = new X509Certificates(); + + protected static DataSource defaultDataSource; + + @ClassRule + public static final PostgreSqlContainerLiquibaseTemplateClassRule liquibaseRule = new PostgreSqlContainerLiquibaseTemplateClassRule( + DockerImageName.parse("postgres:15"), ROOT_USER, "fhir", "fhir_template", CHANGE_LOG_FILE, + CHANGE_LOG_PARAMETERS, false); + + @Rule + public final PostgresTemplateRule templateRule = new PostgresTemplateRule(liquibaseRule); + + @Rule + public final TestNameLoggerRule testNameLoggerRule = new TestNameLoggerRule(); + + private static final Logger logger = LoggerFactory.getLogger(AbstractIntegrationTest.class); + + protected static final String CONTEXT_PATH = "/fhir"; + + protected static final FhirContext fhirContext = FhirContext.forR4(); + protected static final ReadAccessHelper readAccessHelper = new ReadAccessHelperImpl(); + + private static final ReferenceCleaner referenceCleaner = new ReferenceCleanerImpl(new ReferenceExtractorImpl()); + + private static String baseUrl; + private static JettyServer fhirServer; + private static FhirWebserviceClient webserviceClient; + private ServerSocketChannel apiConnectorChannel; + private ServerSocketChannel statusConnectorChannel; + + @BeforeClass + public static void beforeClass() throws Exception + { + defaultDataSource = createDefaultDataSource(liquibaseRule.getHost(), liquibaseRule.getMappedPort(5432), + liquibaseRule.getDatabaseName()); + defaultDataSource.unwrap(BasicDataSource.class).start(); + } + + @Before + public void before() + { + apiConnectorChannel = JettyServer.serverSocketChannel("127.0.0.1"); + statusConnectorChannel = JettyServer.serverSocketChannel("127.0.0.1"); + + baseUrl = "https://localhost:" + apiConnectorChannel.socket().getLocalPort() + CONTEXT_PATH; + + logger.info("Creating webservice client ..."); + webserviceClient = createWebserviceClient(apiConnectorChannel.socket().getLocalPort(), + certificates.getClientCertificate().trustStore(), certificates.getClientCertificate().keyStore(), + certificates.getClientCertificate().keyStorePassword()); + + logger.info("Creating template database ..."); + liquibaseRule.createTemplateDatabase(); + } + + private static FhirWebserviceClient createWebserviceClient(int apiPort, KeyStore trustStore, KeyStore keyStore, + char[] keyStorePassword) + { + return new FhirWebserviceClientJersey("https://localhost:" + apiPort + CONTEXT_PATH, trustStore, keyStore, + keyStorePassword, null, null, null, null, Duration.ZERO, Duration.ZERO, false, + "DSF Integration Test Client", fhirContext, referenceCleaner); + } + + private static Map getDefaultInitParameters(ServerSocketChannel statusConnectorChannel, + ServerSocketChannel apiConnectorChannel) + { + Map initParameters = new HashMap<>(); + initParameters.put("dev.dsf.server.status.port", + Integer.toString(statusConnectorChannel.socket().getLocalPort())); + + initParameters.put("dev.dsf.fhir.db.url", "jdbc:postgresql://" + liquibaseRule.getHost() + ":" + + liquibaseRule.getMappedPort(5432) + "/" + liquibaseRule.getDatabaseName()); + initParameters.put("dev.dsf.fhir.db.user.group", DATABASE_USERS_GROUP); + initParameters.put("dev.dsf.fhir.db.user.username", DATABASE_USER); + initParameters.put("dev.dsf.fhir.db.user.password", DATABASE_USER_PASSWORD); + initParameters.put("dev.dsf.fhir.db.user.permanent.delete.group", DATABASE_DELETE_USERS_GROUP); + initParameters.put("dev.dsf.fhir.db.user.permanent.delete.username", DATABASE_DELETE_USER); + initParameters.put("dev.dsf.fhir.db.user.permanent.delete.password", DATABASE_DELETE_USER_PASSWORD); + + initParameters.put("dev.dsf.fhir.server.base.url", baseUrl); + initParameters.put("dev.dsf.fhir.server.organization.identifier.value", "Test_Organization"); + initParameters.put("dev.dsf.fhir.server.init.bundle", "src/test/resources/integration/test-bundle.xml"); + + initParameters.put("dev.dsf.fhir.client.trust.server.certificate.cas", + certificates.getCaCertificateFile().toString()); + initParameters.put("dev.dsf.server.auth.trust.client.certificate.cas", + certificates.getCaCertificateFile().toString()); + initParameters.put("dev.dsf.fhir.client.certificate", certificates.getClientCertificateFile().toString()); + initParameters.put("dev.dsf.fhir.client.certificate.private.key", + certificates.getClientCertificatePrivateKeyFile().toString()); + initParameters.put("dev.dsf.fhir.client.certificate.private.key.password", + String.valueOf(X509Certificates.PASSWORD)); + + initParameters.put("dev.dsf.fhir.server.roleConfig", + String.format(""" + - practitioner-test-user: + thumbprint: %s + dsf-role: + - CREATE + - READ + - UPDATE + - DELETE + - SEARCH + - HISTORY + practitioner-role: + - http://dsf.dev/fhir/CodeSystem/practitioner-role|DIC_USER + - admin-user: + thumbprint: %s + dsf-role: [CREATE, READ, UPDATE, DELETE, SEARCH, HISTORY] + practitioner-role: + - http://dsf.dev/fhir/CodeSystem/practitioner-role|DSF_ADMIN + - minimal-test-user: + thumbprint: %s + dsf-role: + - CREATE: [Task] + - READ: &tqqr [Task, Questionnaire, QuestionnaireResponse] + - UPDATE: [QuestionnaireResponse] + - SEARCH: *tqqr + - HISTORY: *tqqr + practitioner-role: + - http://dsf.dev/fhir/CodeSystem/practitioner-role|DIC_USER + """, certificates.getPractitionerClientCertificate().certificateSha512ThumbprintHex(), + certificates.getAdminClientCertificate().certificateSha512ThumbprintHex(), + certificates.getMinimalClientCertificate().certificateSha512ThumbprintHex())); + initParameters.put("dev.dsf.fhir.debug.log.message.dbStatement", "true"); + + initParameters.put("dev.dsf.fhir.server.endpoint.address", + "https://localhost:" + apiConnectorChannel.socket().getLocalPort() + "/fhir"); + + initParameters.put("dev.dsf.fhir.server.endpoint.address.external", "https://localhost:80010/fhir"); + initParameters.put("dev.dsf.fhir.server.organization.thumbprint.external", + certificates.getExternalClientCertificate().certificateSha512ThumbprintHex()); + return initParameters; + } + + private static Map getInitParametersWithThumbprint(ServerSocketChannel statusConnectorChannel, + ServerSocketChannel apiConnectorChannel) + { + Map initParameters = getDefaultInitParameters(statusConnectorChannel, apiConnectorChannel); + initParameters.put("dev.dsf.fhir.server.organization.thumbprint", + certificates.getClientCertificate().certificateSha512ThumbprintHex()); + return initParameters; + } + + private static JettyServer createFhirServer(Map initParameters, + ServerSocketChannel apiConnectorChannel, ServerSocketChannel statusConnectorChannel) throws Exception + { + + KeyStore clientCertificateTrustStore = KeyStoreCreator + .jksForTrustedCertificates(certificates.getCaCertificate()); + KeyStore serverCertificateKeyStore = certificates.getServerCertificate().keyStore(); + + Function apiConnector = JettyServer.httpsConnector(apiConnectorChannel, + clientCertificateTrustStore, serverCertificateKeyStore, + certificates.getServerCertificate().keyStorePassword(), false); + Function statusConnector = JettyServer.statusConnector(statusConnectorChannel); + List> servletContainerInitializers = List.of( + JakartaWebSocketServletContainerInitializer.class, JerseyServletContainerInitializer.class, + SpringServletContainerInitializer.class); + + BiConsumer> securityHandlerConfigurer = (webAppContext, statusPortSupplier) -> + { + SessionHandler sessionHandler = webAppContext.getSessionHandler(); + DsfLoginService dsfLoginService = new DsfLoginService(webAppContext); + + StatusPortAuthenticator statusPortAuthenticator = new StatusPortAuthenticator(statusPortSupplier); + ClientCertificateAuthenticator clientCertificateAuthenticator = new ClientCertificateAuthenticator( + clientCertificateTrustStore); + DelegatingAuthenticator delegatingAuthenticator = new DelegatingAuthenticator(sessionHandler, + statusPortAuthenticator, clientCertificateAuthenticator, null, null, null, null); + + SecurityHandler securityHandler = new DsfSecurityHandler(dsfLoginService, delegatingAuthenticator, null); + securityHandler.setSessionRenewedOnAuthentication(true); + + webAppContext.setSecurityHandler(securityHandler); + }; + + return new JettyServer(apiConnector, statusConnector, "dsf-fhir-server", CONTEXT_PATH, + servletContainerInitializers, initParameters, securityHandlerConfigurer); + } + + @Test + public void testFhirServerStartupWithoutThumbprint() throws Exception + { + fhirServer = createFhirServer(getDefaultInitParameters(statusConnectorChannel, apiConnectorChannel), + apiConnectorChannel, statusConnectorChannel); + fhirServer.start(); + Organization organization = (Organization) webserviceClient + .search(Organization.class, Map.of("identifier", List.of("Test_Organization"))).getEntry().get(0) + .getResource(); + String thumbprint = organization + .getExtensionByUrl("http://dsf.dev/fhir/StructureDefinition/extension-certificate-thumbprint") + .getValue().primitiveValue(); + assertEquals(certificates.getClientCertificate().certificateSha512ThumbprintHex(), thumbprint); + } + + @Test + public void testFhirServerStartupWithThumbprint() throws Exception + { + fhirServer = createFhirServer(getInitParametersWithThumbprint(statusConnectorChannel, apiConnectorChannel), + apiConnectorChannel, statusConnectorChannel); + fhirServer.start(); + Organization organization = (Organization) webserviceClient + .search(Organization.class, Map.of("identifier", List.of("Test_Organization"))).getEntry().get(0) + .getResource(); + String thumbprint = organization + .getExtensionByUrl("http://dsf.dev/fhir/StructureDefinition/extension-certificate-thumbprint") + .getValue().primitiveValue(); + assertEquals(certificates.getClientCertificate().certificateSha512ThumbprintHex(), thumbprint); + } + + @After + public void cleanUp() throws Exception + { + try + { + if (fhirServer != null) + { + logger.info("Stopping FHIR Server ..."); + fhirServer.stop(); + } + } + catch (Exception e) + { + logger.error("Error while stopping FHIR Server", e); + } + + try + { + if (apiConnectorChannel != null) + { + logger.info("Closing API Connector Channel..."); + apiConnectorChannel.close(); + } + } + catch (Exception e) + { + logger.error("Error while closing API Connector Channel", e); + } + + try + { + if (statusConnectorChannel != null) + { + logger.info("Closing Status Connector Channel..."); + statusConnectorChannel.close(); + } + } + catch (Exception e) + { + logger.error("Error while closing Status Connector Channel", e); + } + + if (webserviceClient != null) + { + webserviceClient = null; + } + defaultDataSource.unwrap(BasicDataSource.class).close(); + } +} diff --git a/dsf-fhir/dsf-fhir-server/src/test/resources/integration/test-bundle.xml b/dsf-fhir/dsf-fhir-server/src/test/resources/integration/test-bundle.xml index c1e319802..4a345ec99 100755 --- a/dsf-fhir/dsf-fhir-server/src/test/resources/integration/test-bundle.xml +++ b/dsf-fhir/dsf-fhir-server/src/test/resources/integration/test-bundle.xml @@ -14,7 +14,7 @@ - + @@ -66,7 +66,7 @@ -
+
@@ -86,7 +86,7 @@ - + @@ -138,7 +138,7 @@ -
+
diff --git a/src/main/resources/templates/dsf-docker-test-setup-3dic-ttp.env b/src/main/resources/templates/dsf-docker-test-setup-3dic-ttp.env index 3fc1812bd..1babebec3 100644 --- a/src/main/resources/templates/dsf-docker-test-setup-3dic-ttp.env +++ b/src/main/resources/templates/dsf-docker-test-setup-3dic-ttp.env @@ -1,5 +1,4 @@ WEBBROWSER_TEST_USER_THUMBPRINT=${Webbrowser Test User.thumbprint} -DIC1_BUNDLE_USER_THUMBPRINT=${dic1.thumbprint} -DIC2_BUNDLE_USER_THUMBPRINT=${dic2.thumbprint} -DIC3_BUNDLE_USER_THUMBPRINT=${dic3.thumbprint} -TTP_BUNDLE_USER_THUMBPRINT=${ttp.thumbprint} \ No newline at end of file +DIC1_THUMBPRINT=${dic1.thumbprint} +DIC2_THUMBPRINT=${dic2.thumbprint} +DIC3_THUMBPRINT=${dic3.thumbprint} \ No newline at end of file diff --git a/src/main/resources/templates/dsf-docker-test-setup-fhir.env b/src/main/resources/templates/dsf-docker-test-setup-fhir.env index bee4009f2..d00663b5f 100644 --- a/src/main/resources/templates/dsf-docker-test-setup-fhir.env +++ b/src/main/resources/templates/dsf-docker-test-setup-fhir.env @@ -1,2 +1 @@ -BUNDLE_USER_THUMBPRINT=${bpe.thumbprint} WEBBROWSER_TEST_USER_THUMBPRINT=${Webbrowser Test User.thumbprint} \ No newline at end of file