diff --git a/feasibility-dsf-process-docker-test-setup/docker-compose.yml b/feasibility-dsf-process-docker-test-setup/docker-compose.yml index 4da679f..627e135 100755 --- a/feasibility-dsf-process-docker-test-setup/docker-compose.yml +++ b/feasibility-dsf-process-docker-test-setup/docker-compose.yml @@ -598,6 +598,7 @@ services: target: /opt/fhir/log environment: TZ: Europe/Berlin + DEV_DSF_SERVER_AUTH_TRUST_CLIENT_CERTIFICATE_CAS: /run/secrets/app_client_trust_certificates.pem DEV_DSF_FHIR_DB_LIQUIBASE_PASSWORD_FILE: /run/secrets/db_liquibase.password DEV_DSF_FHIR_DB_USER_PASSWORD_FILE: /run/secrets/db_fhir_dic_3_user.password DEV_DSF_FHIR_DB_USER_PERMANENT_DELETE_PASSWORD_FILE: /run/secrets/db_fhir_dic_3_user_permanent_delete.password @@ -780,6 +781,7 @@ services: target: /opt/fhir/log environment: TZ: Europe/Berlin + DEV_DSF_SERVER_AUTH_TRUST_CLIENT_CERTIFICATE_CAS: /run/secrets/app_client_trust_certificates.pem DEV_DSF_FHIR_DB_LIQUIBASE_PASSWORD_FILE: /run/secrets/db_liquibase.password DEV_DSF_FHIR_DB_USER_PASSWORD_FILE: /run/secrets/db_fhir_dic_4_user.password DEV_DSF_FHIR_DB_USER_PERMANENT_DELETE_PASSWORD_FILE: /run/secrets/db_fhir_dic_4_user_permanent_delete.password @@ -891,16 +893,13 @@ services: timeout: 5s retries: 5 environment: - SPRING_DATASOURCE_URL: "jdbc:postgresql://dic-4-store-db:5432/fhir?currentSchema=public" - SPRING_DATASOURCE_USERNAME: postgres - SPRING_DATASOURCE_PASSWORD: postgres - SPRING_DATASOURCE_DRIVERCLASSNAME: org.postgresql.Driver - SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT: org.hibernate.dialect.PostgreSQL10Dialect - VALIDATION_REQUESTS_ENABLED: "false" - HAPI_FHIR_USE_APACHE_ADDRESS_STRATEGY: "true" - HAPI_FHIR_CQL_ENABLED: "true" + SPRING_CONFIG_LOCATION: 'file:///data/hapi/application.yaml' networks: dic-4-bpe-backend: + volumes: + - type: bind + source: ./dic-4/fhir/conf/application.yaml + target: /data/hapi/application.yaml volumes_from: - hapi-curl-download:ro depends_on: diff --git a/feasibility-dsf-process/README.md b/feasibility-dsf-process/README.md index c68a21a..57d559e 100644 --- a/feasibility-dsf-process/README.md +++ b/feasibility-dsf-process/README.md @@ -136,10 +136,10 @@ This version of the process is compatible with the following components: | Component | Compatible Version(s) | |-----------|-----------------------| -| DSF FHIR | `0.9.0` | -| DSF BPE | `0.9.0` | +| DSF FHIR | `>= 1.3.0` | +| DSF BPE | `>= 1.3.0` | | Blaze | `>= 0.12` | -| Flare | `1.0` | +| Flare | `>= 1.0` | **Note:** Flare got rewritten. Only the [new project][9] is supported. @@ -151,6 +151,6 @@ This version of the process is compatible with the following components: [5]: [6]: [7]: -[8]: -[9]: +[8]: +[9]: [10]: diff --git a/feasibility-dsf-process/pom.xml b/feasibility-dsf-process/pom.xml index 5dda620..3cf4aba 100755 --- a/feasibility-dsf-process/pom.xml +++ b/feasibility-dsf-process/pom.xml @@ -159,7 +159,7 @@ exec - ${basedir}/scripts/create_certs_for_store_client_tests.sh + ${basedir}/scripts/create_certs_for_client_tests.sh diff --git a/feasibility-dsf-process/scripts/create_certs_for_store_client_tests.sh b/feasibility-dsf-process/scripts/create_certs_for_client_tests.sh similarity index 86% rename from feasibility-dsf-process/scripts/create_certs_for_store_client_tests.sh rename to feasibility-dsf-process/scripts/create_certs_for_client_tests.sh index e271d14..be5e521 100755 --- a/feasibility-dsf-process/scripts/create_certs_for_store_client_tests.sh +++ b/feasibility-dsf-process/scripts/create_certs_for_client_tests.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash BASE_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" -TARGET_DIR=$(readlink -f "${BASE_DIR}/../src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/store/certs") +TARGET_DIR="${BASE_DIR}/../src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/certs" mkdir -p "${TARGET_DIR}" @@ -19,12 +19,14 @@ openssl pkcs12 -export -out ${TARGET_DIR}/ca.p12 \ # Issue server certificate using said self signed CA openssl req -nodes -sha256 -new -newkey rsa:2048 -keyout ${TARGET_DIR}/server_cert_key.pem \ -out ${TARGET_DIR}/server_cert_csr.pem \ - -subj "/C=DE/ST=Berlin/L=Berlin/O=Bar/CN=localhost" + -subj "/C=DE/ST=Berlin/L=Berlin/O=Bar/CN=localhost" \ + -addext "subjectAltName = DNS:localhost, DNS:proxy" openssl x509 -req -days 7 -sha256 -in ${TARGET_DIR}/server_cert_csr.pem \ -CA ${TARGET_DIR}/ca.pem \ -CAkey ${TARGET_DIR}/ca_key.pem \ -CAcreateserial \ + -copy_extensions copyall \ -out ${TARGET_DIR}/server_cert.pem # Server cert chain @@ -56,4 +58,4 @@ rm -f ${TARGET_DIR}/server_cert_csr.pem rm -f ${TARGET_DIR}/server_cert.pem rm -f ${TARGET_DIR}/client_cert_csr.pem rm -f ${TARGET_DIR}/client_cert_key.pem -rm -f ${TARGET_DIR}/client_cert.pem \ No newline at end of file +rm -f ${TARGET_DIR}/client_cert.pem diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/FeasibilityProcessPluginDefinition.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/FeasibilityProcessPluginDefinition.java index fc63f93..7d05445 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/FeasibilityProcessPluginDefinition.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/FeasibilityProcessPluginDefinition.java @@ -17,6 +17,8 @@ import java.util.Properties; import static com.google.common.base.Preconditions.checkNotNull; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.FEASIBILITY_EXECUTE_PROCESS_ID; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.FEASIBILITY_REQUEST_PROCESS_ID; public class FeasibilityProcessPluginDefinition implements ProcessPluginDefinition { @@ -30,7 +32,7 @@ public FeasibilityProcessPluginDefinition() { Properties props = new Properties(); props.load(input); - this.version = props.getProperty("build.version"); + this.version = props.getProperty("build.version").replaceFirst("-.*$", ""); this.releaseDate = LocalDate.parse(props.getProperty("build.date")); } catch (IOException e) { throw new IllegalStateException("Could not load application properties.", e); @@ -84,9 +86,9 @@ public Map> getFhirResourcesByProcessId() { var vF = "fhir/ValueSet/feasibility.xml"; return Map.of( - "medizininformatik-initiativede_feasibilityExecute", + FEASIBILITY_EXECUTE_PROCESS_ID, Arrays.asList(aExe, sTExe, sTResS, vF, cF, sMeasure, sMeasureReport, sLibrary), - "medizininformatik-initiativede_feasibilityRequest", + FEASIBILITY_REQUEST_PROCESS_ID, Arrays.asList(aReq, sTReq, sTResS, sExtDic, vF, cF, sMeasure, sMeasureReport, sLibrary)); } } diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/FeasibilityProcessPluginDeploymentStateListener.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/FeasibilityProcessPluginDeploymentStateListener.java new file mode 100644 index 0000000..7e7580b --- /dev/null +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/FeasibilityProcessPluginDeploymentStateListener.java @@ -0,0 +1,70 @@ +package de.medizininformatik_initiative.feasibility_dsf_process; + +import ca.uhn.fhir.rest.client.api.IGenericClient; +import de.medizininformatik_initiative.feasibility_dsf_process.client.flare.FlareWebserviceClient; +import dev.dsf.bpe.v1.ProcessPluginDeploymentStateListener; +import org.hl7.fhir.r4.model.CapabilityStatement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; + +import java.io.IOException; +import java.util.List; + +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.FEASIBILITY_EXECUTE_PROCESS_ID; +import static java.util.Objects.requireNonNull; + +/** + * After process deployment starts a connection test if the feasibilityExecute process is active. + *

+ * The configured {@link EvaluationStrategy} determines which client is used for the connection test. + *

+ * + * @author Mathias Rühle + */ +public class FeasibilityProcessPluginDeploymentStateListener + implements ProcessPluginDeploymentStateListener, InitializingBean { + + private static final Logger logger = LoggerFactory.getLogger(FeasibilityProcessPluginDeploymentStateListener.class); + + private EvaluationStrategy strategy; + private IGenericClient storeClient; + private FlareWebserviceClient flareWebserviceClient; + + public FeasibilityProcessPluginDeploymentStateListener(EvaluationStrategy strategy, IGenericClient storeClient, + FlareWebserviceClient flareWebserviceClient) { + this.strategy = strategy; + this.storeClient = storeClient; + this.flareWebserviceClient = flareWebserviceClient; + } + + @Override + public void onProcessesDeployed(List processes) { + if (processes.contains(FEASIBILITY_EXECUTE_PROCESS_ID)) { + if (strategy.equals(EvaluationStrategy.CQL)) { + try { + CapabilityStatement statement = storeClient.capabilities().ofType(CapabilityStatement.class) + .execute(); + logger.info("Connection test OK ({} - {})", statement.getSoftware().getName(), + statement.getSoftware().getVersion()); + } catch (RuntimeException e) { + logger.warn("Connection test FAILED - error: {} - {}", e.getClass().getName(), e.getMessage()); + } + } else { + try { + flareWebserviceClient.testConnection(); + logger.info("Connection test OK (flare)"); + } catch (IOException e) { + logger.warn("Connection test FAILED (flare) - error: {} - {}", e.getClass().getName(), + e.getMessage()); + } + } + } + } + + @Override + public void afterPropertiesSet() throws Exception { + requireNonNull(storeClient); + requireNonNull(flareWebserviceClient); + } +} diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClient.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClient.java index 23e3070..fd1981d 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClient.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClient.java @@ -1,5 +1,7 @@ package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; +import org.apache.http.client.ClientProtocolException; + import java.io.IOException; /** @@ -17,4 +19,16 @@ public interface FlareWebserviceClient { * @throws InterruptedException If the operation is interrupted. */ int requestFeasibility(byte[] structuredQuery) throws IOException, InterruptedException; + + /** + * Tests the connection to the flare server, and flare server only, using the connection settings defined by spring + * configuration. + *

+ * Throws an exception if the connection test fails, otherwise the connection test was successful. + *

+ * + * @throws IOException in case of a problem or the connection was aborted + * @throws ClientProtocolException in case of an http protocol error + */ + void testConnection() throws ClientProtocolException, IOException; } diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImpl.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImpl.java index 4464317..10038fe 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImpl.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImpl.java @@ -1,19 +1,24 @@ package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.BasicResponseHandler; +import org.apache.http.message.BasicHeader; + import java.io.IOException; import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import static java.net.http.HttpRequest.BodyPublishers.ofByteArray; -import static java.net.http.HttpResponse.BodyHandlers.ofString; +import static ca.uhn.fhir.rest.api.Constants.HEADER_CONTENT_TYPE; /** * Client for communicating with a Flare instance. */ public class FlareWebserviceClientImpl implements FlareWebserviceClient { - private final HttpClient httpClient; + private final org.apache.http.client.HttpClient httpClient; private final URI flareBaseUrl; public FlareWebserviceClientImpl(HttpClient httpClient, URI flareBaseUrl) { @@ -23,13 +28,23 @@ public FlareWebserviceClientImpl(HttpClient httpClient, URI flareBaseUrl) { @Override public int requestFeasibility(byte[] structuredQuery) throws IOException, InterruptedException { - var req = HttpRequest.newBuilder() - .POST(ofByteArray(structuredQuery)) - .setHeader("Content-Type", "application/sq+json") - .uri(flareBaseUrl.resolve("/query/execute")) - .build(); - - var res = httpClient.send(req, ofString()); - return Integer.parseInt(res.body()); + var req = new HttpPost(resolve("/query/execute")); + req.setEntity(new ByteArrayEntity(structuredQuery)); + req.setHeader(new BasicHeader(HEADER_CONTENT_TYPE, "application/sq+json")); + + var response = httpClient.execute(req, new BasicResponseHandler()); + + return Integer.parseInt(response); + } + + private URI resolve(String path) { + return flareBaseUrl.resolve((flareBaseUrl.getPath() + path).replaceAll("//", "/")); + } + + @Override + public void testConnection() throws ClientProtocolException, IOException { + var req = new HttpGet(resolve("/cache/stats")); + + httpClient.execute(req, new BasicResponseHandler()); } } diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientSpringConfig.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientSpringConfig.java index 4383061..15254f1 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientSpringConfig.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientSpringConfig.java @@ -1,14 +1,44 @@ package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; +import de.medizininformatik_initiative.feasibility_dsf_process.client.store.TlsClientFactory; +import de.medizininformatik_initiative.feasibility_dsf_process.spring.config.BaseConfig; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.ProxyAuthenticationStrategy; +import org.apache.http.message.BasicHeader; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import java.io.IOException; import java.net.URI; -import java.net.http.HttpClient; -import java.time.Duration; +import java.net.URISyntaxException; + +import javax.net.ssl.SSLContext; + +import static ca.uhn.fhir.rest.api.Constants.HEADER_AUTHORIZATION; +import static ca.uhn.fhir.rest.api.Constants.HEADER_AUTHORIZATION_VALPREFIX_BEARER; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.String.format; @Configuration +@Import(BaseConfig.class) public class FlareWebserviceClientSpringConfig { @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.flare.base_url:}") @@ -17,15 +47,118 @@ public class FlareWebserviceClientSpringConfig { @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.flare.timeout.connect:2000}") private int connectTimeout; + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.host:#{null}}") + private String proxyHost; + + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.port:}") + private Integer proxyPort; + + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.username:#{null}}") + private String proxyUsername; + + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.password:#{null}}") + private String proxyPassword; + + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.basic.username:#{null}}") + private String basicAuthUsername; + + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.basic.password:#{null}}") + private String basicAuthPassword; + + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.bearer.token:#{null}}") + private String bearerAuthToken; + @Bean public FlareWebserviceClient flareWebserviceClient(HttpClient httpClient) { - return new FlareWebserviceClientImpl(httpClient, URI.create(flareBaseUrl)); + return new FlareWebserviceClient() { + + FlareWebserviceClient client; + + @Override + public int requestFeasibility(byte[] structuredQuery) throws IOException, InterruptedException { + return getClient().requestFeasibility(structuredQuery); + } + + private FlareWebserviceClient getClient() { + if (client == null) { + checkArgument(!isNullOrEmpty(flareBaseUrl), "FLARE_BASE_URL is not set."); + try { + URI parsedFlareBaseUrl = new URI(flareBaseUrl); + client = new FlareWebserviceClientImpl(httpClient, parsedFlareBaseUrl); + } catch (URISyntaxException e) { + throw new IllegalArgumentException( + format("Could not parse FLARE_BASE_URL '%s' as URI.", flareBaseUrl), e); + } + } + return client; + } + + @Override + public void testConnection() throws ClientProtocolException, IOException { + getClient().testConnection(); + } + }; } @Bean - public HttpClient flareHttpClient() { - return HttpClient.newBuilder() - .connectTimeout(Duration.ofMillis(connectTimeout)) - .build(); + public HttpClient flareHttpClient(@Qualifier("base-client") SSLContext sslContext) { + HttpClientBuilder builder = new TlsClientFactory(null, sslContext).getNativeHttpClientBuilder(); + + BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + + if (!isNullOrEmpty(proxyHost) && proxyPort != null) { + HttpHost proxy = new HttpHost(proxyHost, proxyPort); + builder.setProxy(proxy); + if (!isNullOrEmpty(proxyUsername) && !isNullOrEmpty(proxyPassword)) { + builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); + credentialsProvider.setCredentials(new AuthScope(proxy), + new UsernamePasswordCredentials(proxyUsername, proxyPassword)); + } + } + if (!isNullOrEmpty(basicAuthUsername) && !isNullOrEmpty(basicAuthPassword)) { + URI flareUri = URI.create(flareBaseUrl); + credentialsProvider.setCredentials(new AuthScope(new HttpHost(flareUri.getHost(), flareUri.getPort())), + new UsernamePasswordCredentials(basicAuthUsername, basicAuthPassword)); + } else if (!isNullOrEmpty(bearerAuthToken)) { + return new BearerHttpClient(builder.setDefaultCredentialsProvider(credentialsProvider).build()); + } + return builder.setDefaultCredentialsProvider(credentialsProvider).build(); + } + + private final class BearerHttpClient extends CloseableHttpClient { + private CloseableHttpClient client; + + public BearerHttpClient(CloseableHttpClient client) { + this.client = client; + } + + @Override + public HttpParams getParams() { + return client.getParams(); + } + + @Override + public ClientConnectionManager getConnectionManager() { + return client.getConnectionManager(); + } + + @Override + public void close() throws IOException { + client.close(); + } + + @Override + protected CloseableHttpResponse doExecute(HttpHost target, HttpRequest request, HttpContext context) + throws IOException, ClientProtocolException { + return client.execute(target, request, context); + } + + @Override + public T execute(HttpUriRequest request, ResponseHandler responseHandler) + throws IOException, ClientProtocolException { + request.setHeader(new BasicHeader(HEADER_AUTHORIZATION, + HEADER_AUTHORIZATION_VALPREFIX_BEARER + "1234")); + return super.execute(request, responseHandler); + } } } diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/listener/SetCorrelationKeyListener.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/listener/SetCorrelationKeyListener.java new file mode 100644 index 0000000..ca63063 --- /dev/null +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/listener/SetCorrelationKeyListener.java @@ -0,0 +1,33 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.client.listener; + +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.constants.BpmnExecutionVariables; +import dev.dsf.bpe.v1.variables.Target; +import dev.dsf.bpe.v1.variables.Variables; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.delegate.ExecutionListener; +import org.springframework.beans.factory.InitializingBean; + +import java.util.Objects; + +public class SetCorrelationKeyListener implements ExecutionListener, InitializingBean +{ + private final ProcessPluginApi api; + + public SetCorrelationKeyListener(ProcessPluginApi api) { + this.api = api; + } + + @Override + public void afterPropertiesSet() throws Exception { + Objects.requireNonNull(api, "api"); + } + + @Override + public void notify(DelegateExecution execution) throws Exception { + Variables variables = api.getVariables(execution); + Target target = variables.getTarget(); + + execution.setVariableLocal(BpmnExecutionVariables.CORRELATION_KEY, target.getCorrelationKey()); + } +} diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/StoreClientSpringConfig.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/StoreClientSpringConfig.java index 8e43ffa..b8b92c8 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/StoreClientSpringConfig.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/StoreClientSpringConfig.java @@ -6,21 +6,19 @@ import ca.uhn.fhir.rest.client.impl.RestfulClientFactory; import ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor; import ca.uhn.fhir.rest.client.interceptor.BearerTokenAuthInterceptor; -import org.apache.http.ssl.SSLContexts; +import de.medizininformatik_initiative.feasibility_dsf_process.spring.config.BaseConfig; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.lang.Nullable; +import org.springframework.context.annotation.Import; import javax.net.ssl.SSLContext; -import java.io.FileInputStream; -import java.io.IOException; -import java.security.*; -import java.security.cert.CertificateException; @Configuration +@Import(BaseConfig.class) public class StoreClientSpringConfig { + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.host:#{null}}") private String proxyHost; @@ -51,29 +49,13 @@ public class StoreClientSpringConfig { @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.timeout.socket:20000}") private Integer socketTimeout; - @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_path:#{null}}") - private String trustStorePath; - - @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_password:#{null}}") - private String trustStorePassword; - - @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.key_store_path:#{null}}") - private String keyStorePath; - - @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.key_store_password:#{null}}") - private String keyStorePassword; - - @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.base_url}") + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.base_url:}") private String storeBaseUrl; @Bean @Qualifier("store-client") IGenericClient client(@Qualifier("store-client") FhirContext fhirContext, @Qualifier("store-client") RestfulClientFactory clientFactory) { - if (storeBaseUrl == null || storeBaseUrl.isBlank()) { - throw new IllegalArgumentException("Store url is not set."); - } - clientFactory.setServerValidationMode(ServerValidationModeEnum.NEVER); clientFactory.setConnectTimeout(connectTimeout); clientFactory.setConnectionRequestTimeout(connectRequestTimeout); @@ -108,57 +90,7 @@ FhirContext fhirContext() { @Bean @Qualifier("store-client") RestfulClientFactory clientFactory(@Qualifier("store-client") FhirContext fhirContext, - @Qualifier("store-client") SSLContext sslContext) { + @Qualifier("base-client") SSLContext sslContext) { return new TlsClientFactory(fhirContext, sslContext); } - - @Bean - @Qualifier("store-client-trust") - KeyStore loadTrustStore() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { - if (trustStorePath == null || trustStorePath.isBlank()) { - return DefaultTrustStoreUtils.loadDefaultTrustStore(); - } - - var trustStoreInputStream = new FileInputStream(trustStorePath); - - var trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - trustStore.load(trustStoreInputStream, (trustStorePassword == null) ? null : trustStorePassword.toCharArray()); - trustStoreInputStream.close(); - - return trustStore; - } - - - @Bean - @Qualifier("store-client-key") - @Nullable - KeyStore loadKeyStore() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { - if (keyStorePath == null) { - return null; - } - - var keyStoreInputStream = new FileInputStream(keyStorePath); - - var keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(keyStoreInputStream, (keyStorePassword == null) ? null : keyStorePassword.toCharArray()); - keyStoreInputStream.close(); - - return keyStore; - } - - @Bean - @Qualifier("store-client") - SSLContext createSslContext(@Qualifier("store-client-trust") KeyStore trustStore, - @Nullable @Qualifier("store-client-key") KeyStore keyStore) - throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, UnrecoverableKeyException { - var sslContextBuilder = SSLContexts.custom() - .loadTrustMaterial(trustStore, null); - - if (keyStore != null) { - sslContextBuilder.loadKeyMaterial(keyStore, (keyStorePassword == null) ? null : - keyStorePassword.toCharArray()); - } - - return sslContextBuilder.build(); - } } diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/TlsClientFactory.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/TlsClientFactory.java index 8f28ad7..cd1d8c7 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/TlsClientFactory.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/TlsClientFactory.java @@ -1,10 +1,12 @@ package de.medizininformatik_initiative.feasibility_dsf_process.client.store; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.client.apache.ApacheHttpClient; import ca.uhn.fhir.rest.client.api.Header; import ca.uhn.fhir.rest.client.api.IHttpClient; +import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.impl.RestfulClientFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -25,14 +27,16 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.ProxyAuthenticationStrategy; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.hl7.fhir.instance.model.api.IBaseBinary; -import javax.net.ssl.SSLContext; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLContext; + // TODO: doc @Slf4j @RequiredArgsConstructor @@ -51,17 +55,47 @@ public TlsClientFactory(FhirContext fhirContext, SSLContext sslContext) { } @Override - protected synchronized ApacheHttpClient getHttpClient(String serverBase) { - if (clientByServerBase.containsKey(serverBase)) { - log.debug("Reusing ApacheHttpClient for ServerBase {}", serverBase); - return clientByServerBase.get(serverBase); - } else { - log.debug("Returning new ApacheHttpClient for ServerBase {}", serverBase); - ApacheHttpClient client = new ApacheHttpClient(getNativeHttpClient(), new StringBuilder(serverBase), - null, null, null, null); - clientByServerBase.put(serverBase, client); - return client; - } + protected synchronized IHttpClient getHttpClient(String serverBase) { + return new IHttpClient() { + + @Override + public IHttpRequest createParamRequest(FhirContext theContext, Map> theParams, + EncodingEnum theEncoding) { + return getClient().createParamRequest(theContext, theParams, theEncoding); + } + + @Override + public IHttpRequest createGetRequest(FhirContext theContext, EncodingEnum theEncoding) { + return getClient().createGetRequest(theContext, theEncoding); + } + + @Override + public IHttpRequest createByteRequest(FhirContext theContext, String theContents, String theContentType, + EncodingEnum theEncoding) { + return getClient().createByteRequest(theContext, theContents, theContentType, theEncoding); + } + + @Override + public IHttpRequest createBinaryRequest(FhirContext theContext, IBaseBinary theBinary) { + return getClient().createBinaryRequest(theContext, theBinary); + } + + private ApacheHttpClient getClient() { + if (serverBase == null || serverBase.isBlank()) { + throw new IllegalArgumentException("Store url is not set."); + } + if (clientByServerBase.containsKey(serverBase)) { + log.debug("Reusing ApacheHttpClient for ServerBase {}", serverBase); + return clientByServerBase.get(serverBase); + } else { + log.debug("Returning new ApacheHttpClient for ServerBase {}", serverBase); + ApacheHttpClient client = new ApacheHttpClient(getNativeHttpClient(), new StringBuilder(serverBase), + null, null, null, null); + clientByServerBase.put(serverBase, client); + return client; + } + } + }; } @Override @@ -74,39 +108,42 @@ public synchronized IHttpClient getHttpClient(StringBuilder theUrl, Map socketFactoryRegistry = RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.getSocketFactory()) - .register("https", new SSLConnectionSocketFactory(sslContext)).build(); + return myHttpClient; + } - PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager( - socketFactoryRegistry, null, null, null, 5000, - TimeUnit.MILLISECONDS); + public HttpClientBuilder getNativeHttpClientBuilder() { + SSLContext sslContext = getSslContext(); - connectionManager.setMaxTotal(getPoolMaxTotal()); - connectionManager.setDefaultMaxPerRoute(getPoolMaxPerRoute()); + Registry socketFactoryRegistry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", new SSLConnectionSocketFactory(sslContext)).build(); - RequestConfig defaultRequestConfig = RequestConfig.custom().setSocketTimeout(getSocketTimeout()) - .setConnectTimeout(getConnectTimeout()).setConnectionRequestTimeout(getConnectionRequestTimeout()) - .setProxy(myProxy).build(); + PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager( + socketFactoryRegistry, null, null, null, 5000, + TimeUnit.MILLISECONDS); - HttpClientBuilder builder = HttpClients.custom().setConnectionManager(connectionManager) - .setSSLContext(sslContext).setDefaultRequestConfig(defaultRequestConfig).disableCookieManagement(); + connectionManager.setMaxTotal(getPoolMaxTotal()); + connectionManager.setDefaultMaxPerRoute(getPoolMaxPerRoute()); - if (myProxy != null && StringUtils.isNotBlank(getProxyUsername()) - && StringUtils.isNotBlank(getProxyPassword())) { - CredentialsProvider credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials(new AuthScope(myProxy.getHostName(), myProxy.getPort()), - new UsernamePasswordCredentials(getProxyUsername(), getProxyPassword())); - builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); - builder.setDefaultCredentialsProvider(credsProvider); - } + RequestConfig defaultRequestConfig = RequestConfig.custom().setSocketTimeout(getSocketTimeout()) + .setConnectTimeout(getConnectTimeout()).setConnectionRequestTimeout(getConnectionRequestTimeout()) + .setProxy(myProxy).build(); - myHttpClient = builder.build(); - } + HttpClientBuilder builder = HttpClients.custom().setConnectionManager(connectionManager) + .setSSLContext(sslContext).setDefaultRequestConfig(defaultRequestConfig).disableCookieManagement(); - return myHttpClient; + if (myProxy != null && StringUtils.isNotBlank(getProxyUsername()) + && StringUtils.isNotBlank(getProxyPassword())) { + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(myProxy.getHostName(), myProxy.getPort()), + new UsernamePasswordCredentials(getProxyUsername(), getProxyPassword())); + builder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); + builder.setDefaultCredentialsProvider(credsProvider); + } + return builder; } protected SSLContext getSslContext() { diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/message/SendDicRequest.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/message/SendDicRequest.java new file mode 100644 index 0000000..1c8044a --- /dev/null +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/message/SendDicRequest.java @@ -0,0 +1,40 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.message; + +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractTaskMessageSend; +import dev.dsf.bpe.v1.variables.Variables; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Task; +import org.hl7.fhir.r4.model.Task.ParameterComponent; + +import java.util.stream.Stream; + +import static com.google.common.base.Preconditions.checkNotNull; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.CODESYSTEM_FEASIBILITY; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.CODESYSTEM_FEASIBILITY_VALUE_MEASURE_REFERENCE; + +public class SendDicRequest extends AbstractTaskMessageSend { + + public SendDicRequest(ProcessPluginApi api) { + super(api); + } + + @Override + protected Stream getAdditionalInputParameters(DelegateExecution execution, + Variables variables) { + return Stream.of(api.getTaskHelper().createInput( + new Reference(checkNotNull(variables.getString("measure-id"), "variable 'measure-id' not set")), + CODESYSTEM_FEASIBILITY, CODESYSTEM_FEASIBILITY_VALUE_MEASURE_REFERENCE)); + } + + @Override + protected void handleIntermediateThrowEventError(DelegateExecution execution, Variables variables, + Exception exception, String errorMessage) { + execution.setVariableLocal("sendError", true); + } + + @Override + protected void addErrorMessage(Task task, String errorMessage) { + } +} diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/DownloadMeasureReport.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/DownloadMeasureReport.java index 45c6bf2..34b6612 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/DownloadMeasureReport.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/DownloadMeasureReport.java @@ -44,7 +44,7 @@ protected void doExecute(DelegateExecution execution, Variables variables) { FhirWebserviceClient client = clientProvider .getWebserviceClientByReference(measureReportId); MeasureReport measureReport = downloadMeasureReport(client, measureReportId); - execution.setVariable(VARIABLE_MEASURE_REPORT, measureReport); + execution.setVariableLocal(VARIABLE_MEASURE_REPORT, measureReport); } private MeasureReport downloadMeasureReport(FhirWebserviceClient client, IdType measureReportId) { diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateCqlMeasure.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateCqlMeasure.java index 4522462..10835e3 100755 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateCqlMeasure.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateCqlMeasure.java @@ -21,8 +21,8 @@ public class EvaluateCqlMeasure extends AbstractServiceDelegate implements Initi private static final Logger logger = LoggerFactory.getLogger(EvaluateCqlMeasure.class); - private static final String CODE_SYSTEM_MEASURE_POPULATION = "http://terminology.hl7.org/CodeSystem/measure-population"; - private static final String CODE_INITIAL_POPULATION = "initial-population"; + private static final String MEASURE_POPULATION = "http://terminology.hl7.org/CodeSystem/measure-population"; + private static final String INITIAL_POPULATION = "initial-population"; private final IGenericClient storeClient; @@ -69,8 +69,8 @@ private void validateMeasureReport(MeasureReport report) { if (!report.getGroupFirstRep().getPopulationFirstRep().hasCode()) { throw new RuntimeException("Missing MeasureReport population code"); } - if (!report.getGroupFirstRep().getPopulationFirstRep().getCode().hasCoding(CODE_SYSTEM_MEASURE_POPULATION, - CODE_INITIAL_POPULATION)) { + if (!report.getGroupFirstRep().getPopulationFirstRep().getCode().hasCoding(MEASURE_POPULATION, + INITIAL_POPULATION)) { throw new RuntimeException("Missing MeasureReport initial-population code"); } if (!report.getGroupFirstRep().getPopulationFirstRep().hasCount()) { diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/FeasibilityResourceCleaner.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/FeasibilityResourceCleaner.java new file mode 100644 index 0000000..a661b56 --- /dev/null +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/FeasibilityResourceCleaner.java @@ -0,0 +1,36 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.service; + +import org.hl7.fhir.r4.model.Library; +import org.hl7.fhir.r4.model.Measure; +import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.Resource; + +import java.util.List; + +public class FeasibilityResourceCleaner { + + private static final String CQL_CONTENT_TYPE = "text/cql"; + + public void cleanLibrary(Library library) { + stripMeta(library); + stripNonCqlAttachments(library); + } + + public void cleanMeasure(Measure measure) { + stripMeta(measure); + } + + private void stripMeta(Resource resource) { + resource.setMeta(new Meta()); + } + + private void stripNonCqlAttachments(Library library) { + var cqlAttachment = library.getContent() + .stream() + .filter(a -> a.getContentType().equalsIgnoreCase(CQL_CONTENT_TYPE)) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Library content of type `%s` is missing.".formatted(CQL_CONTENT_TYPE))); + + library.setContent(List.of(cqlAttachment)); + } +} diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/LogReceiveTimeout.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/LogReceiveTimeout.java new file mode 100644 index 0000000..c125ae9 --- /dev/null +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/LogReceiveTimeout.java @@ -0,0 +1,28 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.service; + +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Target; +import dev.dsf.bpe.v1.variables.Variables; +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LogReceiveTimeout extends AbstractServiceDelegate { + + private static final Logger logger = LoggerFactory.getLogger(LogReceiveTimeout.class); + + public LogReceiveTimeout(ProcessPluginApi api) { + super(api); + } + + @Override + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError, Exception { + Target target = variables.getTarget(); + logger.warn("Timeout while waiting for result from {} (endpoint url: {}).", + target.getOrganizationIdentifierValue(), + target.getEndpointUrl()); + } + +} diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/SendDicRequests.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/SendDicRequests.java deleted file mode 100644 index a003739..0000000 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/SendDicRequests.java +++ /dev/null @@ -1,146 +0,0 @@ -package de.medizininformatik_initiative.feasibility_dsf_process.service; - -import dev.dsf.bpe.v1.ProcessPluginApi; -import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; -import dev.dsf.bpe.v1.constants.CodeSystems.BpmnMessage; -import dev.dsf.bpe.v1.constants.NamingSystems; -import dev.dsf.bpe.v1.variables.Target; -import dev.dsf.bpe.v1.variables.Targets; -import dev.dsf.bpe.v1.variables.Variables; -import org.camunda.bpm.engine.delegate.BpmnError; -import org.camunda.bpm.engine.delegate.DelegateExecution; -import org.camunda.bpm.engine.impl.el.FixedValue; -import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Identifier; -import org.hl7.fhir.r4.model.Meta; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.StringType; -import org.hl7.fhir.r4.model.Task; -import org.hl7.fhir.r4.model.Task.ParameterComponent; -import org.hl7.fhir.r4.model.Task.TaskStatus; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Date; -import java.util.concurrent.ForkJoinPool; -import java.util.stream.Collectors; - -import static com.google.common.base.Preconditions.checkNotNull; -import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.CODESYSTEM_FEASIBILITY; -import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.CODESYSTEM_FEASIBILITY_VALUE_MEASURE_REFERENCE; -import static org.hl7.fhir.r4.model.Task.TaskIntent.ORDER; - -public class SendDicRequests extends AbstractServiceDelegate { - - private static final Logger logger = LoggerFactory.getLogger(SendDicRequests.class); - - private final ForkJoinPool forkJoinPool; - - // set via field injection - private FixedValue instantiatesCanonical; - private FixedValue messageName; - private FixedValue profile; - - public SendDicRequests(ProcessPluginApi api, ForkJoinPool forkJoinPool) { - super(api); - this.forkJoinPool = forkJoinPool; - } - - public void setInstantiatesCanonical(FixedValue instantiatesCanonical) { - this.instantiatesCanonical = instantiatesCanonical; - } - - public void setMessageName(FixedValue messageName) { - this.messageName = messageName; - } - - public void setProfile(FixedValue profile) { - this.profile = profile; - } - - @Override - protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError, Exception { - Targets targets = variables.getTargets(); - Task targetTaskTemplate = createTargetTaskTemplate(execution, variables); - - forkJoinPool.submit( - () -> targets.getEntries() - .parallelStream() - .map((target) -> { - Task targetTask = targetTaskTemplate.copy(); - - targetTask.getRestriction().addRecipient(getRecipient(target)); - - String correlationKey = target.getCorrelationKey(); - if (correlationKey != null) { - ParameterComponent correlationKeyInput = new ParameterComponent( - new CodeableConcept( - new Coding(BpmnMessage.URL, BpmnMessage.Codes.CORRELATION_KEY, null)), - new StringType(correlationKey)); - targetTask.getInput().add(correlationKeyInput); - } - - return sendTask(targetTask, target); - }) - .collect(Collectors.toList())) - .get(); - } - - private Task createTargetTaskTemplate(DelegateExecution execution, Variables variables) { - String instantiatesCanonical = checkNotNull(this.instantiatesCanonical).getExpressionText(); - String messageName = checkNotNull(this.messageName).getExpressionText(); - String profile = checkNotNull(this.profile).getExpressionText(); - Reference measure = new Reference() - .setReference(checkNotNull(variables.getString("measure-id"), "variable 'measure-id' not set")); - String businessKey = execution.getBusinessKey(); - Task targetTask = new Task(); - targetTask.copy(); - targetTask.setMeta(new Meta().addProfile(profile)); - targetTask.setStatus(TaskStatus.REQUESTED); - targetTask.setIntent(ORDER); - targetTask.setAuthoredOn(new Date()); - targetTask.setRequester(getRequester()); - targetTask.setInstantiatesCanonical(instantiatesCanonical); - - ParameterComponent messageNameInput = new ParameterComponent( - new CodeableConcept(new Coding(BpmnMessage.URL, BpmnMessage.Codes.MESSAGE_NAME, null)), - new StringType(messageName)); - targetTask.addInput(messageNameInput); - - ParameterComponent businessKeyInput = new ParameterComponent( - new CodeableConcept(new Coding(BpmnMessage.URL, BpmnMessage.Codes.BUSINESS_KEY, null)), - new StringType(businessKey)); - targetTask.getInput().add(businessKeyInput); - targetTask.getInput().add(api.getTaskHelper().createInput(measure, CODESYSTEM_FEASIBILITY, - CODESYSTEM_FEASIBILITY_VALUE_MEASURE_REFERENCE)); - return targetTask; - } - - private Reference getRequester() { - return new Reference().setType("Organization") - .setIdentifier(new Identifier().setSystem(NamingSystems.OrganizationIdentifier.SID) - .setValue(api.getOrganizationProvider().getLocalOrganizationIdentifierValue().get())); - } - - private Reference getRecipient(Target target) { - return new Reference().setType("Organization") - .setIdentifier(new Identifier().setSystem(NamingSystems.OrganizationIdentifier.SID) - .setValue(target.getOrganizationIdentifierValue())); - } - - private Boolean sendTask(Task task, Target target) { - try { - logger.info("Sending task to target organization {}, endpoint {}", - target.getOrganizationIdentifierValue(), target.getEndpointUrl()); - api.getFhirWebserviceClientProvider() - .getWebserviceClient(target.getEndpointUrl()) - .create(task); - return true; - } catch (Exception e) { - logger.error("Error sending task to target organization {}, endpoint {}: {}", - target.getOrganizationIdentifierValue(), target.getEndpointUrl(), e.getMessage()); - return false; - } - } -} diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreFeasibilityResources.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreFeasibilityResources.java index 0d6a2a3..dd9743d 100755 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreFeasibilityResources.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreFeasibilityResources.java @@ -10,27 +10,27 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Library; import org.hl7.fhir.r4.model.Measure; -import org.hl7.fhir.r4.model.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; -import java.util.List; import java.util.Objects; -import static dev.dsf.fhir.authorization.read.ReadAccessHelper.READ_ACCESS_TAG_SYSTEM; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_LIBRARY; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_MEASURE; public class StoreFeasibilityResources extends AbstractServiceDelegate implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(StoreFeasibilityResources.class); - private static final String CQL_QUERY_CONTENT_TYPE = "text/cql"; private final IGenericClient storeClient; + private final FeasibilityResourceCleaner cleaner; - public StoreFeasibilityResources(IGenericClient storeClient, ProcessPluginApi api) { + public StoreFeasibilityResources(IGenericClient storeClient, ProcessPluginApi api, FeasibilityResourceCleaner cleaner) { super(api); this.storeClient = storeClient; + this.cleaner = cleaner; } @Override @@ -38,33 +38,21 @@ public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); Objects.requireNonNull(storeClient, "storeClient"); + Objects.requireNonNull(cleaner, "cleaner"); } @Override protected void doExecute(DelegateExecution execution, Variables variables) { - Measure measure = (Measure) variables.getResource(ConstantsFeasibility.VARIABLE_MEASURE); - Library library = (Library) variables.getResource(ConstantsFeasibility.VARIABLE_LIBRARY); + Measure measure = variables.getResource(VARIABLE_MEASURE); + Library library = variables.getResource(VARIABLE_LIBRARY); - var cleanedLibrary = stripReadAccessInformation(stripNonCqlAttachments(library)); - var libraryRes = storeLibraryResource(cleanedLibrary); - var measureRes = storeMeasureResource(stripReadAccessInformation(measure), libraryRes.getId()); + cleaner.cleanLibrary(library); + cleaner.cleanMeasure(measure); - variables.setString(ConstantsFeasibility.VARIABLE_MEASURE_ID, measureRes.getId().getIdPart()); - } - - private T stripReadAccessInformation(T resource) { - resource.getMeta().getTag().removeIf(t -> READ_ACCESS_TAG_SYSTEM.equals(t.getSystem())); - return resource; - } - - private Library stripNonCqlAttachments(Library library) { - var cqlAttachment = library.getContent() - .stream() - .filter(a -> a.getContentType().equalsIgnoreCase(CQL_QUERY_CONTENT_TYPE)) - .findFirst() - .orElseThrow(() -> new IllegalStateException("query is missing content of type " + CQL_QUERY_CONTENT_TYPE)); + var libraryRes = storeLibraryResource(library); + var measureRes = storeMeasureResource(measure, libraryRes.getId()); - return library.setContent(List.of(cqlAttachment)); + variables.setString(ConstantsFeasibility.VARIABLE_MEASURE_ID, measureRes.getId().getIdPart()); } private MethodOutcome storeLibraryResource(Library library) { diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreLiveResult.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreLiveResult.java index 7f3790b..218ab0f 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreLiveResult.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreLiveResult.java @@ -40,14 +40,12 @@ public StoreLiveResult(ProcessPluginApi api) { protected void doExecute(DelegateExecution execution, Variables variables) { Task task = variables.getLatestTask(); - MeasureReport measureReport = variables.getResource(VARIABLE_MEASURE_REPORT); + MeasureReport measureReport = (MeasureReport) execution.getVariableLocal(VARIABLE_MEASURE_REPORT); addReadAccessTag(measureReport); MeasureReport storedMeasureReport = storeMeasureReport(measureReport); addMeasureReportReferenceToTaskOutput(task, storedMeasureReport.getIdElement()); logger.info("Added measure report {} to {}", storedMeasureReport.getId(), task.getId()); - - variables.setResource(VARIABLE_MEASURE_REPORT, storedMeasureReport); } private void addReadAccessTag(MeasureReport measureReport) { diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/spring/config/BaseConfig.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/spring/config/BaseConfig.java index 9745f74..5cf7105 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/spring/config/BaseConfig.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/spring/config/BaseConfig.java @@ -1,17 +1,88 @@ package de.medizininformatik_initiative.feasibility_dsf_process.spring.config; import ca.uhn.fhir.context.FhirContext; +import org.apache.http.ssl.SSLContexts; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.lang.Nullable; + +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.net.ssl.SSLContext; @Configuration public class BaseConfig { + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_path:#{null}}") private String trustStorePath; + + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_password:#{null}}") private String trustStorePassword; + + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.key_store_path:#{null}}") private String keyStorePath; + + @Value("${de.medizininformatik_initiative.feasibility_dsf_process.client.store.key_store_password:#{null}}") private String keyStorePassword; + @Bean @Qualifier("base") FhirContext fhirContext() { return FhirContext.forR4(); } + @Bean + @Qualifier("base-client-trust") + KeyStore loadTrustStore() throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { + if (trustStorePath == null || trustStorePath.isBlank()) { + return DefaultTrustStoreUtils.loadDefaultTrustStore(); + } + + var trustStoreInputStream = new FileInputStream(trustStorePath); + + var trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(trustStoreInputStream, (trustStorePassword == null) ? null : trustStorePassword.toCharArray()); + trustStoreInputStream.close(); + + return trustStore; + } + + @Bean + @Qualifier("base-client-key") + @Nullable + KeyStore loadKeyStore() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException { + if (keyStorePath == null) { + return null; + } + + var keyStoreInputStream = new FileInputStream(keyStorePath); + + var keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(keyStoreInputStream, (keyStorePassword == null) ? null : keyStorePassword.toCharArray()); + keyStoreInputStream.close(); + + return keyStore; + } + + @Bean + @Qualifier("base-client") + SSLContext createSslContext(@Qualifier("base-client-trust") KeyStore trustStore, + @Nullable @Qualifier("base-client-key") KeyStore keyStore) + throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException, UnrecoverableKeyException { + var sslContextBuilder = SSLContexts.custom() + .loadTrustMaterial(trustStore, null); + + if (keyStore != null) { + sslContextBuilder.loadKeyMaterial(keyStore, (keyStorePassword == null) ? null : + keyStorePassword.toCharArray()); + } + + return sslContextBuilder.build(); + } + } diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/DefaultTrustStoreUtils.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/spring/config/DefaultTrustStoreUtils.java similarity index 95% rename from feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/DefaultTrustStoreUtils.java rename to feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/spring/config/DefaultTrustStoreUtils.java index a933266..0e5b7fd 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/DefaultTrustStoreUtils.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/spring/config/DefaultTrustStoreUtils.java @@ -1,4 +1,4 @@ -package de.medizininformatik_initiative.feasibility_dsf_process.client.store; +package de.medizininformatik_initiative.feasibility_dsf_process.spring.config; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -12,11 +12,11 @@ import java.security.cert.CertificateException; // TODO: doc -final class DefaultTrustStoreUtils { +public final class DefaultTrustStoreUtils { private DefaultTrustStoreUtils() { } - static KeyStore loadDefaultTrustStore() { + public static KeyStore loadDefaultTrustStore() { Path location = null; String type = null; String password = null; @@ -67,4 +67,4 @@ static KeyStore loadDefaultTrustStore() { return trustStore; } -} \ No newline at end of file +} diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/spring/config/FeasibilityConfig.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/spring/config/FeasibilityConfig.java index 0c1903e..fb6040c 100755 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/spring/config/FeasibilityConfig.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/spring/config/FeasibilityConfig.java @@ -5,21 +5,26 @@ import de.medizininformatik_initiative.feasibility_dsf_process.EnhancedFhirWebserviceClientProvider; import de.medizininformatik_initiative.feasibility_dsf_process.EnhancedFhirWebserviceClientProviderImpl; import de.medizininformatik_initiative.feasibility_dsf_process.EvaluationSettingsProvider; +import de.medizininformatik_initiative.feasibility_dsf_process.EvaluationStrategy; import de.medizininformatik_initiative.feasibility_dsf_process.FeasibilityCachingLaplaceCountObfuscator; +import de.medizininformatik_initiative.feasibility_dsf_process.FeasibilityProcessPluginDeploymentStateListener; import de.medizininformatik_initiative.feasibility_dsf_process.Obfuscator; import de.medizininformatik_initiative.feasibility_dsf_process.RateLimit; import de.medizininformatik_initiative.feasibility_dsf_process.client.flare.FlareWebserviceClient; +import de.medizininformatik_initiative.feasibility_dsf_process.client.listener.SetCorrelationKeyListener; +import de.medizininformatik_initiative.feasibility_dsf_process.message.SendDicRequest; import de.medizininformatik_initiative.feasibility_dsf_process.message.SendDicResponse; import de.medizininformatik_initiative.feasibility_dsf_process.service.DownloadFeasibilityResources; import de.medizininformatik_initiative.feasibility_dsf_process.service.DownloadMeasureReport; import de.medizininformatik_initiative.feasibility_dsf_process.service.EvaluateCqlMeasure; import de.medizininformatik_initiative.feasibility_dsf_process.service.EvaluateRequestRate; import de.medizininformatik_initiative.feasibility_dsf_process.service.EvaluateStructuredQueryMeasure; +import de.medizininformatik_initiative.feasibility_dsf_process.service.FeasibilityResourceCleaner; +import de.medizininformatik_initiative.feasibility_dsf_process.service.LogReceiveTimeout; import de.medizininformatik_initiative.feasibility_dsf_process.service.ObfuscateEvaluationResult; import de.medizininformatik_initiative.feasibility_dsf_process.service.RateLimitExceededTaskRejecter; import de.medizininformatik_initiative.feasibility_dsf_process.service.SelectRequestTargets; import de.medizininformatik_initiative.feasibility_dsf_process.service.SelectResponseTarget; -import de.medizininformatik_initiative.feasibility_dsf_process.service.SendDicRequests; import de.medizininformatik_initiative.feasibility_dsf_process.service.SetupEvaluationSettings; import de.medizininformatik_initiative.feasibility_dsf_process.service.StoreFeasibilityResources; import de.medizininformatik_initiative.feasibility_dsf_process.service.StoreLiveResult; @@ -33,8 +38,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; -import java.util.concurrent.ForkJoinPool; - @Configuration public class FeasibilityConfig { @@ -80,8 +83,8 @@ public SelectRequestTargets selectRequestTargets() { @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public SendDicRequests sendDicRequests(ForkJoinPool threadPool) { - return new SendDicRequests(api, threadPool); + public SendDicRequest sendDicRequests() { + return new SendDicRequest(api); } @Bean @@ -96,6 +99,18 @@ public StoreLiveResult storeLiveResult() { return new StoreLiveResult(api); } + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public LogReceiveTimeout logReceiveTimeout() { + return new LogReceiveTimeout(api); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public SetCorrelationKeyListener setCorrelationKeyListener() { + return new SetCorrelationKeyListener(api); + } + // // process executeFeasibility implementations // @@ -129,7 +144,7 @@ public DownloadFeasibilityResources downloadFeasibilityResources( @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public StoreFeasibilityResources storeFeasibilityResources() { - return new StoreFeasibilityResources(storeClient, api); + return new StoreFeasibilityResources(storeClient, api, new FeasibilityResourceCleaner()); } @Bean @@ -169,7 +184,11 @@ public SendDicResponse sendDicResponse() { } @Bean - public ForkJoinPool ioThreadPool() { - return new ForkJoinPool(8); + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public FeasibilityProcessPluginDeploymentStateListener deploymentStateListener() { + return new FeasibilityProcessPluginDeploymentStateListener( + EvaluationStrategy + .fromStrategyRepresentation(evaluationSettingsProvider.evaluationStrategyRepresentation()), + storeClient, flareWebserviceClient); } } diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/variables/ConstantsFeasibility.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/variables/ConstantsFeasibility.java index 24f74a7..ef5d0d4 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/variables/ConstantsFeasibility.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/variables/ConstantsFeasibility.java @@ -20,4 +20,7 @@ public interface ConstantsFeasibility { String CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION = "initial-population"; String EXTENSION_DIC_URI = "http://medizininformatik-initiative.de/fhir/StructureDefinition/dic"; + + String FEASIBILITY_REQUEST_PROCESS_ID = "medizininformatik-initiativede_feasibilityRequest"; + String FEASIBILITY_EXECUTE_PROCESS_ID = "medizininformatik-initiativede_feasibilityExecute"; } diff --git a/feasibility-dsf-process/src/main/resources/bpe/feasibilityExecute.bpmn b/feasibility-dsf-process/src/main/resources/bpe/feasibilityExecute.bpmn index 07897a9..96f46ac 100755 --- a/feasibility-dsf-process/src/main/resources/bpe/feasibilityExecute.bpmn +++ b/feasibility-dsf-process/src/main/resources/bpe/feasibilityExecute.bpmn @@ -9,24 +9,6 @@ Flow_0h1fgqi Flow_11cnoe1 - - - SequenceFlow_0ascyjc - - - - http://medizininformatik-initiative.de/fhir/StructureDefinition/feasibility-task-single-dic-result|#{version} - - - http://medizininformatik-initiative.de/bpe/Process/feasibilityRequest|#{version} - - - feasibilitySingleDicResultMessage - - - - - Flow_11lpcfm SequenceFlow_0ascyjc @@ -55,36 +37,36 @@ ${execution.getVariable('evaluation-strategy')=='cql'} - + ${execution.getVariable('evaluation-strategy')=='structured-query'} - + Flow_0gowl5w Flow_1cs9290 - + Flow_0r37nze Flow_10dmu4o - + Flow_1f6bge9 Flow_07fkz0p - + Flow_1qdam0i Flow_1cs9290 Flow_0r37nze Flow_1lvwaa9 - - - + + + ${execution.getVariable('evaluation-obfuscation')==true} - + ${execution.getVariable('evaluation-obfuscation')==false} - + Flow_07fkz0p Flow_0js57at @@ -97,20 +79,40 @@ ${execution.getVariable('request-rate-below-limit')} - + Flow_0ug5qml Flow_1gqxni9 - + ${execution.getVariable('request-rate-below-limit') == false} - - + + - - + + Flow_1gqxni9 + + + R3/PT5S + + SequenceFlow_0ascyjc + + + + http://medizininformatik-initiative.de/fhir/StructureDefinition/feasibility-task-single-dic-result|#{version} + + + http://medizininformatik-initiative.de/bpe/Process/feasibilityRequest|#{version} + + + feasibilitySingleDicResultMessage + + + + + @@ -125,10 +127,10 @@ - + - + @@ -149,16 +151,16 @@ - + - + - + - + @@ -173,10 +175,10 @@ - + - + diff --git a/feasibility-dsf-process/src/main/resources/bpe/feasibilityRequest.bpmn b/feasibility-dsf-process/src/main/resources/bpe/feasibilityRequest.bpmn index 1ebe489..b8e27e1 100755 --- a/feasibility-dsf-process/src/main/resources/bpe/feasibilityRequest.bpmn +++ b/feasibility-dsf-process/src/main/resources/bpe/feasibilityRequest.bpmn @@ -7,88 +7,107 @@ SequenceFlow_11k77gx - Flow_14amax3 + Flow_0j3kdv8 - Flow_00sw3oh + Flow_08iflnw - - Flow_1qn1bcu - Flow_05o62hi - - - + + Flow_0j3kdv8 + Flow_08iflnw + + + R0/PT5S + + + + Flow_1oq6cy1 Flow_097xx9e - - Flow_14foi4g + + Flow_02h2l8y Flow_1oq6cy1 - - Flow_097xx9e - - - + - - ${target.correlationKey} - + R0/PT5S - Flow_1d7lpns - Flow_14foi4g - - - + Flow_0qr31l0 + + + Flow_1d7lpns - + + + + Flow_1d7lpns + Flow_1whfhqu + + + + http://medizininformatik-initiative.de/fhir/StructureDefinition/feasibility-task-execute|#{version} + + + feasibilityExecuteMessage + + + http://medizininformatik-initiative.de/bpe/Process/feasibilityExecute|#{version} + + + + + + + + Flow_0uyhw70 + Flow_02h2l8y + + + + + + + Flow_02yveoj + Flow_0uyhw70 + Flow_1epm5e3 + + + + Flow_1epm5e3 + Flow_0pq3j9e + + PT5M + + + + + Flow_0pq3j9e + Flow_0gkh900 + + + + + Flow_1whfhqu + Flow_02yveoj + HasSendError + + + + ${execution.hasVariable("sendError")} + + + Flow_097xx9e + Flow_0gkh900 + HasSendError + Flow_0qr31l0 + + - - - - Flow_0sb70bv - - PT45S - - - - - - - http://medizininformatik-initiative.de/fhir/StructureDefinition/feasibility-task-execute|#{version} - - - feasibilityExecuteMessage - - - http://medizininformatik-initiative.de/bpe/Process/feasibilityExecute|#{version} - - - Flow_0faz7g4 - Flow_05069oz - - - - Flow_14amax3 - Flow_1qn1bcu - Flow_0faz7g4 - - - - Flow_0adxuc3 - Flow_05069oz - Flow_00sw3oh - - - Flow_05o62hi - Flow_0sb70bv - Flow_0adxuc3 - - - - + + @@ -108,110 +127,141 @@ - + - + - + - + - + - - + + + - - + + + + + - - + + + + + - - + + + + + - - - + + - - + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + - - + + - - + + - - + + + + + - - + + - - - + + + - - - + + + - - - + + + - - - - + + + - - - - + + + + - - - - + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + - - - - + + + - - - + + + diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/FeasibilityProcessPluginDeploymentStateListenerTest.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/FeasibilityProcessPluginDeploymentStateListenerTest.java new file mode 100644 index 0000000..93372a2 --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/FeasibilityProcessPluginDeploymentStateListenerTest.java @@ -0,0 +1,146 @@ +package de.medizininformatik_initiative.feasibility_dsf_process; + +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.gclient.IFetchConformanceTyped; +import ca.uhn.fhir.rest.gclient.IFetchConformanceUntyped; +import de.medizininformatik_initiative.feasibility_dsf_process.client.flare.FlareWebserviceClient; +import org.hl7.fhir.r4.model.CapabilityStatement; +import org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementSoftwareComponent; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.List; + +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.FEASIBILITY_EXECUTE_PROCESS_ID; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.FEASIBILITY_REQUEST_PROCESS_ID; +import static java.lang.String.format; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class FeasibilityProcessPluginDeploymentStateListenerTest { + + @Mock FlareWebserviceClient flareClient; + @Mock IGenericClient storeClient; + @Mock IFetchConformanceUntyped untypedFetch; + @Mock IFetchConformanceTyped typedFetch; + private final ByteArrayOutputStream out = new ByteArrayOutputStream(); + private final ByteArrayOutputStream err = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + private final PrintStream originalErr = System.err; + + @BeforeEach + void setup() { + System.setOut(new PrintStream(out)); + System.setErr(new PrintStream(err)); + } + + @AfterEach + void restoreStreams() { + System.setOut(originalOut); + System.setErr(originalErr); + } + + @Test + @DisplayName("connection test is not executed when no process is deployed.") + void noConnectionTestIfNoProcessDeployed() throws Exception { + FeasibilityProcessPluginDeploymentStateListener listener = new FeasibilityProcessPluginDeploymentStateListener( + EvaluationStrategy.CQL, storeClient, flareClient); + + listener.onProcessesDeployed(List.of()); + + verify(flareClient, never()).requestFeasibility(any(byte[].class)); + } + + @Test + @DisplayName("connection test is not executed when 'feasibilityExecute' process is not deployed.") + void noConnectionTestIfExecuteProcessIsNotDeployed() throws Exception { + FeasibilityProcessPluginDeploymentStateListener listener = new FeasibilityProcessPluginDeploymentStateListener( + EvaluationStrategy.CQL, storeClient, flareClient); + + listener.onProcessesDeployed(List.of("foo", FEASIBILITY_REQUEST_PROCESS_ID, "bar")); + + verify(flareClient, never()).requestFeasibility(any(byte[].class)); + } + + @Test + @DisplayName("flare client connection test succeeds when evaluation strategy is 'structured-query' and no error occurs") + void flareClientConnectionTestSucceeds() throws Exception { + + FeasibilityProcessPluginDeploymentStateListener listener = new FeasibilityProcessPluginDeploymentStateListener( + EvaluationStrategy.STRUCTURED_QUERY, storeClient, flareClient); + + listener.onProcessesDeployed(List.of(FEASIBILITY_EXECUTE_PROCESS_ID)); + + verify(flareClient).testConnection(); + assertThat(out.toString(), containsString("Connection test OK (flare)")); + } + + @Test + @DisplayName("store client connection test succeeds when evaluation strategy is 'cql' and no error occurs") + void storeClientConnectionTestSucceeds() throws Exception { + String softwareName = "software-213030"; + String softwareVersion = "version-213136"; + CapabilityStatementSoftwareComponent software = new CapabilityStatementSoftwareComponent() + .setName(softwareName) + .setVersion(softwareVersion); + CapabilityStatement statement = new CapabilityStatement().setSoftware(software); + when(storeClient.capabilities()).thenReturn(untypedFetch); + when(untypedFetch.ofType(CapabilityStatement.class)).thenReturn(typedFetch); + when(typedFetch.execute()).thenReturn(statement); + + FeasibilityProcessPluginDeploymentStateListener listener = new FeasibilityProcessPluginDeploymentStateListener( + EvaluationStrategy.CQL, storeClient, flareClient); + + listener.onProcessesDeployed(List.of(FEASIBILITY_EXECUTE_PROCESS_ID)); + assertThat(out.toString(), + containsString(format("Connection test OK (%s - %s)", softwareName, softwareVersion))); + } + + @Test + @DisplayName("flare client connection test fails when evaluation strategy is 'structured-query' and error occurs") + void flareClientConnectionTestFails() throws Exception { + String errorMessage = "error-223236"; + IOException exception = new IOException(errorMessage); + doThrow(exception).when(flareClient).testConnection(); + FeasibilityProcessPluginDeploymentStateListener listener = new FeasibilityProcessPluginDeploymentStateListener( + EvaluationStrategy.STRUCTURED_QUERY, storeClient, flareClient); + + listener.onProcessesDeployed(List.of(FEASIBILITY_EXECUTE_PROCESS_ID)); + + assertThat(out.toString(), containsString(format("Connection test FAILED (flare) - error: %s - %s", + exception.getClass().getName(), errorMessage))); + } + + @Test + @DisplayName("store client connection test fails when evaluation strategy is 'cql' and error occurs") + void storeClientConnectionTestFails() throws Exception { + String errorMessage = "error-223622"; + RuntimeException exception = new RuntimeException(errorMessage); + when(storeClient.capabilities()).thenReturn(untypedFetch); + when(untypedFetch.ofType(CapabilityStatement.class)).thenReturn(typedFetch); + doThrow(exception).when(typedFetch).execute(); + + FeasibilityProcessPluginDeploymentStateListener listener = new FeasibilityProcessPluginDeploymentStateListener( + EvaluationStrategy.CQL, storeClient, flareClient); + + listener.onProcessesDeployed(List.of(FEASIBILITY_EXECUTE_PROCESS_ID)); + assertThat(out.toString(), + containsString(format("Connection test FAILED - error: %s - %s", exception.getClass().getName(), + errorMessage))); + + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplBaseIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplBaseIT.java new file mode 100644 index 0000000..0d45968 --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplBaseIT.java @@ -0,0 +1,39 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.utility.DockerImageName; + +import java.net.URL; +import java.time.Duration; +import java.util.Map; + +public abstract class FlareWebserviceClientImplBaseIT { + + protected static final Network DEFAULT_CONTAINER_NETWORK = Network.newNetwork(); + + @Container + public static GenericContainer fhirServer = new GenericContainer<>( + DockerImageName.parse("samply/blaze:0.23.0")) + .withExposedPorts(8080) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .withNetworkAliases("fhir-server") + .withEnv("LOG_LEVEL", "debug"); + + @Container + public static GenericContainer flare = new GenericContainer<>( + DockerImageName.parse("ghcr.io/medizininformatik-initiative/flare:2.1.0")) + .withExposedPorts(8080) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .withNetworkAliases("flare") + .withEnv(Map.of( + "FLARE_FHIR_SERVER", "http://fhir-server:8080/fhir/" + )) + .withStartupTimeout(Duration.ofMinutes(5)) + .dependsOn(fhirServer); + + protected static URL getResource(final String name) { + return FlareWebserviceClientImplBaseIT.class.getResource(name); + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdProxyBasicAuthIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdProxyBasicAuthIT.java new file mode 100644 index 0000000..a5140df --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdProxyBasicAuthIT.java @@ -0,0 +1,64 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.testcontainers.containers.BindMode.READ_ONLY; + +@Tag("client") +@Tag("flare") +@SpringBootTest(classes = FlareWebserviceClientSpringConfig.class) +@Testcontainers +public class FlareWebserviceClientImplFwdProxyBasicAuthIT extends FlareWebserviceClientImplBaseIT { + + @Autowired + protected FlareWebserviceClient flareClient; + + private static URL squidProxyConf = getResource("forward_proxy_basic_auth.conf"); + private static URL passwordFile = getResource("forward_proxy.htpasswd"); + + @Container + public static GenericContainer forwardProxy = new GenericContainer<>( + DockerImageName.parse("ubuntu/squid:6.1-23.10_edge")) + .withExposedPorts(8080) + .withFileSystemBind(squidProxyConf.getPath(), "/etc/squid/squid.conf", READ_ONLY) + .withFileSystemBind(passwordFile.getPath(), "/etc/squid/passwd", READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .dependsOn(flare); + + @DynamicPropertySource + static void dynamicProperties(DynamicPropertyRegistry registry) { + var proxyHost = forwardProxy.getHost(); + var proxyPort = forwardProxy.getFirstMappedPort(); + + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.flare.base_url", + () -> "http://flare:8080/"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.host", + () -> proxyHost); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.port", + () -> proxyPort); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.username", + () -> "test"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.password", + () -> "bar"); + } + + @Test + void sendQuery() throws Exception { + var rawStructuredQuery = this.getClass().getResource("valid-structured-query.json").openStream().readAllBytes(); + var feasibility = assertDoesNotThrow(() -> flareClient.requestFeasibility(rawStructuredQuery)); + assertEquals(0, feasibility); + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdProxyBasicAuthRevProxyBearerTokenAuthIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdProxyBasicAuthRevProxyBearerTokenAuthIT.java new file mode 100644 index 0000000..3e89e22 --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdProxyBasicAuthRevProxyBearerTokenAuthIT.java @@ -0,0 +1,82 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.testcontainers.containers.BindMode.READ_ONLY; + +@Tag("client") +@Tag("flare") +@SpringBootTest(classes = FlareWebserviceClientSpringConfig.class) +@Testcontainers +public class FlareWebserviceClientImplFwdProxyBasicAuthRevProxyBearerTokenAuthIT extends FlareWebserviceClientImplBaseIT { + + @Autowired + protected FlareWebserviceClient flareClient; + + private static URL nginxConf = getResource("nginx.conf"); + private static URL nginxTestProxyConfTemplate = getResource("reverse_proxy_bearer_token_auth.conf.template"); + private static URL indexFile = getResource("index.html"); + private static URL squidProxyConf = getResource("forward_proxy_basic_auth.conf"); + private static URL forwardProxyPasswordFile = getResource("forward_proxy.htpasswd"); + private static String bearerToken = "1234"; + + @Container + public static GenericContainer proxy = new GenericContainer<>( + DockerImageName.parse("nginx:1.25.1")) + .withExposedPorts(8080) + .withFileSystemBind(nginxConf.getPath(), "/etc/nginx/nginx.conf", READ_ONLY) + .withFileSystemBind(indexFile.getPath(), "/usr/share/nginx/html/index.html", READ_ONLY) + .withFileSystemBind(nginxTestProxyConfTemplate.getPath(), + "/etc/nginx/templates/default.conf.template", + READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .withNetworkAliases("proxy") + .dependsOn(flare); + @Container + public static GenericContainer forwardProxy = new GenericContainer<>( + DockerImageName.parse("ubuntu/squid:6.1-23.10_edge")) + .withExposedPorts(8080) + .withFileSystemBind(squidProxyConf.getPath(), "/etc/squid/squid.conf", READ_ONLY) + .withFileSystemBind(forwardProxyPasswordFile.getPath(), "/etc/squid/passwd", READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .dependsOn(proxy); + + @DynamicPropertySource + static void dynamicProperties(DynamicPropertyRegistry registry) { + var proxyHost = forwardProxy.getHost(); + var proxyPort = forwardProxy.getFirstMappedPort(); + + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.flare.base_url", + () -> "http://proxy:8080/"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.host", + () -> proxyHost); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.port", + () -> proxyPort); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.username", + () -> "test"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.password", + () -> "bar"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.bearer.token", + () -> bearerToken); + } + + @Test + void sendQuery() throws Exception { + var rawStructuredQuery = this.getClass().getResource("valid-structured-query.json").openStream().readAllBytes(); + var feasibility = assertDoesNotThrow(() -> flareClient.requestFeasibility(rawStructuredQuery)); + assertEquals(0, feasibility); + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdProxyBasicAuthRevProxyTlsIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdProxyBasicAuthRevProxyTlsIT.java new file mode 100644 index 0000000..73fdb62 --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdProxyBasicAuthRevProxyTlsIT.java @@ -0,0 +1,89 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.testcontainers.containers.BindMode.READ_ONLY; + +@Tag("client") +@Tag("flare") +@SpringBootTest(classes = FlareWebserviceClientSpringConfig.class) +@Testcontainers +public class FlareWebserviceClientImplFwdProxyBasicAuthRevProxyTlsIT extends FlareWebserviceClientImplBaseIT { + + @Autowired + protected FlareWebserviceClient flareClient; + + private static URL squidProxyConf = getResource("forward_proxy_basic_auth.conf"); + private static URL passwordFile = getResource("forward_proxy.htpasswd"); + private static URL nginxConf = getResource("nginx.conf"); + private static URL nginxTestProxyConfTemplate = getResource("reverse_proxy_tls.conf.template"); + private static URL indexFile = getResource("index.html"); + private static URL serverCertChain = getResource("../certs/server_cert_chain.pem"); + private static URL serverCertKey = getResource("../certs/server_cert_key.pem"); + private static URL trustStoreFile = getResource("../certs/ca.p12"); + + @Container + public static GenericContainer proxy = new GenericContainer<>( + DockerImageName.parse("nginx:1.25.1")) + .withExposedPorts(8443) + .withFileSystemBind(nginxConf.getPath(), "/etc/nginx/nginx.conf", READ_ONLY) + .withFileSystemBind(indexFile.getPath(), "/usr/share/nginx/html/index.html", READ_ONLY) + .withFileSystemBind(nginxTestProxyConfTemplate.getPath(), + "/etc/nginx/templates/default.conf.template", + READ_ONLY) + .withFileSystemBind(serverCertChain.getPath(), "/etc/nginx/certs/server_cert.pem", READ_ONLY) + .withFileSystemBind(serverCertKey.getPath(), "/etc/nginx/certs/server_cert_key.pem", READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .withNetworkAliases("proxy") + .dependsOn(flare); + + @Container + public static GenericContainer forwardProxy = new GenericContainer<>( + DockerImageName.parse("ubuntu/squid:6.1-23.10_edge")) + .withExposedPorts(8080) + .withFileSystemBind(squidProxyConf.getPath(), "/etc/squid/squid.conf", READ_ONLY) + .withFileSystemBind(passwordFile.getPath(), "/etc/squid/passwd", READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .dependsOn(proxy); + + @DynamicPropertySource + static void dynamicProperties(DynamicPropertyRegistry registry) { + var proxyHost = forwardProxy.getHost(); + var proxyPort = forwardProxy.getFirstMappedPort(); + + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.flare.base_url", + () -> "https://proxy:8443/"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_path", + () -> trustStoreFile.getPath()); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_password", + () -> "changeit"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.host", + () -> proxyHost); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.port", + () -> proxyPort); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.username", + () -> "test"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.password", + () -> "bar"); + } + + @Test + void sendQuery() throws Exception { + var rawStructuredQuery = this.getClass().getResource("valid-structured-query.json").openStream().readAllBytes(); + var feasibility = assertDoesNotThrow(() -> flareClient.requestFeasibility(rawStructuredQuery)); + assertEquals(0, feasibility); + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdProxyIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdProxyIT.java new file mode 100644 index 0000000..2d8f828 --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdProxyIT.java @@ -0,0 +1,58 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.testcontainers.containers.BindMode.READ_ONLY; + +@Tag("client") +@Tag("flare") +@SpringBootTest(classes = FlareWebserviceClientSpringConfig.class) +@Testcontainers +public class FlareWebserviceClientImplFwdProxyIT extends FlareWebserviceClientImplBaseIT { + + @Autowired + protected FlareWebserviceClient flareClient; + + private static URL squidProxyConf = getResource("forward_proxy.conf"); + + @Container + public static GenericContainer forwardProxy = new GenericContainer<>( + DockerImageName.parse("ubuntu/squid:6.1-23.10_edge")) + .withExposedPorts(8080) + .withFileSystemBind(squidProxyConf.getPath(), "/etc/squid/squid.conf", READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .dependsOn(flare); + + @DynamicPropertySource + static void dynamicProperties(DynamicPropertyRegistry registry) { + var proxyHost = forwardProxy.getHost(); + var proxyPort = forwardProxy.getFirstMappedPort(); + + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.flare.base_url", + () -> "http://flare:8080/"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.host", + () -> proxyHost); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.port", + () -> proxyPort); + } + + @Test + void sendQuery() throws Exception { + var rawStructuredQuery = this.getClass().getResource("valid-structured-query.json").openStream().readAllBytes(); + var feasibility = assertDoesNotThrow(() -> flareClient.requestFeasibility(rawStructuredQuery)); + assertEquals(0, feasibility); + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdRevProxyBasicAuthIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdRevProxyBasicAuthIT.java new file mode 100644 index 0000000..fe88bfa --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplFwdRevProxyBasicAuthIT.java @@ -0,0 +1,85 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.testcontainers.containers.BindMode.READ_ONLY; + +@Tag("client") +@Tag("flare") +@SpringBootTest(classes = FlareWebserviceClientSpringConfig.class) +@Testcontainers +public class FlareWebserviceClientImplFwdRevProxyBasicAuthIT extends FlareWebserviceClientImplBaseIT { + + @Autowired + protected FlareWebserviceClient flareClient; + + private static URL nginxConf = getResource("nginx.conf"); + private static URL nginxTestProxyConfTemplate = getResource("reverse_proxy_basic_auth.conf.template"); + private static URL indexFile = getResource("index.html"); + private static URL reverseProxyPasswordFile = getResource("reverse_proxy.htpasswd"); + private static URL squidProxyConf = getResource("forward_proxy_basic_auth.conf"); + private static URL forwardProxyPasswordFile = getResource("forward_proxy.htpasswd"); + + @Container + public static GenericContainer proxy = new GenericContainer<>( + DockerImageName.parse("nginx:1.25.1")) + .withExposedPorts(8080) + .withFileSystemBind(nginxConf.getPath(), "/etc/nginx/nginx.conf", READ_ONLY) + .withFileSystemBind(indexFile.getPath(), "/usr/share/nginx/html/index.html", READ_ONLY) + .withFileSystemBind(nginxTestProxyConfTemplate.getPath(), + "/etc/nginx/templates/default.conf.template", + READ_ONLY) + .withFileSystemBind(reverseProxyPasswordFile.getPath(), "/etc/auth/.htpasswd", READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .withNetworkAliases("proxy") + .dependsOn(flare); + @Container + public static GenericContainer forwardProxy = new GenericContainer<>( + DockerImageName.parse("ubuntu/squid:6.1-23.10_edge")) + .withExposedPorts(8080) + .withFileSystemBind(squidProxyConf.getPath(), "/etc/squid/squid.conf", READ_ONLY) + .withFileSystemBind(forwardProxyPasswordFile.getPath(), "/etc/squid/passwd", READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .dependsOn(proxy); + + @DynamicPropertySource + static void dynamicProperties(DynamicPropertyRegistry registry) { + var proxyHost = forwardProxy.getHost(); + var proxyPort = forwardProxy.getFirstMappedPort(); + + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.flare.base_url", + () -> "http://proxy:8080/"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.host", + () -> proxyHost); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.port", + () -> proxyPort); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.username", + () -> "test"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.proxy.password", + () -> "bar"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.basic.username", + () -> "test"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.basic.password", + () -> "foo"); + } + + @Test + void sendQuery() throws Exception { + var rawStructuredQuery = this.getClass().getResource("valid-structured-query.json").openStream().readAllBytes(); + var feasibility = assertDoesNotThrow(() -> flareClient.requestFeasibility(rawStructuredQuery)); + assertEquals(0, feasibility); + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplNonProxyIT.java similarity index 51% rename from feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplIT.java rename to feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplNonProxyIT.java index f75711b..64d1446 100644 --- a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplIT.java +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplNonProxyIT.java @@ -6,15 +6,9 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.Network; -import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; import java.io.IOException; -import java.time.Duration; -import java.util.Map; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -23,30 +17,10 @@ @Tag("flare") @SpringBootTest(classes = FlareWebserviceClientSpringConfig.class) @Testcontainers -public class FlareWebserviceClientImplIT { +public class FlareWebserviceClientImplNonProxyIT extends FlareWebserviceClientImplBaseIT { @Autowired - private FlareWebserviceClient flareClient; - - private static final Network DEFAULT_CONTAINER_NETWORK = Network.newNetwork(); - - @Container - public static GenericContainer fhirServer = new GenericContainer<>(DockerImageName.parse("samply/blaze:0.22.2")) - .withExposedPorts(8080) - .withNetwork(DEFAULT_CONTAINER_NETWORK) - .withNetworkAliases("fhir-server") - .withEnv("LOG_LEVEL", "debug"); - - @Container - public static GenericContainer flare = new GenericContainer<>(DockerImageName.parse("ghcr.io/medizininformatik-initiative/flare:0.2.3")) - .withExposedPorts(8080) - .withNetwork(DEFAULT_CONTAINER_NETWORK) - .withNetworkAliases("flare") - .withEnv(Map.of( - "FLARE_FHIR_SERVER", "http://fhir-server:8080/fhir/" - )) - .withStartupTimeout(Duration.ofMinutes(5)) - .dependsOn(fhirServer); + protected FlareWebserviceClient flareClient; @DynamicPropertySource static void dynamicProperties(DynamicPropertyRegistry registry) { @@ -58,7 +32,7 @@ static void dynamicProperties(DynamicPropertyRegistry registry) { } @Test - public void testRequestToFlareWithEmptyFhirServer() throws IOException { + public void sendQuery() throws IOException { var rawStructuredQuery = this.getClass().getResource("valid-structured-query.json") .openStream().readAllBytes(); diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyBasicAuthIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyBasicAuthIT.java new file mode 100644 index 0000000..c00262b --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyBasicAuthIT.java @@ -0,0 +1,68 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.testcontainers.containers.BindMode.READ_ONLY; + +@Tag("client") +@Tag("flare") +@SpringBootTest(classes = FlareWebserviceClientSpringConfig.class) +@Testcontainers +public class FlareWebserviceClientImplRevProxyBasicAuthIT extends FlareWebserviceClientImplBaseIT { + + @Autowired + protected FlareWebserviceClient flareClient; + + private static URL nginxConf = getResource("nginx.conf"); + private static URL nginxTestProxyConfTemplate = getResource("reverse_proxy_basic_auth.conf.template"); + private static URL indexFile = getResource("index.html"); + private static URL passwordFile = getResource("reverse_proxy.htpasswd"); + private static String basicAuthUsername = "test"; + private static String basicAuthPassword = "foo"; + + @Container + public static GenericContainer proxy = new GenericContainer<>( + DockerImageName.parse("nginx:1.25.1")) + .withExposedPorts(8080) + .withFileSystemBind(nginxConf.getPath(), "/etc/nginx/nginx.conf", READ_ONLY) + .withFileSystemBind(indexFile.getPath(), "/usr/share/nginx/html/index.html", READ_ONLY) + .withFileSystemBind(nginxTestProxyConfTemplate.getPath(), + "/etc/nginx/templates/default.conf.template", + READ_ONLY) + .withFileSystemBind(passwordFile.getPath(), "/etc/auth/.htpasswd", READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .dependsOn(flare); + + @DynamicPropertySource + static void dynamicProperties(DynamicPropertyRegistry registry) { + var proxyHost = proxy.getHost(); + var proxyPort = proxy.getFirstMappedPort(); + + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.flare.base_url", + () -> String.format("http://%s:%s/", proxyHost, proxyPort)); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.basic.username", + () -> basicAuthUsername); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.basic.password", + () -> basicAuthPassword); + } + + @Test + void sendQuery() throws Exception { + var rawStructuredQuery = this.getClass().getResource("valid-structured-query.json").openStream().readAllBytes(); + var feasibility = assertDoesNotThrow(() -> flareClient.requestFeasibility(rawStructuredQuery)); + assertEquals(0, feasibility); + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyBearerTokenAuthIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyBearerTokenAuthIT.java new file mode 100644 index 0000000..9646393 --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyBearerTokenAuthIT.java @@ -0,0 +1,65 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.testcontainers.containers.BindMode.READ_ONLY; + +@Tag("client") +@Tag("flare") +@SpringBootTest(classes = FlareWebserviceClientSpringConfig.class) +@Testcontainers +public class FlareWebserviceClientImplRevProxyBearerTokenAuthIT extends FlareWebserviceClientImplBaseIT { + + @Autowired + protected FlareWebserviceClient flareClient; + + private static URL nginxConf = getResource("nginx.conf"); + private static URL nginxTestProxyConfTemplate = getResource("reverse_proxy_bearer_token_auth.conf.template"); + private static URL indexFile = getResource("index.html"); + private static URL passwordFile = getResource("reverse_proxy.htpasswd"); + private static String bearerToken = "1234"; + + @Container + public static GenericContainer proxy = new GenericContainer<>( + DockerImageName.parse("nginx:1.25.1")) + .withExposedPorts(8080) + .withFileSystemBind(nginxConf.getPath(), "/etc/nginx/nginx.conf", READ_ONLY) + .withFileSystemBind(indexFile.getPath(), "/usr/share/nginx/html/index.html", READ_ONLY) + .withFileSystemBind(nginxTestProxyConfTemplate.getPath(), + "/etc/nginx/templates/default.conf.template", + READ_ONLY) + .withFileSystemBind(passwordFile.getPath(), "/etc/auth/.htpasswd", READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .dependsOn(flare); + + @DynamicPropertySource + static void dynamicProperties(DynamicPropertyRegistry registry) { + var proxyHost = proxy.getHost(); + var proxyPort = proxy.getFirstMappedPort(); + + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.flare.base_url", + () -> String.format("http://%s:%s/", proxyHost, proxyPort)); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.bearer.token", + () -> bearerToken); + } + + @Test + void sendQuery() throws Exception { + var rawStructuredQuery = this.getClass().getResource("valid-structured-query.json").openStream().readAllBytes(); + var feasibility = assertDoesNotThrow(() -> flareClient.requestFeasibility(rawStructuredQuery)); + assertEquals(0, feasibility); + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyTlsBasicAuthIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyTlsBasicAuthIT.java new file mode 100644 index 0000000..12b7ba4 --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyTlsBasicAuthIT.java @@ -0,0 +1,77 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.testcontainers.containers.BindMode.READ_ONLY; + +@Tag("client") +@Tag("flare") +@SpringBootTest(classes = FlareWebserviceClientSpringConfig.class) +@Testcontainers +public class FlareWebserviceClientImplRevProxyTlsBasicAuthIT extends FlareWebserviceClientImplBaseIT { + + @Autowired + protected FlareWebserviceClient flareClient; + + private static URL nginxConf = getResource("nginx.conf"); + private static URL nginxTestProxyConfTemplate = getResource("reverse_proxy_tls.conf.template"); + private static URL indexFile = getResource("index.html"); + private static URL serverCertChain = getResource("../certs/server_cert_chain.pem"); + private static URL serverCertKey = getResource("../certs/server_cert_key.pem"); + private static URL trustStoreFile = getResource("../certs/ca.p12"); + private static URL passwordFile = getResource("reverse_proxy.htpasswd"); + private static String basicAuthUsername = "test"; + private static String basicAuthPassword = "foo"; + + @Container + public static GenericContainer proxy = new GenericContainer<>( + DockerImageName.parse("nginx:1.25.1")) + .withExposedPorts(8443) + .withFileSystemBind(nginxConf.getPath(), "/etc/nginx/nginx.conf", READ_ONLY) + .withFileSystemBind(indexFile.getPath(), "/usr/share/nginx/html/index.html", READ_ONLY) + .withFileSystemBind(nginxTestProxyConfTemplate.getPath(), + "/etc/nginx/templates/default.conf.template", + READ_ONLY) + .withFileSystemBind(serverCertChain.getPath(), "/etc/nginx/certs/server_cert.pem", READ_ONLY) + .withFileSystemBind(serverCertKey.getPath(), "/etc/nginx/certs/server_cert_key.pem", READ_ONLY) + .withFileSystemBind(passwordFile.getPath(), "/etc/auth/.htpasswd", READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .dependsOn(flare); + + @DynamicPropertySource + static void dynamicProperties(DynamicPropertyRegistry registry) { + var proxyHost = proxy.getHost(); + var proxyPort = proxy.getFirstMappedPort(); + + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.flare.base_url", + () -> String.format("https://%s:%s/", proxyHost, proxyPort)); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_path", + () -> trustStoreFile.getPath()); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_password", + () -> "changeit"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.basic.username", + () -> basicAuthUsername); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.auth.basic.password", + () -> basicAuthPassword); + } + + @Test + void sendQuery() throws Exception { + var rawStructuredQuery = this.getClass().getResource("valid-structured-query.json").openStream().readAllBytes(); + var feasibility = assertDoesNotThrow(() -> flareClient.requestFeasibility(rawStructuredQuery)); + assertEquals(0, feasibility); + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyTlsClientCertIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyTlsClientCertIT.java new file mode 100644 index 0000000..c628a25 --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyTlsClientCertIT.java @@ -0,0 +1,74 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.testcontainers.containers.BindMode.READ_ONLY; + +@Tag("client") +@Tag("flare") +@SpringBootTest(classes = FlareWebserviceClientSpringConfig.class) +@Testcontainers +public class FlareWebserviceClientImplRevProxyTlsClientCertIT extends FlareWebserviceClientImplBaseIT { + + @Autowired + protected FlareWebserviceClient flareClient; + + private static URL nginxConf = getResource("nginx.conf"); + private static URL nginxTestProxyConfTemplate = getResource("reverse_proxy_tls.conf.template"); + private static URL indexFile = getResource("index.html"); + private static URL serverCertChain = getResource("../certs/server_cert_chain.pem"); + private static URL serverCertKey = getResource("../certs/server_cert_key.pem"); + private static URL trustStoreFile = getResource("../certs/ca.p12"); + private static URL keyStoreFile = getResource("../certs/client_key_store.p12"); + + @Container + public static GenericContainer proxy = new GenericContainer<>( + DockerImageName.parse("nginx:1.25.1")) + .withExposedPorts(8443) + .withFileSystemBind(nginxConf.getPath(), "/etc/nginx/nginx.conf", READ_ONLY) + .withFileSystemBind(indexFile.getPath(), "/usr/share/nginx/html/index.html", READ_ONLY) + .withFileSystemBind(nginxTestProxyConfTemplate.getPath(), + "/etc/nginx/templates/default.conf.template", + READ_ONLY) + .withFileSystemBind(serverCertChain.getPath(), "/etc/nginx/certs/server_cert.pem", READ_ONLY) + .withFileSystemBind(serverCertKey.getPath(), "/etc/nginx/certs/server_cert_key.pem", READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .dependsOn(flare); + + @DynamicPropertySource + static void dynamicProperties(DynamicPropertyRegistry registry) { + var proxyHost = proxy.getHost(); + var proxyPort = proxy.getFirstMappedPort(); + + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.flare.base_url", + () -> String.format("https://%s:%s/", proxyHost, proxyPort)); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_path", + () -> trustStoreFile.getPath()); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_password", + () -> "changeit"); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.key_store_path", + () -> keyStoreFile.getPath()); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.key_store_password", + () -> "changeit"); + } + + @Test + void sendQuery() throws Exception { + var rawStructuredQuery = this.getClass().getResource("valid-structured-query.json").openStream().readAllBytes(); + var feasibility = assertDoesNotThrow(() -> flareClient.requestFeasibility(rawStructuredQuery)); + assertEquals(0, feasibility); + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyTlsIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyTlsIT.java new file mode 100644 index 0000000..5f57059 --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplRevProxyTlsIT.java @@ -0,0 +1,69 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import java.net.URL; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.testcontainers.containers.BindMode.READ_ONLY; + +@Tag("client") +@Tag("flare") +@SpringBootTest(classes = FlareWebserviceClientSpringConfig.class) +@Testcontainers +public class FlareWebserviceClientImplRevProxyTlsIT extends FlareWebserviceClientImplBaseIT { + + @Autowired + protected FlareWebserviceClient flareClient; + + private static URL nginxConf = getResource("nginx.conf"); + private static URL nginxTestProxyConfTemplate = getResource("reverse_proxy_tls.conf.template"); + private static URL indexFile = getResource("index.html"); + private static URL serverCertChain = getResource("../certs/server_cert_chain.pem"); + private static URL serverCertKey = getResource("../certs/server_cert_key.pem"); + private static URL trustStoreFile = getResource("../certs/ca.p12"); + + @Container + public static GenericContainer proxy = new GenericContainer<>( + DockerImageName.parse("nginx:1.25.1")) + .withExposedPorts(8443) + .withFileSystemBind(nginxConf.getPath(), "/etc/nginx/nginx.conf", READ_ONLY) + .withFileSystemBind(indexFile.getPath(), "/usr/share/nginx/html/index.html", READ_ONLY) + .withFileSystemBind(nginxTestProxyConfTemplate.getPath(), + "/etc/nginx/templates/default.conf.template", + READ_ONLY) + .withFileSystemBind(serverCertChain.getPath(), "/etc/nginx/certs/server_cert.pem", READ_ONLY) + .withFileSystemBind(serverCertKey.getPath(), "/etc/nginx/certs/server_cert_key.pem", READ_ONLY) + .withNetwork(DEFAULT_CONTAINER_NETWORK) + .dependsOn(flare); + + @DynamicPropertySource + static void dynamicProperties(DynamicPropertyRegistry registry) { + var proxyHost = proxy.getHost(); + var proxyPort = proxy.getFirstMappedPort(); + + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.flare.base_url", + () -> String.format("https://%s:%s/", proxyHost, proxyPort)); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_path", + () -> trustStoreFile.getPath()); + registry.add("de.medizininformatik_initiative.feasibility_dsf_process.client.store.trust_store_password", + () -> "changeit"); + } + + @Test + void sendQuery() throws Exception { + var rawStructuredQuery = this.getClass().getResource("valid-structured-query.json").openStream().readAllBytes(); + var feasibility = assertDoesNotThrow(() -> flareClient.requestFeasibility(rawStructuredQuery)); + assertEquals(0, feasibility); + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplTest.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplTest.java index 7d44c62..fc63a1f 100644 --- a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplTest.java +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/FlareWebserviceClientImplTest.java @@ -1,33 +1,33 @@ package de.medizininformatik_initiative.feasibility_dsf_process.client.flare; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.BasicResponseHandler; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.net.http.HttpClient; -import java.net.http.HttpHeaders; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.Optional; - -import javax.net.ssl.SSLSession; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class FlareWebserviceClientImplTest { - private HttpClient httpClient; + private org.apache.http.client.HttpClient httpClient; private FlareWebserviceClient flareWebserviceClient; + @Captor ArgumentCaptor postCaptor; @BeforeEach public void setUp() throws URISyntaxException { @@ -37,7 +37,7 @@ public void setUp() throws URISyntaxException { @Test public void testRequestFeasibility_FailsOnCommunicationError() throws IOException, InterruptedException { - when(httpClient.send(any(HttpRequest.class), eq(HttpResponse.BodyHandlers.ofString()))) + when(httpClient.execute(any(HttpPost.class), any(BasicResponseHandler.class))) .thenThrow(IOException.class); var structuredQuery = "foo".getBytes(); @@ -46,8 +46,8 @@ public void testRequestFeasibility_FailsOnCommunicationError() throws IOExceptio @Test public void testRequestFeasibility_FailsOnWrongBodyContent() throws IOException, InterruptedException { - var response = new StringHttpResponse("{\"invalid\": true}"); - when(httpClient.send(any(HttpRequest.class), eq(HttpResponse.BodyHandlers.ofString()))) + var response = "{\"invalid\": true}"; + when(httpClient.execute(any(HttpPost.class), any(BasicResponseHandler.class))) .thenReturn(response); var structuredQuery = "foo".getBytes(); @@ -56,8 +56,8 @@ public void testRequestFeasibility_FailsOnWrongBodyContent() throws IOException, @Test public void testRequestFeasibility() throws IOException, InterruptedException { - var response = new StringHttpResponse("15"); - when(httpClient.send(any(HttpRequest.class), eq(HttpResponse.BodyHandlers.ofString()))) + var response = "15"; + when(httpClient.execute(any(HttpPost.class), any(BasicResponseHandler.class))) .thenReturn(response); var structuredQuery = "foo".getBytes(); @@ -66,53 +66,50 @@ public void testRequestFeasibility() throws IOException, InterruptedException { assertEquals(15, feasibility); } + @Test + public void testBaseUrlPathIsKept() throws Exception { + String path = "/foo/bar/"; + flareWebserviceClient = new FlareWebserviceClientImpl(httpClient, URI.create("http://foo.bar:1234" + path)); + var structuredQuery = "foo".getBytes(); - private class StringHttpResponse implements HttpResponse { + when(httpClient.execute(postCaptor.capture(), any(BasicResponseHandler.class))) + .thenReturn("99"); - private final String body; + flareWebserviceClient.requestFeasibility(structuredQuery); - public StringHttpResponse(String body) { - this.body = body; - } + assertEquals(path + "query/execute", postCaptor.getValue().getURI().getPath()); + } - @Override - public int statusCode() { - return 200; - } + @Test + void testNullBaseUrlDoesNotFailAtInit() throws Exception { + var config = new FlareWebserviceClientSpringConfig(); - @Override - public HttpRequest request() { - return null; - } + assertDoesNotThrow(() -> config.flareWebserviceClient(httpClient)); + } - @Override - public Optional> previousResponse() { - return Optional.empty(); - } + @Test + void testNullBaseUrlFails() throws Exception { + var config = new FlareWebserviceClientSpringConfig(); + var structuredQuery = "foo".getBytes(); + flareWebserviceClient = config.flareWebserviceClient(httpClient); - @Override - public HttpHeaders headers() { - return null; - } + var e = assertThrows(IllegalArgumentException.class, + () -> flareWebserviceClient.requestFeasibility(structuredQuery)); - @Override - public String body() { - return body; - } + assertEquals("FLARE_BASE_URL is not set.", e.getMessage()); + } - @Override - public Optional sslSession() { - return Optional.empty(); - } + @Test + void testIllegalBaseUrlFails() throws Exception { + var config = new FlareWebserviceClientSpringConfig(); + var invalidUrl = "{ßöäü;"; + var structuredQuery = "foo".getBytes(); + ReflectionTestUtils.setField(config, "flareBaseUrl", invalidUrl); + flareWebserviceClient = config.flareWebserviceClient(httpClient); - @Override - public URI uri() { - return null; - } + var e = assertThrows(IllegalArgumentException.class, + () -> flareWebserviceClient.requestFeasibility(structuredQuery)); - @Override - public HttpClient.Version version() { - return null; - } + assertEquals("Could not parse FLARE_BASE_URL '" + invalidUrl + "' as URI.", e.getMessage()); } } diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/StoreClientIT.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/StoreClientIT.java index d548b0d..bca6a64 100644 --- a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/StoreClientIT.java +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/client/store/StoreClientIT.java @@ -4,6 +4,7 @@ import de.medizininformatik_initiative.feasibility_dsf_process.client.store.StoreClientConfiguration.ConnectionConfiguration; import de.medizininformatik_initiative.feasibility_dsf_process.client.store.StoreClientConfiguration.ProxyConfiguration; import de.medizininformatik_initiative.feasibility_dsf_process.client.store.StoreClientConfiguration.StoreAuthenticationConfiguration; +import de.medizininformatik_initiative.feasibility_dsf_process.spring.config.DefaultTrustStoreUtils; import lombok.NonNull; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -52,7 +53,7 @@ public class StoreClientIT { private static final Network DEFAULT_CONTAINER_NETWORK = Network.newNetwork(); @Container - public GenericContainer fhirServer = new GenericContainer<>(DockerImageName.parse("samply/blaze:0.22.2")) + public GenericContainer fhirServer = new GenericContainer<>(DockerImageName.parse("samply/blaze:0.23.0")) .withExposedPorts(8080) .withNetwork(DEFAULT_CONTAINER_NETWORK) .withNetworkAliases("fhir-server") @@ -189,16 +190,16 @@ public void testRequestToReverseProxyWithSelfSignedCertificate() throws IOExcept @Test public void testRequestToReverseProxyWithClientCert() throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException { - // Make sure to run `create_certs_for_store_client_tests.sh` from the `scripts` directory first. This will be + // Make sure to run `create_certs_for_client_tests.sh` from the `scripts` directory first. This will be // automatically triggered by maven when trying to run integration tests as it is coupled with a phase called // `pre-integration-test`. var nginxConf = getResource("nginx.conf"); var nginxTestProxyConfTemplate = getResource("reverse_proxy_client_cert.conf.template"); var staticFhirMetadata = getResource("fhir_metadata.json"); var indexFile = getResource("index.html"); - var trustedCerts = getResource("./certs/ca.pem"); - var serverCertChain = getResource("./certs/server_cert_chain.pem"); - var serverCertKey = getResource("./certs/server_cert_key.pem"); + var trustedCerts = getResource("../certs/ca.pem"); + var serverCertChain = getResource("../certs/server_cert_chain.pem"); + var serverCertKey = getResource("../certs/server_cert_key.pem"); NginxContainer nginx = new NginxContainer<>("nginx:1.25.1") .withExposedPorts(80) @@ -212,12 +213,12 @@ public void testRequestToReverseProxyWithClientCert() throws KeyStoreException, .withNetwork(DEFAULT_CONTAINER_NETWORK); nginx.start(); - var serverTrustStoreStream = getResourceAsStream("./certs/ca.p12"); + var serverTrustStoreStream = getResourceAsStream("../certs/ca.p12"); var trustStore = KeyStore.getInstance("PKCS12"); trustStore.load(serverTrustStoreStream, "changeit".toCharArray()); serverTrustStoreStream.close(); - var clientCertStream = getResourceAsStream("./certs/client_key_store.p12"); + var clientCertStream = getResourceAsStream("../certs/client_key_store.p12"); var keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(clientCertStream, "changeit".toCharArray()); clientCertStream.close(); diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/DownloadMeasureReportTest.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/DownloadMeasureReportTest.java index 66eae5b..dd78618 100644 --- a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/DownloadMeasureReportTest.java +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/DownloadMeasureReportTest.java @@ -111,6 +111,6 @@ public void testDoExecute() throws Exception { service.execute(execution); - verify(execution).setVariable(VARIABLE_MEASURE_REPORT, measureReport); + verify(execution).setVariableLocal(VARIABLE_MEASURE_REPORT, measureReport); } } diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateCqlMeasureTest.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateCqlMeasureTest.java index 97bbe9f..066e85c 100644 --- a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateCqlMeasureTest.java +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateCqlMeasureTest.java @@ -1,48 +1,24 @@ package de.medizininformatik_initiative.feasibility_dsf_process.service; import ca.uhn.fhir.rest.client.api.IGenericClient; -import ca.uhn.fhir.rest.gclient.IOperation; -import dev.dsf.bpe.v1.ProcessPluginApi; -import dev.dsf.bpe.v1.service.FhirWebserviceClientProvider; -import dev.dsf.bpe.v1.service.TaskHelper; import dev.dsf.bpe.v1.variables.Variables; -import dev.dsf.fhir.authorization.read.ReadAccessHelper; -import dev.dsf.fhir.client.FhirWebserviceClient; -import dev.dsf.fhir.client.PreferReturnMinimalWithRetry; -import org.camunda.bpm.engine.ProcessEngine; -import org.camunda.bpm.engine.RuntimeService; import org.camunda.bpm.engine.delegate.DelegateExecution; import org.hl7.fhir.r4.model.CodeableConcept; -import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.DateType; import org.hl7.fhir.r4.model.MeasureReport; -import org.hl7.fhir.r4.model.Quantity; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.Task; -import org.hl7.fhir.r4.model.Task.TaskStatus; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Answers; import org.mockito.ArgumentMatchers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import java.time.Instant; -import java.util.Date; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; - import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_MEASURE_ID; import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_MEASURE_REPORT; -import static org.hl7.fhir.r4.model.Task.TaskStatus.FAILED; -import static org.junit.Assert.assertSame; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -50,143 +26,101 @@ @ExtendWith(MockitoExtension.class) public class EvaluateCqlMeasureTest { - private static final String CODE_SYSTEM_MEASURE_POPULATION = "http://terminology.hl7.org/CodeSystem/measure-population"; - private static final String CODE_INITIAL_POPULATION = "initial-population"; + private static final String MEASURE_POPULATION = "http://terminology.hl7.org/CodeSystem/measure-population"; + private static final String INITIAL_POPULATION = "initial-population"; private static final String MEASURE_ID = "id-145128"; - @Mock(answer = Answers.RETURNS_DEEP_STUBS) private IOperation storeOperation; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private IGenericClient storeClient; - @Mock private IGenericClient storeClient; - @Mock private TaskHelper taskHelper; - @Mock private DelegateExecution execution; - @Mock private FhirWebserviceClientProvider clientProvider; - @Mock private FhirWebserviceClient webserviceClient; - @Mock private ReadAccessHelper readAccessHelper; - @Mock private PreferReturnMinimalWithRetry retry; - @Mock private ProcessEngine processEngine; - @Mock private RuntimeService runtimeService; - @Mock private ProcessPluginApi api; - @Mock private Variables variables; + @Mock + private DelegateExecution execution; - @InjectMocks private EvaluateCqlMeasure service; + @Mock + private Variables variables; - private final String instanceId = "instanceId-020112";; - private Task task; - private Task.TaskOutputComponent taskOutputComponent; + @InjectMocks + private EvaluateCqlMeasure service; @BeforeEach public void setUp() { - task = new Task(); - task.setStatus(TaskStatus.INPROGRESS); - taskOutputComponent = new Task.TaskOutputComponent(); - - when(api.getVariables(execution)).thenReturn(variables); + when(variables.getString(VARIABLE_MEASURE_ID)).thenReturn(MEASURE_ID); } - public static Stream measureReports() { - return Stream.of( - Arguments.of("MissingMeasureReportGroup", Optional.of("Missing MeasureReport group"), new MeasureReport() - .setDate(Date.from(Instant.parse("2007-12-03T10:15:30.00Z")))), - Arguments.of("MissingMeasureReportPopulation", Optional.of("Missing MeasureReport population"), - new MeasureReport() - .setDate(Date.from(Instant.parse("2007-12-03T10:15:30.00Z"))) - .setGroup(List.of( - new MeasureReport.MeasureReportGroupComponent() - .setCode(new CodeableConcept()) - .setMeasureScore(new Quantity(1)) - .setStratifier(List.of(new MeasureReport.MeasureReportGroupStratifierComponent())) - .setPopulation(List.of(new MeasureReport.MeasureReportGroupPopulationComponent()))))), - Arguments.of("MissingMeasureReportPopulationCode", Optional.of("Missing MeasureReport population code"), new MeasureReport() - .setDate(Date.from(Instant.parse("2007-12-03T10:15:30.00Z"))) - .setGroup(List.of( - new MeasureReport.MeasureReportGroupComponent() - .setCode(new CodeableConcept()) - .setMeasureScore(new Quantity(1)) - .setStratifier(List.of(new MeasureReport.MeasureReportGroupStratifierComponent())) - .setPopulation(List.of( - new MeasureReport.MeasureReportGroupPopulationComponent() - .setCode(new CodeableConcept()) - .setCount(0) - .setSubjectResults(new Reference("http://localhost/Patient/123"))))))), - Arguments.of("MeasureReportWrongCoding", Optional.of("Missing MeasureReport initial-population code"), new MeasureReport() - .setDate(Date.from(Instant.parse("2007-12-03T10:15:30.00Z"))) - .setGroup(List.of( - new MeasureReport.MeasureReportGroupComponent() - .setCode(new CodeableConcept()) - .setMeasureScore(new Quantity(1)) - .setStratifier(List.of(new MeasureReport.MeasureReportGroupStratifierComponent())) - .setPopulation(List.of( - new MeasureReport.MeasureReportGroupPopulationComponent() - .setCode(new CodeableConcept().setCoding(List.of( - new Coding(CODE_SYSTEM_MEASURE_POPULATION, "something", "foo") - ))) - .setCount(0) - .setSubjectResults(new Reference("http://localhost/Patient/123"))))))), - Arguments.of("MissingMeasureReportPopulationCount", Optional.of("Missing MeasureReport population count"), new MeasureReport() - .setDate(Date.from(Instant.parse("2007-12-03T10:15:30.00Z"))) - .setGroup(List.of( - new MeasureReport.MeasureReportGroupComponent() - .setCode(new CodeableConcept()) - .setMeasureScore(new Quantity(1)) - .setStratifier(List.of(new MeasureReport.MeasureReportGroupStratifierComponent())) - .setPopulation(List.of( - new MeasureReport.MeasureReportGroupPopulationComponent() - .setCode(new CodeableConcept().setCoding(List.of( - new Coding(CODE_SYSTEM_MEASURE_POPULATION, CODE_INITIAL_POPULATION, "foo") - ))) - .setSubjectResults(new Reference("http://localhost/Patient/123"))))))), - Arguments.of("ValidMeasureReport", Optional.empty(), new MeasureReport() - .setDate(Date.from(Instant.parse("2007-12-03T10:15:30.00Z"))) - .setGroup(List.of( - new MeasureReport.MeasureReportGroupComponent() - .setCode(new CodeableConcept()) - .setMeasureScore(new Quantity(1)) - .setStratifier(List.of(new MeasureReport.MeasureReportGroupStratifierComponent())) - .setPopulation(List.of( - new MeasureReport.MeasureReportGroupPopulationComponent() - .setCode(new CodeableConcept().setCoding(List.of( - new Coding(CODE_SYSTEM_MEASURE_POPULATION, CODE_INITIAL_POPULATION, "foo") - ))) - .setCount(0) - .setSubjectResults(new Reference("http://localhost/Patient/123"))))))) - ); + @Test + public void testDoExecute() { + var report = new MeasureReport(); + var code = new CodeableConcept(); + var coding = code.getCodingFirstRep(); + coding.setSystem(MEASURE_POPULATION); + coding.setCode(INITIAL_POPULATION); + report.getGroupFirstRep().getPopulationFirstRep().setCode(code).setCount(0); + when(evaluateMeasure()).thenReturn(report); + + service.doExecute(execution, variables); + + verify(variables).setResource(VARIABLE_MEASURE_REPORT, report); } - @ParameterizedTest - @MethodSource("measureReports") - public void testDoExecute(String name, Optional expectedErrMsg, MeasureReport measureReport) throws Exception { - when(variables.getString(VARIABLE_MEASURE_ID)) - .thenReturn(MEASURE_ID); - when(storeClient.operation()) - .thenReturn(storeOperation); - when(storeOperation.onInstance("Measure/" + MEASURE_ID) + private MeasureReport evaluateMeasure() { + return storeClient.operation().onInstance("Measure/" + MEASURE_ID) .named("evaluate-measure") .withParameter(ArgumentMatchers.>any(), eq("periodStart"), any(DateType.class)) .andParameter(eq("periodEnd"), any(DateType.class)) .useHttpGet() .returnResourceType(MeasureReport.class) - .execute()) - .thenReturn(measureReport); - - if (expectedErrMsg.isPresent()) { - when(api.getFhirWebserviceClientProvider()).thenReturn(clientProvider); - when(api.getTaskHelper()).thenReturn(taskHelper); - when(variables.getTasks()).thenReturn(List.of(task)); - when(clientProvider.getLocalWebserviceClient()).thenReturn(webserviceClient); - when(webserviceClient.withMinimalReturn()).thenReturn(retry); - when(execution.getProcessEngine()).thenReturn(processEngine); - when(processEngine.getRuntimeService()).thenReturn(runtimeService); - when(execution.getProcessInstanceId()).thenReturn(instanceId); - - service.execute(execution); - - assertSame(FAILED, task.getStatus()); - verify(retry).update(task); - verify(runtimeService).deleteProcessInstance(eq(instanceId), contains(expectedErrMsg.get())); - } else { - service.execute(execution); - - verify(variables).setResource(VARIABLE_MEASURE_REPORT, measureReport); - } + .execute(); + } + + @Test + void testFailsOnEmptyMeasureReport() { + when(evaluateMeasure()).thenReturn(new MeasureReport()); + + assertThatThrownBy(() -> service.doExecute(execution, variables)) + .hasMessage("Missing MeasureReport group"); + } + + @Test + void testFailsOnMissingPopulation() { + MeasureReport report = new MeasureReport(); + report.getGroupFirstRep().setCode(new CodeableConcept().setText("foo")); + when(evaluateMeasure()).thenReturn(report); + + assertThatThrownBy(() -> service.doExecute(execution, variables)) + .hasMessage("Missing MeasureReport population"); + } + + @Test + void testFailsOnMissingPopulationCode() { + MeasureReport report = new MeasureReport(); + report.getGroupFirstRep().getPopulationFirstRep().setCount(0); + when(evaluateMeasure()).thenReturn(report); + + assertThatThrownBy(() -> service.doExecute(execution, variables)) + .hasMessage("Missing MeasureReport population code"); + } + + @Test + void testFailsOnWrongPopulationCode() { + MeasureReport report = new MeasureReport(); + report.getGroupFirstRep().getPopulationFirstRep().setCode(new CodeableConcept().setText("foo")); + when(evaluateMeasure()).thenReturn(report); + + assertThatThrownBy(() -> service.doExecute(execution, variables)) + .hasMessage("Missing MeasureReport initial-population code"); + } + + @Test + void testFailsOnMissingPopulationCount() { + var report = new MeasureReport(); + var code = new CodeableConcept(); + var coding = code.getCodingFirstRep(); + coding.setSystem(MEASURE_POPULATION); + coding.setCode(INITIAL_POPULATION); + report.getGroupFirstRep().getPopulationFirstRep().setCode(code); + when(evaluateMeasure()).thenReturn(report); + + assertThatThrownBy(() -> service.doExecute(execution, variables)) + .hasMessage("Missing MeasureReport population count"); } } diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/FeasibilityResourceCleanerTest.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/FeasibilityResourceCleanerTest.java new file mode 100644 index 0000000..3b85ac2 --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/FeasibilityResourceCleanerTest.java @@ -0,0 +1,58 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.service; + +import org.hl7.fhir.r4.model.Attachment; +import org.hl7.fhir.r4.model.Library; +import org.hl7.fhir.r4.model.Measure; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static de.medizininformatik_initiative.feasibility_dsf_process.service.TestUtil.CQL_CONTENT_TYPE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.condition.MappedCondition.mappedCondition; +import static org.assertj.core.data.Index.atIndex; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class FeasibilityResourceCleanerTest { + + private FeasibilityResourceCleaner cleaner; + + @BeforeEach + void setUp() { + cleaner = new FeasibilityResourceCleaner(); + } + + @Test + void cleanLibrary() { + var library = new Library(); + library.getMeta().setSource("source-172322"); + library.setContent(List.of(new Attachment().setContentType("application/json"), + new Attachment().setContentType("text/cql"))); + + cleaner.cleanLibrary(library); + + assertTrue(library.getMeta().isEmpty()); + assertThat(library.getContent()) + .hasSize(1) + .has(mappedCondition(Attachment::getContentType, CQL_CONTENT_TYPE), atIndex(0)); + } + + @Test + public void cleanLibrary_FailsIfCqlContentIsMissing() { + var library = new Library(); + + assertThatThrownBy(() -> cleaner.cleanLibrary(library)) + .hasMessage("Library content of type `text/cql` is missing."); + } + + @Test + void cleanMeasure() { + var measure = new Measure(); + + cleaner.cleanMeasure(measure); + + assertTrue(measure.getMeta().isEmpty()); + } +} diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreFeasibilityResourcesTest.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreFeasibilityResourcesTest.java index c45f5d8..9fda4ae 100644 --- a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreFeasibilityResourcesTest.java +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreFeasibilityResourcesTest.java @@ -2,11 +2,8 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.api.IGenericClient; -import dev.dsf.bpe.v1.ProcessPluginApi; import dev.dsf.bpe.v1.variables.Variables; -import dev.dsf.fhir.authorization.read.ReadAccessHelperImpl; import org.camunda.bpm.engine.delegate.DelegateExecution; -import org.hl7.fhir.r4.model.Attachment; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Library; import org.hl7.fhir.r4.model.Measure; @@ -14,23 +11,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; -import java.util.List; -import java.util.UUID; - -import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_LIBRARY; -import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_MEASURE; -import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_MEASURE_ID; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThrows; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -38,113 +23,42 @@ @ExtendWith(MockitoExtension.class) public class StoreFeasibilityResourcesTest { - public static final String ID = "foo"; - - @Spy private ReadAccessHelperImpl readAccessHelper; - - @Captor ArgumentCaptor resourceCaptor; + public static final String LIBRARY_ID = "library-id-173603"; + public static final String MEASURE_ID = "measure-id-173603"; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private IGenericClient storeClient; - @Mock private ProcessPluginApi api; - @Mock private DelegateExecution execution; - @Mock private Variables variables; - - @InjectMocks private StoreFeasibilityResources service; - - @Test - public void testDoExecute() { - Measure measure = new Measure(); - Library library = new Library(); - library.setContent(List.of(new Attachment() - .setContentType("text/cql") - .setData("foo".getBytes()))); - when(variables.getResource(VARIABLE_MEASURE)).thenReturn(measure); - when(variables.getResource(VARIABLE_LIBRARY)).thenReturn(library); - - var libraryServerId = UUID.randomUUID(); - var libraryMethodOutcome = new MethodOutcome(new IdType(libraryServerId.toString())); - var measureMethodOutcome = new MethodOutcome(new IdType(ID)); + @Mock + private FeasibilityResourceCleaner cleaner; - when(storeClient.create().resource(any(Resource.class)).execute()) - .thenReturn(libraryMethodOutcome, measureMethodOutcome); + @Mock + private DelegateExecution execution; - service.doExecute(execution, variables); + @Mock + private Variables variables; - verify(variables).setString(VARIABLE_MEASURE_ID, ID); - } + @InjectMocks + private StoreFeasibilityResources service; @Test - public void testDoExecute_ReadAccessTagsGetStripped() { + public void testDoExecute() { var measure = new Measure(); - measure.getMeta().addTag("foo", "bar", "1234"); - measure = readAccessHelper.addLocal(measure); - var library = new Library(); - library = readAccessHelper.addAll(library); - library.setContent(List.of(new Attachment() - .setContentType("text/cql") - .setData("foo".getBytes()))); - when(variables.getResource(VARIABLE_MEASURE)).thenReturn(measure); - when(variables.getResource(VARIABLE_LIBRARY)).thenReturn(library); - - var libraryServerId = UUID.randomUUID(); - var libraryMethodOutcome = new MethodOutcome(new IdType(libraryServerId.toString())); - var measureMethodOutcome = new MethodOutcome(new IdType(ID)); - - when(storeClient.create().resource(resourceCaptor.capture()).execute()) - .thenReturn(libraryMethodOutcome, measureMethodOutcome); - - service.doExecute(execution, variables); - - var capturedResources = resourceCaptor.getAllValues(); - var capturedLibrary = (Library) capturedResources.get(0); - var capturedMeasure = (Measure) capturedResources.get(1); - assertFalse(readAccessHelper.hasLocal(capturedLibrary)); - assertFalse(readAccessHelper.hasAll(capturedLibrary)); - assertFalse(readAccessHelper.hasLocal(capturedMeasure)); - assertFalse(readAccessHelper.hasAll(capturedMeasure)); - assertNotNull(capturedMeasure.getMeta().getTag("foo", "bar")); - } - - @Test - public void testDoExecute_OnlyCqlContentGetsStored() { - Measure measure = new Measure(); - Library library = new Library(); - - var libraryAttachments = List.of(new Attachment() - .setContentType("application/json") - .setData("foo".getBytes()), - new Attachment() - .setContentType("text/cql") - .setData("bar".getBytes())); - library.setContent(libraryAttachments); + library.getContentFirstRep().setContentType("text/cql"); when(variables.getResource(VARIABLE_MEASURE)).thenReturn(measure); when(variables.getResource(VARIABLE_LIBRARY)).thenReturn(library); - var libraryServerId = UUID.randomUUID(); - var libraryMethodOutcome = new MethodOutcome(new IdType(libraryServerId.toString())); - - var measureServerId = UUID.randomUUID(); - var measureMethodOutcome = new MethodOutcome(new IdType(measureServerId.toString())); + var libraryMethodOutcome = new MethodOutcome(new IdType(LIBRARY_ID)); + var measureMethodOutcome = new MethodOutcome(new IdType(MEASURE_ID)); - when(storeClient.create().resource(resourceCaptor.capture()).execute()) + when(storeClient.create().resource(any(Resource.class)).execute()) .thenReturn(libraryMethodOutcome, measureMethodOutcome); service.doExecute(execution, variables); - var capturedLibrary = (Library) resourceCaptor.getAllValues().get(0); - assertEquals("text/cql", capturedLibrary.getContent().get(0).getContentType()); - } - - @Test - public void testDoExecute_FailsIfCqlContentIsMissing() { - Measure measure = new Measure(); - Library library = new Library(); - when(variables.getResource(VARIABLE_MEASURE)).thenReturn(measure); - when(variables.getResource(VARIABLE_LIBRARY)).thenReturn(library); - - assertThrows(IllegalStateException.class, () -> service.doExecute(execution, variables)); + verify(cleaner).cleanLibrary(library); + verify(cleaner).cleanMeasure(measure); + verify(variables).setString(VARIABLE_MEASURE_ID, MEASURE_ID); } } diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreLiveResultTest.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreLiveResultTest.java index 3a9ae1d..19d6530 100644 --- a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreLiveResultTest.java +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/StoreLiveResultTest.java @@ -77,8 +77,7 @@ public void testDoExecute_MeasureReportReferenceIsAddedToTask() throws Exception IdType measureReportId = new IdType("e26daf2d-2d55-4f23-a7c8-4b994e3a319e"); report.setIdElement(measureReportId); TaskOutputComponent taskOutputComponent = new TaskOutputComponent(); - when(variables.getResource(VARIABLE_MEASURE_REPORT)) - .thenReturn(measureReport); + when(execution.getVariableLocal(VARIABLE_MEASURE_REPORT)).thenReturn(measureReport); when(client.create(any(MeasureReport.class))).thenReturn(report); when(taskHelper.createOutput(refCaptor.capture(), eq(CODESYSTEM_FEASIBILITY), eq(CODESYSTEM_FEASIBILITY_VALUE_MEASURE_REPORT_REFERENCE))) .thenReturn(taskOutputComponent); @@ -91,8 +90,7 @@ public void testDoExecute_MeasureReportReferenceIsAddedToTask() throws Exception @Test public void testDoExecute_MeasureReportIsStored() throws Exception { - when(variables.getResource(VARIABLE_MEASURE_REPORT)) - .thenReturn(measureReport); + when(execution.getVariableLocal(VARIABLE_MEASURE_REPORT)).thenReturn(measureReport); when(taskHelper.createOutput(refCaptor.capture(), eq(CODESYSTEM_FEASIBILITY), eq(CODESYSTEM_FEASIBILITY_VALUE_MEASURE_REPORT_REFERENCE))) .thenReturn(new TaskOutputComponent()); when(client.create(measureReportCaptor.capture())).thenReturn(measureReport); diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/TestUtil.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/TestUtil.java new file mode 100644 index 0000000..78b7912 --- /dev/null +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/TestUtil.java @@ -0,0 +1,8 @@ +package de.medizininformatik_initiative.feasibility_dsf_process.service; + +import org.assertj.core.api.Condition; + +public interface TestUtil { + + Condition CQL_CONTENT_TYPE = new Condition<>("text/cql"::equals, "content-type"); +} diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/forward_proxy.conf b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/forward_proxy.conf new file mode 100644 index 0000000..6839ffc --- /dev/null +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/forward_proxy.conf @@ -0,0 +1,2 @@ +http_port 8080 +http_access allow all diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/forward_proxy.conf.template b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/forward_proxy.conf.template new file mode 100644 index 0000000..6c9ca92 --- /dev/null +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/forward_proxy.conf.template @@ -0,0 +1,10 @@ +server { + listen 8080; + listen [::]:8080; + + location / { + # Using docker default resolver - this is intended to be used within an integration test running docker anyway. + resolver 127.0.0.11; + proxy_pass http://$http_host$uri$is_args$args; + } +} diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/forward_proxy.htpasswd b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/forward_proxy.htpasswd new file mode 100644 index 0000000..ba294cf --- /dev/null +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/forward_proxy.htpasswd @@ -0,0 +1 @@ +test:$apr1$4ihrtZR3$7fCQXazNsjIaSbt6CuWi/1 diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/forward_proxy_basic_auth.conf b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/forward_proxy_basic_auth.conf new file mode 100644 index 0000000..42e40e4 --- /dev/null +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/forward_proxy_basic_auth.conf @@ -0,0 +1,9 @@ +http_port 8080 +auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd +auth_param basic children 1 startup=1 +auth_param basic casesensitive off +auth_param basic utf8 on +auth_param basic realm Test Forward Proxy Basic Authentication +acl auth_users proxy_auth REQUIRED +http_access allow auth_users +http_access deny all diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/index.html b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/index.html new file mode 100644 index 0000000..07bee1c --- /dev/null +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/index.html @@ -0,0 +1,8 @@ + + + Test-Proxy + + + Test-Proxy + + \ No newline at end of file diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/nginx.conf b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/nginx.conf new file mode 100644 index 0000000..b2e5fe2 --- /dev/null +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/nginx.conf @@ -0,0 +1,25 @@ +user nginx; +worker_processes 1; + +error_log /var/log/nginx/error.log debug; +pid /var/run/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include /etc/nginx/mime.types; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + access_log /var/log/nginx/access.log main; + + sendfile on; + keepalive_timeout 65; + + include /etc/nginx/conf.d/*.conf; +} diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy.htpasswd b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy.htpasswd new file mode 100644 index 0000000..0e38cbd --- /dev/null +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy.htpasswd @@ -0,0 +1 @@ +test:$2y$10$i6wzZb/8uwpaex4fp66cKeqprGQfzsJcac4EKHuwT98d58K2E3Q5a diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_basic_auth.conf.template b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_basic_auth.conf.template new file mode 100644 index 0000000..16af4da --- /dev/null +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_basic_auth.conf.template @@ -0,0 +1,17 @@ +server { + listen 8080; + listen [::]:8080; + + location / { + root /usr/share/nginx/html; + index index.html; + } + + location /query/execute { + auth_basic "Test Area"; + auth_basic_user_file /etc/auth/.htpasswd; + + proxy_pass http://flare:8080; + proxy_read_timeout 43200s; + } +} diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_bearer_token_auth.conf.template b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_bearer_token_auth.conf.template new file mode 100644 index 0000000..1fb91c8 --- /dev/null +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_bearer_token_auth.conf.template @@ -0,0 +1,19 @@ +server { + listen 8080; + listen [::]:8080; + + location / { + root /usr/share/nginx/html; + index index.html; + } + + location /query/execute { + if ($http_authorization != "Bearer 1234") { + add_header WWW-Authenticate Bearer always; + return 401; + } + + proxy_pass http://flare:8080; + proxy_read_timeout 43200s; + } +} diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_tls.conf.template b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_tls.conf.template new file mode 100644 index 0000000..be16007 --- /dev/null +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_tls.conf.template @@ -0,0 +1,17 @@ +server { + listen 8443 ssl; + listen [::]:8443 ssl; + + ssl_certificate /etc/nginx/certs/server_cert.pem; + ssl_certificate_key /etc/nginx/certs/server_cert_key.pem; + ssl_protocols TLSv1.3; + ssl_prefer_server_ciphers off; + add_header Strict-Transport-Security "max-age=63072000" always; + + location /query/execute { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_http_version 1.1; + proxy_read_timeout 43200s; + proxy_pass http://flare:8080; + } +} diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_tls_basic_auth.conf.template b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_tls_basic_auth.conf.template new file mode 100644 index 0000000..9889594 --- /dev/null +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_tls_basic_auth.conf.template @@ -0,0 +1,20 @@ +server { + listen 8443 ssl; + listen [::]:8443 ssl; + + ssl_certificate /etc/nginx/certs/server_cert.pem; + ssl_certificate_key /etc/nginx/certs/server_cert_key.pem; + ssl_protocols TLSv1.3; + ssl_prefer_server_ciphers off; + add_header Strict-Transport-Security "max-age=63072000" always; + + location /query/execute { + auth_basic "Test Area"; + auth_basic_user_file /etc/auth/.htpasswd; + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_http_version 1.1; + proxy_read_timeout 43200s; + proxy_pass http://flare:8080; + } +} diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_tls_client_cert.conf.template b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_tls_client_cert.conf.template new file mode 100644 index 0000000..d50435b --- /dev/null +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/reverse_proxy_tls_client_cert.conf.template @@ -0,0 +1,23 @@ +server { + listen 8443 ssl; + listen [::]:8443 ssl; + + ssl_certificate /etc/nginx/certs/server_cert.pem; + ssl_certificate_key /etc/nginx/certs/server_cert_key.pem; + ssl_protocols TLSv1.3; + ssl_prefer_server_ciphers off; + add_header Strict-Transport-Security "max-age=63072000" always; + ssl_client_certificate /etc/nginx/certificates/clientCA.pem; + ssl_verify_client on; + ssl_verify_depth 2; + + location /query/execute { + if ($ssl_client_verify != SUCCESS) { + return 403; + } + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_http_version 1.1; + proxy_read_timeout 43200s; + proxy_pass http://flare:8080; + } +} diff --git a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/valid-structured-query.json b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/valid-structured-query.json index 6d31eeb..80bff13 100644 --- a/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/valid-structured-query.json +++ b/feasibility-dsf-process/src/test/resources/de/medizininformatik_initiative/feasibility_dsf_process/client/flare/valid-structured-query.json @@ -1,5 +1,6 @@ { "version": "http://to_be_decided.com/draft-1/schema#", + "display": "", "inclusionCriteria": [ [ { @@ -10,15 +11,21 @@ "display": "Geschlecht" } ], + "context": { + "code": "Patient", + "system": "fdpg.mii.cds", + "version": "1.0.0", + "display": "Patient" + }, "valueFilter": { - "type": "concept", "selectedConcepts": [ { "code": "female", - "system": "http://hl7.org/fhir/administrative-gender", - "display": "Female" + "display": "Female", + "system": "http://hl7.org/fhir/administrative-gender" } - ] + ], + "type": "concept" } } ]