From fd315558ff0d97723bcb97bd87ee8018dcc6d0e5 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Wed, 5 Nov 2025 07:39:08 +0100 Subject: chore: minor update of spring boot and other deps (#182) (cherry picked from commit c7884bcc9c3e3e45b8ec8319210a27f0bf4557a7) --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5053602..a2d2eda 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage plugins { war - id("org.springframework.boot") version "3.5.6" + id("org.springframework.boot") version "3.5.7" id("io.spring.dependency-management") version "1.1.7" kotlin("jvm") version "1.9.25" kotlin("plugin.spring") version "1.9.25" @@ -112,7 +112,7 @@ dependencies { integrationTestImplementation("org.htmlunit:htmlunit") integrationTestImplementation("org.springframework:spring-webflux") // Fix for CVE-2024-25710 - integrationTestImplementation("org.apache.commons:commons-compress:1.26.0") + integrationTestImplementation("org.apache.commons:commons-compress:1.27.1") } tasks.withType { -- cgit v1.2.3 From ae3c9b8a9d9bfb0e2436f4964baaee430e6071d5 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Thu, 6 Nov 2025 16:02:29 +0100 Subject: fix: docs uses APP_PSEUDOMYMIZE_GPAS_PID_DOMAIN (#184) (cherry picked from commit 7f7304ebca5744edd8423e1486adcf77d623a745) --- README.md | 10 +++- .../processor/config/GPasConfigPropertiesTest.kt | 54 ++++++++++++++++++++++ .../pseudonym/GpasPseudonymGeneratorTest.kt | 1 + .../etl/processor/config/AppConfigProperties.kt | 5 +- .../monitoring/ConnectionCheckServiceTest.kt | 1 + 5 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 src/integrationTest/kotlin/dev/dnpm/etl/processor/config/GPasConfigPropertiesTest.kt diff --git a/README.md b/README.md index ae3c9d8..d377797 100644 --- a/README.md +++ b/README.md @@ -111,8 +111,8 @@ Ab Version 2025.1 (Multi-Pseudonym Support) * `APP_PSEUDONYMIZE_GPAS_URI`: URI der gPAS-Instanz REST API (e.g. http://127.0.0.1:9990/ttp-fhir/fhir/gpas) * `APP_PSEUDONYMIZE_GPAS_USERNAME`: gPas Basic-Auth Benutzername * `APP_PSEUDONYMIZE_GPAS_PASSWORD`: gPas Basic-Auth Passwort -* `APP_PSEUDONYMIZE_GPAS_PID_DOMAIN`: gPas Domänenname für Patienten ID -* `APP_PSEUDONYMIZE_GPAS_GENOM_DE_TAN_DOMAIN`: gPas Multi-Pseudonym-Domäne für genomDE Vorgangsnummern ( +* `APP_PSEUDONYMIZE_GPAS_PATIENT_DOMAIN`: gPas Domänenname für Patienten ID (ebenfalls gültig: `APP_PSEUDONYMIZE_GPAS_PID_DOMAIN`) +* `APP_PSEUDONYMIZE_GPAS_GENOM_DE_TAN_DOMAIN`: gPAS Multi-Pseudonym-Domäne für genomDE Vorgangsnummern ( Clinical data node) ### (Externe) Consent-Services @@ -336,6 +336,12 @@ für HTTP nicht gibt. Wird die Umgebungsvariable `APP_KAFKA_INPUT_TOPIC` gesetzt, kann eine Nachricht auch über dieses Kafka-Topic an den ETL-Prozessor übermittelt werden. +Soll eine SSL-gesicherte Verbindung zu Kafka verwendet werden, so sind die SSL-Zertifikate in +der Spring-Konfiguration anzugeben. +Ein Beispiel findet sich in [`application-dev.yml`](src/main/resources/application-dev.yml). +Dies kann auch mit Umgebungsvariablen wie `SPRING_KAFKA_SECURITY_...` und `SPRING_KAFKA_SSL_...` +umgesetzt werden. + ##### Retention Time Generell werden in Apache Kafka alle Records entsprechend der Konfiguration vorgehalten. diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/config/GPasConfigPropertiesTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/config/GPasConfigPropertiesTest.kt new file mode 100644 index 0000000..66e0660 --- /dev/null +++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/config/GPasConfigPropertiesTest.kt @@ -0,0 +1,54 @@ +package dev.dnpm.etl.processor.config + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.context.runner.ApplicationContextRunner +import org.springframework.test.context.ContextConfiguration + + +@SpringBootTest +@ContextConfiguration( + classes = [ + GPasConfigProperties::class, + ] +) +class GPasConfigPropertiesTest { + + @EnableConfigurationProperties(GPasConfigProperties::class) + class TestConfig {} + + @Test + fun shouldUseConfiguredPatientDomainIfPidDomainGiven() { + ApplicationContextRunner() + .withUserConfiguration(TestConfig::class.java) + .withPropertyValues( + "app.pseudonymize.gpas.uri=http://localhost/", + "app.pseudonymize.gpas.pid-domain=test-pid-domain" + ) + .run { context -> + val properties = context.getBean(GPasConfigProperties::class.java) + + assertThat(properties).isNotNull + assertThat(properties.patientDomain).isEqualTo("test-pid-domain") + } + } + + @Test + fun shouldUseConfiguredPatientDomain() { + ApplicationContextRunner() + .withUserConfiguration(TestConfig::class.java) + .withPropertyValues( + "app.pseudonymize.gpas.uri=http://localhost/", + "app.pseudonymize.gpas.patient-domain=test-patient-domain" + ) + .run { context -> + val properties = context.getBean(GPasConfigProperties::class.java) + + assertThat(properties).isNotNull + assertThat(properties.patientDomain).isEqualTo("test-patient-domain") + } + } + +} \ No newline at end of file diff --git a/src/integrationTest/kotlin/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGeneratorTest.kt b/src/integrationTest/kotlin/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGeneratorTest.kt index c2a8ba6..10f2359 100644 --- a/src/integrationTest/kotlin/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGeneratorTest.kt +++ b/src/integrationTest/kotlin/dev/dnpm/etl/processor/pseudonym/GpasPseudonymGeneratorTest.kt @@ -50,6 +50,7 @@ class GpasPseudonymGeneratorTest { val retryTemplate = RetryTemplateBuilder().customPolicy(SimpleRetryPolicy(1)).build() val gPasConfigProperties = GPasConfigProperties( "https://localhost:9990/ttp-fhir/fhir/gpas", + null, "test", "test2", null, null diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt index 395dbd2..e5db63e 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppConfigProperties.kt @@ -21,6 +21,7 @@ package dev.dnpm.etl.processor.config import dev.dnpm.etl.processor.security.Role import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.DeprecatedConfigurationProperty @ConfigurationProperties(AppConfigProperties.NAME) data class AppConfigProperties( @@ -47,7 +48,9 @@ data class PseudonymizeConfigProperties( @ConfigurationProperties(GPasConfigProperties.NAME) data class GPasConfigProperties( val uri: String?, - val patientDomain: String = "etl-processor", + @get:DeprecatedConfigurationProperty(since = "0.12") + val pidDomain: String?, + val patientDomain: String = pidDomain ?: "etl-processor", val genomDeTanDomain: String = "ccdn", val username: String?, val password: String?, diff --git a/src/test/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckServiceTest.kt index 788ca6a..7205714 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckServiceTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/monitoring/ConnectionCheckServiceTest.kt @@ -74,6 +74,7 @@ class ConnectionCheckServiceTest { val gpasTargetProperties = GPasConfigProperties( "http://localhost/gpas", + null, "patientDomain", "genomDeTanDomain", "username", -- cgit v1.2.3 From ee85f58aad5543eceaf88810e94d94d7e2f92bc5 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Fri, 7 Nov 2025 09:58:37 +0100 Subject: fix: use ..hc.core5.net.URIBuilder for URI (#187) (cherry picked from commit 43e86eeac2f3211aa275e1934d17adf29f1cd6e1) --- .../etl/processor/consent/GicsConsentService.java | 27 ++++++++++++++++++---- .../processor/consent/GicsConsentServiceTest.java | 25 ++++++++++++-------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java b/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java index 95e8e8f..cc0491d 100644 --- a/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java +++ b/src/main/java/dev/dnpm/etl/processor/consent/GicsConsentService.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.parser.DataFormatException; import dev.dnpm.etl.processor.config.AppFhirConfig; import dev.dnpm.etl.processor.config.GIcsConfigProperties; import org.apache.commons.lang3.StringUtils; +import org.apache.hc.core5.net.URIBuilder; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent; import org.jetbrains.annotations.NotNull; @@ -18,9 +19,9 @@ import org.springframework.retry.TerminatedRetryException; import org.springframework.retry.support.RetryTemplate; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; +import java.net.URISyntaxException; import java.util.Date; /** @@ -118,9 +119,21 @@ public class GicsConsentService implements IConsentService { return result; } - private URI endpointUri(String endpoint) { - assert this.gIcsConfigProperties.getUri() != null; - return UriComponentsBuilder.fromUriString(this.gIcsConfigProperties.getUri()).path(endpoint).build().toUri(); + private URI endpointUri(String endpoint) throws URISyntaxException { + if (null == this.gIcsConfigProperties.getUri()) { + throw new URISyntaxException("null", "URI must not be null"); + } + var gPasUrl1 = this.gIcsConfigProperties.getUri(); + if (this.gIcsConfigProperties.getUri().lastIndexOf("/") + == this.gIcsConfigProperties.getUri().length() - 1) { + gPasUrl1 = + this.gIcsConfigProperties + .getUri() + .substring(0, this.gIcsConfigProperties.getUri().length() - 1); + } + var urlBuilder = new URIBuilder(new URI(gPasUrl1)).appendPath(endpoint); + + return urlBuilder.build(); } private HttpHeaders headersWithHttpBasicAuth() { @@ -169,7 +182,11 @@ public class GicsConsentService implements IConsentService { terminatedRetryException.getMessage()); log.error(msg); return null; - } + } catch (URISyntaxException e) { + var msg = String.format("Invalid URI for consents status request: '%s", e.getMessage()); + log.error(msg); + return null; + } } @Override diff --git a/src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java b/src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java index 6fa8f08..c5b269d 100644 --- a/src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java +++ b/src/test/java/dev/dnpm/etl/processor/consent/GicsConsentServiceTest.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import dev.dnpm.etl.processor.config.AppConfiguration; import dev.dnpm.etl.processor.config.AppFhirConfig; import dev.dnpm.etl.processor.config.GIcsConfigProperties; +import org.apache.hc.core5.net.URIBuilder; import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; import org.hl7.fhir.r4.model.OperationOutcome.IssueType; @@ -20,9 +21,11 @@ import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.web.client.RestTemplate; +import java.net.URI; import java.time.Instant; import java.util.Date; +import static dev.dnpm.etl.processor.consent.GicsConsentService.IS_CONSENTED_ENDPOINT; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError; @@ -44,7 +47,11 @@ class GicsConsentServiceTest { GicsConsentService gicsConsentService; - @BeforeEach + static URI expectedGicsConsentedEndpoint() throws Exception { + return new URIBuilder(URI.create(GICS_BASE_URI)).appendPath(IS_CONSENTED_ENDPOINT).build(); + } + + @BeforeEach void setUp( @Autowired AppFhirConfig appFhirConfig, @Autowired GIcsConfigProperties gIcsConfigProperties @@ -64,7 +71,7 @@ class GicsConsentServiceTest { } @Test - void shouldReturnTtpBroadConsentStatus() { + void shouldReturnTtpBroadConsentStatus() throws Exception { final Parameters consentedResponse = new Parameters() .addParameter( new ParametersParameterComponent() @@ -75,7 +82,7 @@ class GicsConsentServiceTest { mockRestServiceServer .expect( requestTo( - "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT) + expectedGicsConsentedEndpoint()) ) .andRespond( withSuccess( @@ -89,7 +96,7 @@ class GicsConsentServiceTest { } @Test - void shouldReturnRevokedConsent() { + void shouldReturnRevokedConsent() throws Exception { final Parameters revokedResponse = new Parameters() .addParameter( new ParametersParameterComponent() @@ -100,7 +107,7 @@ class GicsConsentServiceTest { mockRestServiceServer .expect( requestTo( - "http://localhost:8090/ttp-fhir/fhir/gics" + GicsConsentService.IS_CONSENTED_ENDPOINT) + expectedGicsConsentedEndpoint()) ) .andRespond( withSuccess( @@ -114,7 +121,7 @@ class GicsConsentServiceTest { @Test - void shouldReturnInvalidParameterResponse() { + void shouldReturnInvalidParameterResponse() throws Exception { final OperationOutcome responseWithErrorOutcome = new OperationOutcome() .addIssue( new OperationOutcomeIssueComponent() @@ -125,7 +132,7 @@ class GicsConsentServiceTest { mockRestServiceServer .expect( - requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT) + requestTo(expectedGicsConsentedEndpoint()) ) .andRespond( withSuccess( @@ -139,10 +146,10 @@ class GicsConsentServiceTest { } @Test - void shouldReturnRequestError() { + void shouldReturnRequestError() throws Exception { mockRestServiceServer .expect( - requestTo(GICS_BASE_URI + GicsConsentService.IS_CONSENTED_ENDPOINT) + requestTo(expectedGicsConsentedEndpoint()) ) .andRespond( withServerError() -- cgit v1.2.3 From 63a360465ac0aaa4f8aa7d5380f932d84f4074c9 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Fri, 7 Nov 2025 10:53:28 +0100 Subject: build: no latest image for 0.11.x branch --- .github/workflows/deploy.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 65b5e78..e0951e0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -31,5 +31,4 @@ jobs: run: | ./gradlew bootBuildImage docker tag ghcr.io/${{ github.repository }} ghcr.io/${{ github.repository }}:${{ github.ref_name }} - docker push ghcr.io/${{ github.repository }} docker push ghcr.io/${{ github.repository }}:${{ github.ref_name }} \ No newline at end of file -- cgit v1.2.3