From 7df35412c36bb885897125c76130f08c4658c388 Mon Sep 17 00:00:00 2001 From: Boris Stanojevic Date: Wed, 24 Jan 2024 12:51:07 +0100 Subject: [PATCH 1/2] 433: HAPI FHIR REST Audit Sender for BALP Audits --- .gitignore | 1 + .../boot/atna/IpfAtnaAutoConfiguration.java | 9 +- .../atna/IpfAtnaConfigurationProperties.java | 6 + .../atna/IpfAtnaAutoConfigurationTest.java | 1 + .../src/test/resources/application.yml | 2 +- commons/audit/pom.xml | 4 + .../ipf/commons/audit/AuditContext.java | 6 +- .../commons/audit/DefaultAuditContext.java | 6 +- .../ipf/commons/audit/FhirContextHolder.java | 36 ++++ .../protocol/AuditTransmissionChannel.java | 55 +++-- .../AuditTransmissionProtocolProvider.java | 31 +++ .../NettyTLSSyslogSenderProvider.java | 29 +++ .../ReactorNettyTLSSyslogSenderProvider.java | 29 +++ ...rdingAuditMessageTransmissionProvider.java | 29 +++ .../providers/TLSSyslogSenderProvider.java | 29 +++ .../providers/UDPSyslogSenderProvider.java | 29 +++ .../VertxTLSSyslogSenderProvider.java | 30 +++ .../VertxUDPSyslogSenderProvider.java | 30 +++ .../queue/AbstractAuditMessageQueue.java | 6 +- .../queue/AsynchronousAuditMessageQueue.java | 16 +- ...protocol.AuditTransmissionProtocolProvider | 7 + commons/ihe/fhir/core/pom.xml | 5 + .../ihe/fhir/audit/codes/Constants.java | 9 +- .../AbstractFhirRestTLSAuditRecordSender.java | 133 ++++++++++++ .../ApacheFhirRestTLSAuditRecordSender.java | 27 +++ ...estTLSAuditRecordApacheSenderProvider.java | 28 +++ ...tTLSAuditRecordMethanolSenderProvider.java | 28 +++ .../MethanolFhirRestTLSAuditRecordSender.java | 27 +++ ...protocol.AuditTransmissionProtocolProvider | 2 + ...tractFhirRestTLSSenderIntegrationTest.java | 113 +++++++++++ ...hirRestApacheTLSSenderIntegrationTest.java | 13 ++ ...rRestMethanolTLSSenderIntegrationTest.java | 13 ++ .../queue/FhirDelegateMockedMessageQueue.java | 44 ++++ .../fhir/extension/FhirAuditRepository.java | 192 ++++++++++++++++++ .../src/test/resources/security/ca.keystore | Bin 0 -> 1814 bytes .../src/test/resources/security/client.p12 | Bin 0 -> 4243 bytes .../src/test/resources/security/server.p12 | Bin 0 -> 4259 bytes ...bstractFhirAuditSerializationStrategy.java | 125 ++++++------ ...ava => BalpJsonSerializationStrategy.java} | 6 +- ...java => BalpXmlSerializationStrategy.java} | 6 +- .../audit/marshal/FhirAuditJsonEvent.java | 2 +- .../audit/marshal/FhirAuditXmlEvent.java | 2 +- .../PdqmRequestToPdqQueryTranslator.groovy | 5 +- platform-camel/ihe/fhir/r4/mhd/pom.xml | 12 ++ .../fhir/iti105/TestIti105WithBalpAudit.java | 142 +++++++++++++ .../iti65/v421/TestIti65WithBalpAudit.java | 89 ++++++++ .../iti66/v421/TestIti66WithBalpAudit.java | 102 ++++++++++ .../fhir/iti67/TestIti67WithBalpAudit.java | 111 ++++++++++ .../fhir/iti68/TestIti68WithBalpAudit.java | 65 ++++++ .../fhir/pharm5/TestPharm5WithBalpAudit.java | 81 ++++++++ .../test/resources/common-fhir-balp-beans.xml | 86 ++++++++ .../mhd/src/test/resources/iti-105-balp.xml | 28 +++ .../r4/mhd/src/test/resources/iti-65-balp.xml | 29 +++ .../r4/mhd/src/test/resources/iti-67-balp.xml | 28 +++ .../r4/mhd/src/test/resources/iti-68-balp.xml | 28 +++ .../mhd/src/test/resources/pharm-5-balp.xml | 28 +++ .../src/test/resources/security/ca.keystore | Bin 0 -> 1814 bytes .../src/test/resources/security/client.p12 | Bin 0 -> 4243 bytes .../src/test/resources/security/server.p12 | Bin 0 -> 4259 bytes .../src/test/resources/v421/iti-66-balp.xml | 28 +++ .../ihe/fhir/iti78/TestIti78Success.java | 7 + pom.xml | 7 + 62 files changed, 1937 insertions(+), 105 deletions(-) create mode 100644 commons/audit/src/main/java/org/openehealth/ipf/commons/audit/FhirContextHolder.java create mode 100644 commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionProtocolProvider.java create mode 100644 commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/NettyTLSSyslogSenderProvider.java create mode 100644 commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/ReactorNettyTLSSyslogSenderProvider.java create mode 100644 commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/RecordingAuditMessageTransmissionProvider.java create mode 100644 commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/TLSSyslogSenderProvider.java create mode 100644 commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/UDPSyslogSenderProvider.java create mode 100644 commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxTLSSyslogSenderProvider.java create mode 100644 commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxUDPSyslogSenderProvider.java create mode 100644 commons/audit/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider create mode 100644 commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSAuditRecordSender.java create mode 100644 commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/ApacheFhirRestTLSAuditRecordSender.java create mode 100644 commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordApacheSenderProvider.java create mode 100644 commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordMethanolSenderProvider.java create mode 100644 commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/MethanolFhirRestTLSAuditRecordSender.java create mode 100644 commons/ihe/fhir/core/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider create mode 100644 commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSSenderIntegrationTest.java create mode 100644 commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestApacheTLSSenderIntegrationTest.java create mode 100644 commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestMethanolTLSSenderIntegrationTest.java create mode 100644 commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/queue/FhirDelegateMockedMessageQueue.java create mode 100644 commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/extension/FhirAuditRepository.java create mode 100644 commons/ihe/fhir/core/src/test/resources/security/ca.keystore create mode 100644 commons/ihe/fhir/core/src/test/resources/security/client.p12 create mode 100644 commons/ihe/fhir/core/src/test/resources/security/server.p12 rename commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/{FhirAuditJsonSerializationStrategy.java => BalpJsonSerializationStrategy.java} (82%) rename commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/{FhirAuditXmlSerializationStrategy.java => BalpXmlSerializationStrategy.java} (82%) create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti105/TestIti105WithBalpAudit.java create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/TestIti65WithBalpAudit.java create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti66/v421/TestIti66WithBalpAudit.java create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti67/TestIti67WithBalpAudit.java create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti68/TestIti68WithBalpAudit.java create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/pharm5/TestPharm5WithBalpAudit.java create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/resources/common-fhir-balp-beans.xml create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-105-balp.xml create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-65-balp.xml create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-67-balp.xml create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-68-balp.xml create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/resources/pharm-5-balp.xml create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/resources/security/ca.keystore create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/resources/security/client.p12 create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/resources/security/server.p12 create mode 100644 platform-camel/ihe/fhir/r4/mhd/src/test/resources/v421/iti-66-balp.xml diff --git a/.gitignore b/.gitignore index 27bac84b5e..726edc884a 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ work /commons/ihe/xds/generated-stubs generated-stubs /commons/audit/.vertx +/local_history.patch diff --git a/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfiguration.java b/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfiguration.java index 44c46f4140..c2b1533552 100644 --- a/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfiguration.java +++ b/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfiguration.java @@ -59,6 +59,7 @@ public AuditContext auditContext(IpfAtnaConfigurationProperties config, auditContext.setAuditSource(config.getAuditSourceType()); auditContext.setIncludeParticipantsFromResponse(config.isIncludeParticipantsFromResponse()); auditContext.setAuditValueIfMissing(config.getAuditValueIfMissing()); + auditContext.setAuditRepositoryContextPath(config.getAuditRepositoryContextPath()); // Strategies and complex parameters; overrideable auditContext.setTlsParameters(tlsParameters); @@ -95,10 +96,10 @@ public AuditTransmissionProtocol auditTransmissionProtocol(IpfAtnaConfigurationP TlsParameters tlsParameters) throws Exception { if (config.getAuditSenderClass() != null) { return config.getAuditSenderClass().getConstructor(TlsParameters.class) - .newInstance(tlsParameters); + .newInstance(tlsParameters); } return AuditTransmissionChannel.fromProtocolName(config.getAuditRepositoryTransport()) - .makeInstance(tlsParameters); + .makeInstance(tlsParameters); } @Bean @@ -107,8 +108,8 @@ public AuditMetadataProvider auditMetadataProvider(IpfAtnaConfigurationPropertie @Value("${spring.application.name}") String appName) { var auditMetadataProvider = new DefaultAuditMetadataProvider(); auditMetadataProvider.setSendingApplication(config.getAuditSendingApplication() != null ? - config.getAuditSendingApplication() : - appName); + config.getAuditSendingApplication() : + appName); return auditMetadataProvider; } diff --git a/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaConfigurationProperties.java b/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaConfigurationProperties.java index b7bf094f6f..6316f5ddbf 100644 --- a/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaConfigurationProperties.java +++ b/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaConfigurationProperties.java @@ -64,6 +64,12 @@ public class IpfAtnaConfigurationProperties { @Getter @Setter private int auditRepositoryPort = 514; + /** + * Sets the context-path of the BALP audit record repository. + */ + @Getter @Setter + private String auditRepositoryContextPath = ""; + /** * Enterprise Site Id */ diff --git a/boot/ipf-atna-spring-boot-starter/src/test/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfigurationTest.java b/boot/ipf-atna-spring-boot-starter/src/test/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfigurationTest.java index 565cac25f8..9e01553c81 100644 --- a/boot/ipf-atna-spring-boot-starter/src/test/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfigurationTest.java +++ b/boot/ipf-atna-spring-boot-starter/src/test/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfigurationTest.java @@ -44,6 +44,7 @@ public void testAtnaSettings() throws Exception { assertEquals("mysite", auditContext.getAuditEnterpriseSiteId()); assertEquals("localhost", auditContext.getAuditRepositoryHostName()); assertEquals(1342, auditContext.getAuditRepositoryPort()); + assertEquals("fhir", auditContext.getAuditRepositoryContextPath()); assertEquals("TLS", auditContext.getAuditTransmissionProtocol().getTransportName()); assertTrue(auditContext.getAuditMessageQueue() instanceof AsynchronousAuditMessageQueue); } diff --git a/boot/ipf-atna-spring-boot-starter/src/test/resources/application.yml b/boot/ipf-atna-spring-boot-starter/src/test/resources/application.yml index 56bd9356d9..67a790bfc3 100644 --- a/boot/ipf-atna-spring-boot-starter/src/test/resources/application.yml +++ b/boot/ipf-atna-spring-boot-starter/src/test/resources/application.yml @@ -7,8 +7,8 @@ ipf: audit-enabled: false audit-repository-host: localhost audit-repository-port: 1342 + audit-repository-context-path: fhir audit-repository-transport: TLS audit-enterprise-site-id: mysite audit-queue-class: org.openehealth.ipf.commons.audit.queue.AsynchronousAuditMessageQueue security-domain-name: mydomain - diff --git a/commons/audit/pom.xml b/commons/audit/pom.xml index 863eee205b..1cfeaa8106 100644 --- a/commons/audit/pom.xml +++ b/commons/audit/pom.xml @@ -41,6 +41,10 @@ jakarta.jms-api true + + ca.uhn.hapi.fhir + hapi-fhir-base + org.mockito mockito-core diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/AuditContext.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/AuditContext.java index 89cffc9e08..3517603100 100644 --- a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/AuditContext.java +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/AuditContext.java @@ -111,8 +111,8 @@ default SerializationStrategy getSerializationStrategy() { default void audit(AuditMessage... messages) { if (isAuditEnabled() && messages != null) { getAuditMessageQueue().audit(this, Stream.of(messages) - .map(getAuditMessagePostProcessor()) - .toArray(AuditMessage[]::new)); + .map(getAuditMessagePostProcessor()) + .toArray(AuditMessage[]::new)); } } @@ -165,6 +165,8 @@ default String getAuditValueIfMissing() { */ boolean isIncludeParticipantsFromResponse(); + String getAuditRepositoryContextPath(); + static AuditContext noAudit() { return DefaultAuditContext.NO_AUDIT; } diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultAuditContext.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultAuditContext.java index 79f7801e9b..d1381bd806 100644 --- a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultAuditContext.java +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultAuditContext.java @@ -101,6 +101,10 @@ public class DefaultAuditContext implements AuditContext { @Setter private String auditValueIfMissing = "UNKNOWN"; + @Getter + @Setter + private String auditRepositoryContextPath = ""; + public String getAuditRepositoryTransport() { return auditTransmissionProtocol.getTransportName(); } @@ -111,7 +115,7 @@ public void setAuditRepositoryHost(String host) { public void setAuditRepositoryTransport(String transport) { setAuditTransmissionProtocol( - AuditTransmissionChannel.fromProtocolName(transport).makeInstance(tlsParameters) + AuditTransmissionChannel.fromProtocolName(transport).makeInstance(tlsParameters) ); } diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/FhirContextHolder.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/FhirContextHolder.java new file mode 100644 index 0000000000..36e0e8b952 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/FhirContextHolder.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit; + +import ca.uhn.fhir.context.FhirContext; + +public class FhirContextHolder { + + private static final ThreadLocal currentFhirContext = new ThreadLocal<>(); + + public static void setCurrentContext(FhirContext fhirContext) { + currentFhirContext.set(fhirContext); + } + + public static FhirContext get() { + return currentFhirContext.get(); + } + + public static void remove() { + currentFhirContext.remove(); + } +} + diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionChannel.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionChannel.java index a8a6b87bd1..50c84b929e 100644 --- a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionChannel.java +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionChannel.java @@ -18,33 +18,43 @@ import org.openehealth.ipf.commons.audit.AuditException; import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.providers.NettyTLSSyslogSenderProvider; +import org.openehealth.ipf.commons.audit.protocol.providers.ReactorNettyTLSSyslogSenderProvider; +import org.openehealth.ipf.commons.audit.protocol.providers.RecordingAuditMessageTransmissionProvider; +import org.openehealth.ipf.commons.audit.protocol.providers.TLSSyslogSenderProvider; +import org.openehealth.ipf.commons.audit.protocol.providers.UDPSyslogSenderProvider; +import org.openehealth.ipf.commons.audit.protocol.providers.VertxTLSSyslogSenderProvider; import java.util.Arrays; +import java.util.ServiceLoader; import java.util.stream.Collectors; /** - * Maps AuditTransmissionProtocol names to instances + * Maps AuditTransmissionProtocol names to service providers which should provide a concrete instance of an + * {@link AuditTransmissionProtocol} over a {@link ServiceLoader} mechanism. * * @author Christian Ohr */ public enum AuditTransmissionChannel { - UDP("UDP", UDPSyslogSenderImpl.class), - NIO_UDP("NIO-UDP", UDPSyslogSenderImpl.class), - VERTX_UDP("VERTX-UDP", UDPSyslogSenderImpl.class), - TLS("TLS", TLSSyslogSenderImpl.class), - NIO_TLS("NIO-TLS", NettyTLSSyslogSenderImpl.class), - VERTX_TLS("VERTX-TLS", VertxTLSSyslogSenderImpl.class), - NETTY_TLS("NETTY-TLS", NettyTLSSyslogSenderImpl.class), - REACTOR_NETTY_TLS("REACTOR-NETTY-TLS", ReactorNettyTLSSyslogSenderImpl.class), - RECORDING("RECORDING", RecordingAuditMessageTransmission.class); + UDP("UDP", UDPSyslogSenderProvider.class.getName()), + NIO_UDP("NIO-UDP", UDPSyslogSenderProvider.class.getName()), + VERTX_UDP("VERTX-UDP", UDPSyslogSenderProvider.class.getName()), + TLS("TLS", TLSSyslogSenderProvider.class.getName()), + NIO_TLS("NIO-TLS", NettyTLSSyslogSenderProvider.class.getName()), + VERTX_TLS("VERTX-TLS", VertxTLSSyslogSenderProvider.class.getName()), + NETTY_TLS("NETTY-TLS", NettyTLSSyslogSenderProvider.class.getName()), + REACTOR_NETTY_TLS("REACTOR-NETTY-TLS", ReactorNettyTLSSyslogSenderProvider.class.getName()), + FHIR_REST_TLS("FHIR-REST-TLS", "org.openehealth.ipf.commons.ihe.fhir.audit.protocol.FhirRestTLSAuditRecordApacheSenderProvider"), + FHIR_REST_METHANOL_TLS("FHIR-REST-METHANOL-TLS", "org.openehealth.ipf.commons.ihe.fhir.audit.protocol.FhirRestTLSAuditRecordMethanolSenderProvider"), + RECORDING("RECORDING", RecordingAuditMessageTransmissionProvider.class.getName()); private final String protocolName; - private final Class protocol; + private final String protocolClass; - AuditTransmissionChannel(String protocolName, Class protocol) { + AuditTransmissionChannel(String protocolName, String protocolClass) { this.protocolName = protocolName; - this.protocol = protocol; + this.protocolClass = protocolClass; } public String getProtocolName() { @@ -52,11 +62,13 @@ public String getProtocolName() { } public AuditTransmissionProtocol makeInstance(TlsParameters tlsParameters) { - try { - return protocol.getConstructor(TlsParameters.class).newInstance(tlsParameters); - } catch (Exception e) { - throw new AuditException(e); + ServiceLoader loader = ServiceLoader.load(AuditTransmissionProtocolProvider.class); + for (AuditTransmissionProtocolProvider provider : loader) { + if (protocolClass.equals(provider.getClass().getName())) { + return provider.createAuditTransmissionProtocol(tlsParameters); + } } + throw new AuditException("Could not instantiate AuditTransmissionProtocolProvider for name " + protocolName); } public static AuditTransmissionChannel fromProtocolName(String protocolName) { @@ -66,9 +78,10 @@ public static AuditTransmissionChannel fromProtocolName(String protocolName) { } } throw new IllegalArgumentException("Unknown audit protocol name: " + protocolName + - ". Choose one of: " + - Arrays.stream(AuditTransmissionChannel.values()) - .map(AuditTransmissionChannel::getProtocolName) - .collect(Collectors.joining(","))); + ". Choose one of: " + + Arrays.stream(AuditTransmissionChannel.values()) + .map(AuditTransmissionChannel::getProtocolName) + .collect(Collectors.joining(","))); } + } diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionProtocolProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionProtocolProvider.java new file mode 100644 index 0000000000..ed36133136 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionProtocolProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol; + +import org.openehealth.ipf.commons.audit.TlsParameters; + +/** + * A Service Provider interface located and loaded by {@link java.util.ServiceLoader}. + * Implementations should provide an instance of {@link AuditTransmissionProtocol} + * + * @see AuditTransmissionProtocol + * @see AuditTransmissionChannel + * + * @author Boris Stanojevic + */ +public interface AuditTransmissionProtocolProvider { + AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters); +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/NettyTLSSyslogSenderProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/NettyTLSSyslogSenderProvider.java new file mode 100644 index 0000000000..30f25515e0 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/NettyTLSSyslogSenderProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.NettyTLSSyslogSenderImpl; + +public class NettyTLSSyslogSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new NettyTLSSyslogSenderImpl(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/ReactorNettyTLSSyslogSenderProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/ReactorNettyTLSSyslogSenderProvider.java new file mode 100644 index 0000000000..a18833afce --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/ReactorNettyTLSSyslogSenderProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.ReactorNettyTLSSyslogSenderImpl; + +public class ReactorNettyTLSSyslogSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new ReactorNettyTLSSyslogSenderImpl(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/RecordingAuditMessageTransmissionProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/RecordingAuditMessageTransmissionProvider.java new file mode 100644 index 0000000000..e47861808a --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/RecordingAuditMessageTransmissionProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.RecordingAuditMessageTransmission; + +public class RecordingAuditMessageTransmissionProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new RecordingAuditMessageTransmission(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/TLSSyslogSenderProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/TLSSyslogSenderProvider.java new file mode 100644 index 0000000000..d2c66d9bd5 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/TLSSyslogSenderProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.TLSSyslogSenderImpl; + +public class TLSSyslogSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new TLSSyslogSenderImpl(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/UDPSyslogSenderProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/UDPSyslogSenderProvider.java new file mode 100644 index 0000000000..fd58e63496 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/UDPSyslogSenderProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.UDPSyslogSenderImpl; + +public class UDPSyslogSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new UDPSyslogSenderImpl(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxTLSSyslogSenderProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxTLSSyslogSenderProvider.java new file mode 100644 index 0000000000..7fc0f74424 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxTLSSyslogSenderProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.VertxTLSSyslogSenderImpl; + +@Deprecated +public class VertxTLSSyslogSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new VertxTLSSyslogSenderImpl(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxUDPSyslogSenderProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxUDPSyslogSenderProvider.java new file mode 100644 index 0000000000..3aa76668c7 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxUDPSyslogSenderProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.VertxUDPSyslogSenderImpl; + +@Deprecated +public class VertxUDPSyslogSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new VertxUDPSyslogSenderImpl(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/queue/AbstractAuditMessageQueue.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/queue/AbstractAuditMessageQueue.java index caf1b3b219..a75f0bbb77 100644 --- a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/queue/AbstractAuditMessageQueue.java +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/queue/AbstractAuditMessageQueue.java @@ -18,6 +18,7 @@ import lombok.Setter; import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.FhirContextHolder; import org.openehealth.ipf.commons.audit.model.AuditMessage; import java.util.stream.Stream; @@ -47,9 +48,10 @@ public abstract class AbstractAuditMessageQueue implements AuditMessageQueue { public void audit(AuditContext auditContext, AuditMessage... auditMessages) { if (auditMessages != null) { Stream.of(auditMessages) - .map(msg -> auditContext.getSerializationStrategy().marshal(msg, pretty)) - .forEach(msg -> handle(auditContext, msg)); + .map(msg -> auditContext.getSerializationStrategy().marshal(msg, pretty)) + .forEach(msg -> handle(auditContext, msg)); } + FhirContextHolder.remove(); } protected abstract void handle(AuditContext auditContext, String auditRecord); diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/queue/AsynchronousAuditMessageQueue.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/queue/AsynchronousAuditMessageQueue.java index 8a4d6150d1..8bee6c37f4 100644 --- a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/queue/AsynchronousAuditMessageQueue.java +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/queue/AsynchronousAuditMessageQueue.java @@ -16,8 +16,10 @@ package org.openehealth.ipf.commons.audit.queue; +import ca.uhn.fhir.context.FhirContext; import org.openehealth.ipf.commons.audit.AuditContext; import org.openehealth.ipf.commons.audit.AuditException; +import org.openehealth.ipf.commons.audit.FhirContextHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; @@ -67,31 +69,33 @@ public void setShutdownTimeoutSeconds(int shutdownTimeoutSeconds) { @Override protected void handle(AuditContext auditContext, String auditRecord) { if (auditRecord != null) { - var runnable = runnable(auditContext, auditRecord); + var runnable = runnable(auditContext, auditRecord, FhirContextHolder.get()); if (executorService != null && !executorService.isShutdown()) { CompletableFuture.runAsync(runnable, executorService) - .exceptionally(e -> { - auditContext.getAuditExceptionHandler().handleException(auditContext, e, auditRecord); - return null; - }); + .exceptionally(e -> { + auditContext.getAuditExceptionHandler().handleException(auditContext, e, auditRecord); + return null; + }); } else { runnable.run(); } } } - private Runnable runnable(AuditContext auditContext, String auditRecord) { + private Runnable runnable(AuditContext auditContext, String auditRecord, FhirContext fhirContext) { // Copy the MDC contextMap to re-use it in the worker thread // See this recommendation here: http://logback.qos.ch/manual/mdc.html#managedThreads var mdcContextMap = MDC.getCopyOfContextMap(); return () -> { try { MDC.setContextMap(mdcContextMap); + FhirContextHolder.setCurrentContext(fhirContext); auditContext.getAuditTransmissionProtocol().send(auditContext, auditRecord); } catch (Exception e) { throw new AuditException(e); } finally { MDC.clear(); + FhirContextHolder.remove(); } }; } diff --git a/commons/audit/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider b/commons/audit/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider new file mode 100644 index 0000000000..256537f434 --- /dev/null +++ b/commons/audit/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider @@ -0,0 +1,7 @@ +org.openehealth.ipf.commons.audit.protocol.providers.TLSSyslogSenderProvider +org.openehealth.ipf.commons.audit.protocol.providers.UDPSyslogSenderProvider +org.openehealth.ipf.commons.audit.protocol.providers.NettyTLSSyslogSenderProvider +org.openehealth.ipf.commons.audit.protocol.providers.ReactorNettyTLSSyslogSenderProvider +org.openehealth.ipf.commons.audit.protocol.providers.RecordingAuditMessageTransmissionProvider +org.openehealth.ipf.commons.audit.protocol.providers.VertxTLSSyslogSenderProvider +org.openehealth.ipf.commons.audit.protocol.providers.VertxUDPSyslogSenderProvider \ No newline at end of file diff --git a/commons/ihe/fhir/core/pom.xml b/commons/ihe/fhir/core/pom.xml index e5566be07d..39e6bb4c72 100644 --- a/commons/ihe/fhir/core/pom.xml +++ b/commons/ihe/fhir/core/pom.xml @@ -84,6 +84,11 @@ hapi-fhir-structures-r4 test + + io.undertow + undertow-servlet + test + diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/codes/Constants.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/codes/Constants.java index 03e6b25946..e9b1812e98 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/codes/Constants.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/codes/Constants.java @@ -18,7 +18,14 @@ public class Constants { - public static final String IHE_SYSTEM_NAME = "IHE Transactions"; + public static final String IHE_SYSTEM_NAME = "urn:ihe:event-type-code"; public static final String EHS_SYSTEM_NAME = "e-health-suisse"; + public static final String DCM_SYSTEM_NAME = "http://dicom.nema.org/resources/ontology/DCM"; + public static final String SECURITY_SOURCE_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/security-source-type"; + public static final String AUDIT_ENTITY_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/audit-entity-type"; + public static final String OBJECT_ROLE_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/object-role"; + public static final String AUDIT_LIFECYCLE_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/dicom-audit-lifecycle"; + + } diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSAuditRecordSender.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSAuditRecordSender.java new file mode 100644 index 0000000000..3dee3309dd --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSAuditRecordSender.java @@ -0,0 +1,133 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.impl.RestfulClientFactory; +import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.AuditMetadataProvider; +import org.openehealth.ipf.commons.audit.FhirContextHolder; +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionChannel; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.ihe.fhir.SslAwareAbstractRestfulClientFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; + +import static ca.uhn.fhir.context.FhirContext.forR4; + +/** + * FHIR REST client for sending FHIR Audit Events to an Audit Record Repository. + * + * @author Boris Stanojevic + * @since 4.8 + */ +public abstract class AbstractFhirRestTLSAuditRecordSender implements AuditTransmissionProtocol { + + private IGenericClient client; + private static final String BASE_URL_FORMAT = "https://%s:%s/%s"; + private FhirContext context; + private TlsParameters tlsParameters; + private static final Logger LOG = LoggerFactory.getLogger(AbstractFhirRestTLSAuditRecordSender.class); + + public AbstractFhirRestTLSAuditRecordSender(final FhirContext context, String baseUrl) { + this.context = Objects.requireNonNull(context, "FhirContext must not be null"); + createClient(baseUrl); + } + + public AbstractFhirRestTLSAuditRecordSender(RestfulClientFactory restfulClientFactory, String baseUrl) { + this.context = Objects + .requireNonNull(restfulClientFactory, "RestfulClientFactory must not be null") + .getFhirContext(); + createClient(baseUrl); + } + + public AbstractFhirRestTLSAuditRecordSender(TlsParameters tlsParameters) { + this.tlsParameters = tlsParameters; + } + + @Override + public void send(AuditContext auditContext, + AuditMetadataProvider auditMetadataProvider, + String auditEvent) throws Exception { + if (client == null) { + context = FhirContextHolder.get(); + if (context == null) { + context = forR4(); + } + new TlsParametersAwareRestfulClientFactory( + this.context, + this.tlsParameters); + String baseUrl = String.format(BASE_URL_FORMAT, + auditContext.getAuditRepositoryHostName(), + auditContext.getAuditRepositoryPort(), + auditContext.getAuditRepositoryContextPath()); + createClient(baseUrl); + } + MethodOutcome outcome = client + .create() + .resource(auditEvent) + .execute(); + + LOG.debug("Audit Repository Response: " + outcome.getResponseStatusCode()); + } + + private synchronized void createClient(String baseUrl) { + if (client == null) { + client = context.getRestfulClientFactory().newGenericClient(baseUrl); + } + } + + @Override + public void shutdown() { + + } + + @Override + public String getTransportName() { + return AuditTransmissionChannel.FHIR_REST_TLS.getProtocolName(); + } + + private final class TlsParametersAwareRestfulClientFactory { + + private final FhirContext fhirContext; + private final RestfulClientFactory restfulClientFactory; + + public TlsParametersAwareRestfulClientFactory(FhirContext fhirContext, TlsParameters tlsParameters) { + this.fhirContext = fhirContext; + this.restfulClientFactory = createRestfulFactory(tlsParameters); + } + + private RestfulClientFactory createRestfulFactory(TlsParameters tlsParameters) { + SslAwareAbstractRestfulClientFactory factory = createSslAwareClientFactory(fhirContext); + + factory.initializeSecurityInformation(true, + tlsParameters.getSSLContext(false), null, "", ""); + fhirContext.setRestfulClientFactory(factory); + return factory; + } + + public RestfulClientFactory getRestfulClientFactory() { + return this.restfulClientFactory; + } + } + + protected abstract SslAwareAbstractRestfulClientFactory createSslAwareClientFactory(FhirContext fhirContext); +} diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/ApacheFhirRestTLSAuditRecordSender.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/ApacheFhirRestTLSAuditRecordSender.java new file mode 100644 index 0000000000..445185e2f9 --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/ApacheFhirRestTLSAuditRecordSender.java @@ -0,0 +1,27 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.client.impl.RestfulClientFactory; +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.ihe.fhir.SslAwareAbstractRestfulClientFactory; +import org.openehealth.ipf.commons.ihe.fhir.SslAwareApacheRestfulClientFactory; + +public class ApacheFhirRestTLSAuditRecordSender extends AbstractFhirRestTLSAuditRecordSender { + + public ApacheFhirRestTLSAuditRecordSender(FhirContext context, String baseUrl) { + super(context, baseUrl); + } + + public ApacheFhirRestTLSAuditRecordSender(RestfulClientFactory restfulClientFactory, String baseUrl) { + super(restfulClientFactory, baseUrl); + } + + public ApacheFhirRestTLSAuditRecordSender(TlsParameters tlsParameters) { + super(tlsParameters); + } + + @Override + protected SslAwareAbstractRestfulClientFactory createSslAwareClientFactory(FhirContext fhirContext) { + return new SslAwareApacheRestfulClientFactory(fhirContext); + } +} diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordApacheSenderProvider.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordApacheSenderProvider.java new file mode 100644 index 0000000000..51c64fbbbc --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordApacheSenderProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; + +public class FhirRestTLSAuditRecordApacheSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new ApacheFhirRestTLSAuditRecordSender(tlsParameters); + } +} diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordMethanolSenderProvider.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordMethanolSenderProvider.java new file mode 100644 index 0000000000..2ce6e182de --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordMethanolSenderProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; + +public class FhirRestTLSAuditRecordMethanolSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new MethanolFhirRestTLSAuditRecordSender(tlsParameters); + } +} diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/MethanolFhirRestTLSAuditRecordSender.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/MethanolFhirRestTLSAuditRecordSender.java new file mode 100644 index 0000000000..e2fe01c0cc --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/MethanolFhirRestTLSAuditRecordSender.java @@ -0,0 +1,27 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.client.impl.RestfulClientFactory; +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.ihe.fhir.SslAwareAbstractRestfulClientFactory; +import org.openehealth.ipf.commons.ihe.fhir.SslAwareMethanolRestfulClientFactory; + +public class MethanolFhirRestTLSAuditRecordSender extends AbstractFhirRestTLSAuditRecordSender { + + public MethanolFhirRestTLSAuditRecordSender(FhirContext context, String baseUrl) { + super(context, baseUrl); + } + + public MethanolFhirRestTLSAuditRecordSender(RestfulClientFactory restfulClientFactory, String baseUrl) { + super(restfulClientFactory, baseUrl); + } + + public MethanolFhirRestTLSAuditRecordSender(TlsParameters tlsParameters) { + super(tlsParameters); + } + + @Override + protected SslAwareAbstractRestfulClientFactory createSslAwareClientFactory(FhirContext fhirContext) { + return new SslAwareMethanolRestfulClientFactory(fhirContext); + } +} diff --git a/commons/ihe/fhir/core/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider b/commons/ihe/fhir/core/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider new file mode 100644 index 0000000000..e8c7b31bfc --- /dev/null +++ b/commons/ihe/fhir/core/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider @@ -0,0 +1,2 @@ +org.openehealth.ipf.commons.ihe.fhir.audit.protocol.FhirRestTLSAuditRecordApacheSenderProvider +org.openehealth.ipf.commons.ihe.fhir.audit.protocol.FhirRestTLSAuditRecordMethanolSenderProvider \ No newline at end of file diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSSenderIntegrationTest.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSSenderIntegrationTest.java new file mode 100644 index 0000000000..bb50f46d44 --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSSenderIntegrationTest.java @@ -0,0 +1,113 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.audit.CustomTlsParameters; +import org.openehealth.ipf.commons.audit.DefaultAuditContext; +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.codes.EventOutcomeIndicator; +import org.openehealth.ipf.commons.audit.event.ApplicationActivityBuilder; +import org.openehealth.ipf.commons.audit.utils.AuditUtils; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Paths; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(FhirAuditRepository.class) +public abstract class AbstractFhirRestTLSSenderIntegrationTest { + + protected DefaultAuditContext auditContext; + + private static final Logger LOG = LoggerFactory.getLogger(AbstractFhirRestTLSSenderIntegrationTest.class); + + @BeforeEach + public void setup() { + this.auditContext = new DefaultAuditContext(); + auditContext.setAuditRepositoryPort(FhirAuditRepository.getServerHttpsPort()); + auditContext.setAuditRepositoryHost("localhost"); + auditContext.setAuditEnabled(true); + auditContext.setSerializationStrategy((auditMessage, writer, pretty) -> writer.write("")); + var defaultTls = setupDefaultTlsParameter(); + auditContext.setTlsParameters(defaultTls); + } + + TlsParameters setupDefaultTlsParameter() { + try { + var tlsParameters = new CustomTlsParameters(); + tlsParameters.setKeyStoreFile(Paths.get(AbstractFhirRestTLSSenderIntegrationTest.class.getResource("/security/client.p12").toURI()).toString()); + tlsParameters.setKeyStorePassword("init"); + tlsParameters.setTrustStoreFile(Paths.get(AbstractFhirRestTLSSenderIntegrationTest.class.getResource("/security/ca.keystore").toURI()).toString()); + tlsParameters.setTrustStorePassword("initinit"); + tlsParameters.setEnabledProtocols("TLSv1.2,TLSv1.3"); + return tlsParameters; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @AfterEach + public void tearDown() { + LOG.info("FhirAuditRepository size: " + FhirAuditRepository.getAuditEvents().size() + ". Cleanup...."); + FhirAuditRepository.getAuditEvents().clear(); + LOG.info("FhirAuditRepository size: " + FhirAuditRepository.getAuditEvents().size()); + } + + @Test + public void testTwoWayTLSFlooding() throws Exception { + var count = 500; + var threads = 2; + var executor = Executors.newFixedThreadPool(threads); + + IntStream.range(0, count).forEach(i -> executor.execute(() -> sendAudit(Integer.toString(i)))); + CountDownLatch latch = new CountDownLatch(1); + Executors.newSingleThreadExecutor().execute(new ExpectationRunnable(latch, count)); + assertTrue(latch.await(10, TimeUnit.SECONDS)); + } + + void sendAudit(String userName) { + LOG.debug("Sending audit record"); + auditContext.audit( + new ApplicationActivityBuilder.ApplicationStop(EventOutcomeIndicator.Success) + .setAuditSource(auditContext) + .setApplicationParticipant( + userName, + null, + null, + AuditUtils.getLocalHostName()) + .addApplicationStarterParticipant(System.getProperty("user.name")) + .getMessages()); + } + + static class ExpectationRunnable implements Runnable { + + private final CountDownLatch latch; + + private final int expectedMessagesCount; + + public ExpectationRunnable(CountDownLatch latch, int expectedMessagesCount) { + this.latch = latch; + this.expectedMessagesCount = expectedMessagesCount; + } + + @Override + public void run() { + while (FhirAuditRepository.getAuditEvents().size() < expectedMessagesCount) { + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + latch.countDown(); + } + } +} diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestApacheTLSSenderIntegrationTest.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestApacheTLSSenderIntegrationTest.java new file mode 100644 index 0000000000..b950096fe7 --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestApacheTLSSenderIntegrationTest.java @@ -0,0 +1,13 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import org.junit.jupiter.api.BeforeEach; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionChannel; + +public class FhirRestApacheTLSSenderIntegrationTest extends AbstractFhirRestTLSSenderIntegrationTest { + + @BeforeEach + public void setup() { + super.setup(); + auditContext.setAuditRepositoryTransport(AuditTransmissionChannel.FHIR_REST_TLS.getProtocolName()); + } +} diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestMethanolTLSSenderIntegrationTest.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestMethanolTLSSenderIntegrationTest.java new file mode 100644 index 0000000000..1a883250a2 --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestMethanolTLSSenderIntegrationTest.java @@ -0,0 +1,13 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import org.junit.jupiter.api.BeforeEach; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionChannel; + +public class FhirRestMethanolTLSSenderIntegrationTest extends AbstractFhirRestTLSSenderIntegrationTest { + + @BeforeEach + public void setup() { + super.setup(); + auditContext.setAuditRepositoryTransport(AuditTransmissionChannel.FHIR_REST_METHANOL_TLS.getProtocolName()); + } +} diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/queue/FhirDelegateMockedMessageQueue.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/queue/FhirDelegateMockedMessageQueue.java new file mode 100644 index 0000000000..83bc681872 --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/queue/FhirDelegateMockedMessageQueue.java @@ -0,0 +1,44 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.queue; + +import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.model.AuditMessage; +import org.openehealth.ipf.commons.audit.queue.AbstractMockedAuditMessageQueue; +import org.openehealth.ipf.commons.audit.queue.AsynchronousAuditMessageQueue; +import org.openehealth.ipf.commons.audit.queue.AuditMessageQueue; +import org.openehealth.ipf.commons.audit.queue.SynchronousAuditMessageQueue; + +import java.util.List; + +public class FhirDelegateMockedMessageQueue implements AbstractMockedAuditMessageQueue { + + private final AuditMessageQueue delegate; + + public FhirDelegateMockedMessageQueue() { + this.delegate = new AsynchronousAuditMessageQueue(); + } + + @Override + public void audit(AuditContext auditContext, AuditMessage... auditMessages) { + delegate.audit(auditContext, auditMessages); + } + + @Override + public void flush() { + delegate.flush(); + } + + @Override + public void shutdown() { + delegate.shutdown(); + } + + @Override + public List getMessages() { + return null; + } + + @Override + public void clear() { + + } +} diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/extension/FhirAuditRepository.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/extension/FhirAuditRepository.java new file mode 100644 index 0000000000..a8351b909a --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/extension/FhirAuditRepository.java @@ -0,0 +1,192 @@ +package org.openehealth.ipf.commons.ihe.fhir.extension; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import io.undertow.Handlers; +import io.undertow.Undertow; +import io.undertow.UndertowOptions; +import io.undertow.server.HttpHandler; +import io.undertow.server.handlers.PathHandler; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.InstanceFactory; +import io.undertow.servlet.api.InstanceHandle; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.AuditEvent; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.ResourceType; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.openehealth.ipf.commons.audit.CustomTlsParameters; +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; + +import java.net.ServerSocket; +import java.net.URI; +import java.nio.file.Paths; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; + +import static io.undertow.servlet.Servlets.defaultContainer; +import static io.undertow.servlet.Servlets.deployment; +import static io.undertow.servlet.Servlets.servlet; +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; + +public class FhirAuditRepository implements BeforeAllCallback, BeforeEachCallback { + private static Undertow server; + private ExtensionContext extensionContext; + private static FhirAuditServer fhirAuditServer; + private static final String STORE_KEY = "undertow"; + private static int httpsPort ; + static final String SERVER_KEY_STORE; + static final String SERVER_KEY_STORE_PASS = "init"; + static final String TRUST_STORE; + static final String TRUST_STORE_PASS = "initinit"; + + private static final Logger LOGGER = LoggerFactory.getLogger(FhirAuditRepository.class); + + static { + try { + URI s = new ClassPathResource("/security/server.p12").getURI(); + URI t = new ClassPathResource("/security/ca.keystore").getURI(); + SERVER_KEY_STORE = Paths.get(s).toAbsolutePath().toString(); + TRUST_STORE = Paths.get(t).toAbsolutePath().toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + TlsParameters setupDefaultTlsParameter() { + var tlsParameters = new CustomTlsParameters(); + tlsParameters.setKeyStoreFile(SERVER_KEY_STORE); + tlsParameters.setKeyStorePassword(SERVER_KEY_STORE_PASS); + tlsParameters.setTrustStoreFile(TRUST_STORE); + tlsParameters.setTrustStorePassword(TRUST_STORE_PASS); + tlsParameters.setEnabledProtocols("TLSv1.2,TLSv1.3"); + return tlsParameters; + } + + @Override + public void beforeAll(ExtensionContext extensionContext) throws Exception { + this.extensionContext = extensionContext; + if (hasStartedUndertow()) return; + + httpsPort = freePort(); + registerShutdownHook(); + } + + private int freePort() throws Exception { + try (ServerSocket socket = new ServerSocket(0)) { + socket.setReuseAddress(true); + return socket.getLocalPort(); + } + } + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + if (server == null) { + fhirAuditServer = new FhirAuditServer(); + DeploymentInfo servletBuilder = deployment() + .setClassLoader(FhirAuditRepository.class.getClassLoader()) + .setContextPath("/") + .setDeploymentName("FHIR-Deployment") + .addServlets( + servlet("FhirAuditServer", FhirAuditServer.class, new FhirServletInitiator(fhirAuditServer)) + .addMapping("/*")); + + DeploymentManager manager = defaultContainer().addDeployment(servletBuilder); + manager.deploy(); + + HttpHandler servletHandler = manager.start(); + PathHandler path = Handlers + .path(Handlers.redirect("/")) + .addPrefixPath("/", servletHandler); + server = Undertow.builder() + .setServerOption(UndertowOptions.ENABLE_HTTP2, true) + .addHttpsListener( + httpsPort,"localhost", setupDefaultTlsParameter().getSSLContext(true)) + .setHandler(path) + .build(); + server.start(); + } + } + + private void registerShutdownHook() { + ExtensionContext.Store.CloseableResource closeableResource = () -> { + LOGGER.info("stopping undertow server..."); + if (server != null) server.stop(); + LOGGER.info("successfully stopped undertow server"); + }; + extensionContext.getRoot().getStore(GLOBAL).put(STORE_KEY, closeableResource); + } + + public static int getServerHttpsPort(){ + return httpsPort; + } + public static List getAuditEvents() { + return fhirAuditServer.getAuditEvents(); + } + + private boolean hasStartedUndertow(){ + return extensionContext.getRoot().getStore(GLOBAL).get(STORE_KEY) != null; + } + + static class FhirAuditServer extends RestfulServer implements IResourceProvider { + + private final List auditEvents = new CopyOnWriteArrayList<>(); + + public FhirAuditServer() { + setFhirContext(FhirContext.forR4()); + setResourceProviders(this); + } + + public List getAuditEvents() { + return auditEvents; + } + + @Create() + public MethodOutcome create(@ResourceParam AuditEvent auditEvent) { + auditEvents.add(auditEvent); + return new MethodOutcome( + new IdType(ResourceType.AuditEvent.name(), + UUID.randomUUID().toString()), true); + } + + @Override + public Class getResourceType() { + return AuditEvent.class; + } + } + + static class FhirServletInitiator implements InstanceFactory { + + private final FhirAuditServer fhirAuditServer; + + public FhirServletInitiator(FhirAuditServer fhirAuditServer) { + this.fhirAuditServer = fhirAuditServer; + } + + @Override + public InstanceHandle createInstance() throws InstantiationException { + return new InstanceHandle<>() { + @Override + public FhirAuditServer getInstance() { + return fhirAuditServer; + } + + @Override + public void release() { + + } + }; + } + } +} \ No newline at end of file diff --git a/commons/ihe/fhir/core/src/test/resources/security/ca.keystore b/commons/ihe/fhir/core/src/test/resources/security/ca.keystore new file mode 100644 index 0000000000000000000000000000000000000000..4b334d2e29110a775cd1f620837ac4aa6cf13974 GIT binary patch literal 1814 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 literal 0 HcmV?d00001 diff --git a/commons/ihe/fhir/core/src/test/resources/security/server.p12 b/commons/ihe/fhir/core/src/test/resources/security/server.p12 new file mode 100644 index 0000000000000000000000000000000000000000..47ba4517745c70ab4f02e140bbf2be34016926db GIT binary patch literal 4259 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 literal 0 HcmV?d00001 diff --git a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/AbstractFhirAuditSerializationStrategy.java b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/AbstractFhirAuditSerializationStrategy.java index 96ef5d7887..1b884bf0cd 100644 --- a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/AbstractFhirAuditSerializationStrategy.java +++ b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/AbstractFhirAuditSerializationStrategy.java @@ -22,14 +22,12 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.*; import org.openehealth.ipf.commons.audit.AuditException; -import org.openehealth.ipf.commons.audit.codes.EventActionCode; -import org.openehealth.ipf.commons.audit.codes.EventOutcomeIndicator; -import org.openehealth.ipf.commons.audit.codes.NetworkAccessPointTypeCode; -import org.openehealth.ipf.commons.audit.codes.ParticipantObjectDataLifeCycle; -import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCode; -import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCodeRole; +import org.openehealth.ipf.commons.audit.codes.*; import org.openehealth.ipf.commons.audit.marshal.SerializationStrategy; -import org.openehealth.ipf.commons.audit.model.*; +import org.openehealth.ipf.commons.audit.model.ActiveParticipantType; +import org.openehealth.ipf.commons.audit.model.AuditMessage; +import org.openehealth.ipf.commons.audit.model.AuditSourceIdentificationType; +import org.openehealth.ipf.commons.audit.model.ParticipantObjectIdentificationType; import org.openehealth.ipf.commons.audit.types.CodedValueType; import java.io.IOException; @@ -37,6 +35,8 @@ import java.sql.Date; import java.util.function.Function; +import static org.openehealth.ipf.commons.ihe.fhir.audit.codes.Constants.*; + /** * @author Christian Ohr * @since 3.6 @@ -56,78 +56,77 @@ public AbstractFhirAuditSerializationStrategy(FhirContext fhirContext) { @Override public void marshal(AuditMessage auditMessage, Writer writer, boolean pretty) throws IOException { getParser(fhirContext) - .setPrettyPrint(pretty) - .encodeResourceToWriter(translate(auditMessage), writer); + .setPrettyPrint(pretty) + .encodeResourceToWriter(translate(auditMessage), writer); } protected abstract IParser getParser(FhirContext fhirContext); - protected AuditEvent translate(AuditMessage auditMessage) { + public AuditEvent translate(AuditMessage auditMessage) { var eit = auditMessage.getEventIdentification(); var auditEvent = new AuditEvent() - .setType(codedValueTypeToCoding(eit.getEventID())) - .setAction(getAuditEventAction(eit.getEventActionCode())) - .setRecorded(Date.from(eit.getEventDateTime())) - .setOutcome(getAuditEventOutcome(eit.getEventOutcomeIndicator())) - .setOutcomeDesc(eit.getEventOutcomeDescription()); + .setType(codedValueTypeToCoding(eit.getEventID(), DCM_SYSTEM_NAME)) + .setAction(getAuditEventAction(eit.getEventActionCode())) + .setRecorded(Date.from(eit.getEventDateTime())) + .setOutcome(getAuditEventOutcome(eit.getEventOutcomeIndicator())) + .setOutcomeDesc(eit.getEventOutcomeDescription()); eit.getEventTypeCode().forEach(etc -> - auditEvent.addSubtype(codedValueTypeToCoding(etc))); + auditEvent.addSubtype(codedValueTypeToCoding(etc))); eit.getPurposesOfUse().forEach(pou -> - auditEvent.addPurposeOfEvent(codedValueTypeToCodeableConcept(pou))); + auditEvent.addPurposeOfEvent(codedValueTypeToCodeableConcept(pou))); auditMessage.getActiveParticipants().forEach(ap -> - auditEvent.addAgent(activeParticipantToAgent(ap))); + auditEvent.addAgent(activeParticipantToAgent(ap))); auditEvent.setSource(auditSourceIdentificationToEventSource(auditMessage.getAuditSourceIdentification())); auditMessage.getParticipantObjectIdentifications().forEach(poit -> - auditEvent.addEntity(participantObjectIdentificationToEntity(poit))); + auditEvent.addEntity(participantObjectIdentificationToEntity(poit))); return auditEvent; } protected AuditEvent.AuditEventEntityComponent participantObjectIdentificationToEntity(ParticipantObjectIdentificationType poit) { var entity = new AuditEvent.AuditEventEntityComponent() - .setWhat(new Reference().setIdentifier(new Identifier().setValue(poit.getParticipantObjectID()))) - // poit.getParticipantObjectIDTypeCode())) not used here - .setType(codeToCoding("http://hl7.org/fhir/audit-entity-type", poit.getParticipantObjectTypeCode(), ParticipantObjectTypeCode::getValue)) - .setRole(codeToCoding("http://hl7.org/fhir/object-role", poit.getParticipantObjectTypeCodeRole(), ParticipantObjectTypeCodeRole::getValue)) - .setLifecycle(codeToCoding("http://hl7.org/fhir/dicom-audit-lifecycle", poit.getParticipantObjectDataLifeCycle(), ParticipantObjectDataLifeCycle::getValue)) - .addSecurityLabel(codeToCoding(null, poit.getParticipantObjectSensitivity(), Function.identity())) - .setName(poit.getParticipantObjectName()) - // poit.getParticipantObjectDescription) not mappable here - .setQuery(poit.getParticipantObjectQuery()); + // poit.getParticipantObjectIDTypeCode())) not used here + .setType(codeToCoding(AUDIT_ENTITY_SYSTEM_NAME, poit.getParticipantObjectTypeCode(), ParticipantObjectTypeCode::getValue)) + .setRole(codeToCoding(OBJECT_ROLE_SYSTEM_NAME, poit.getParticipantObjectTypeCodeRole(), ParticipantObjectTypeCodeRole::getValue)) + .setLifecycle(codeToCoding(AUDIT_LIFECYCLE_SYSTEM_NAME, poit.getParticipantObjectDataLifeCycle(), ParticipantObjectDataLifeCycle::getValue)) + .addSecurityLabel(codeToCoding(null, poit.getParticipantObjectSensitivity(), Function.identity())) + .setName(poit.getParticipantObjectName()) + // .setDescription(poit.getParticipantObjectDescriptions().isEmpty() ? null : poit.getParticipantObjectDescriptions().get(0).toString()) + .setQuery(poit.getParticipantObjectQuery()); poit.getParticipantObjectDetails().forEach(tvp -> - entity.addDetail(new AuditEvent.AuditEventEntityDetailComponent() - .setType(tvp.getType()) - .setValue(new Base64BinaryType(tvp.getValue())))); - + entity.addDetail(new AuditEvent.AuditEventEntityDetailComponent() + .setType(tvp.getType()) + .setValue(new Base64BinaryType(tvp.getValue())))); + if (poit.getParticipantObjectTypeCodeRole() == ParticipantObjectTypeCodeRole.Patient) { + entity.setWhat(new Reference(poit.getParticipantObjectID())); + } return entity; } protected AuditEvent.AuditEventSourceComponent auditSourceIdentificationToEventSource(AuditSourceIdentificationType asit) { var source = new AuditEvent.AuditEventSourceComponent() - .setSite(asit.getAuditEnterpriseSiteID()) - .setObserver(new Reference().setIdentifier(new Identifier().setValue(asit.getAuditSourceID()))); + .setSite(asit.getAuditEnterpriseSiteID()) + .setObserver(new Reference().setDisplay(asit.getAuditSourceID())); asit.getAuditSourceType().forEach(ast -> - source.addType(codedValueTypeToCoding(ast))); + source.addType(codedValueTypeToCoding(ast, SECURITY_SOURCE_SYSTEM_NAME))); return source; } protected AuditEvent.AuditEventAgentComponent activeParticipantToAgent(ActiveParticipantType ap) { - var agent = new AuditEvent.AuditEventAgentComponent() - .setWho(new Reference().setIdentifier(new Identifier().setValue(ap.getUserID()))) - .setAltId(ap.getAlternativeUserID()) - .setName(ap.getUserName()) - .setRequestor(ap.isUserIsRequestor()) - .setMedia(codedValueTypeToCoding(ap.getMediaType())) - .setNetwork(new AuditEvent.AuditEventAgentNetworkComponent() - .setAddress(ap.getNetworkAccessPointID()) - .setType(auditEventNetworkType(ap.getNetworkAccessPointTypeCode()))); - ap.getRoleIDCodes().forEach(roleID -> - agent.addPolicy(roleID.getCode())); - return agent; + return new AuditEvent.AuditEventAgentComponent() + .setType(codedValueTypeToCodeableConcept(ap.getRoleIDCodes().get(0), DCM_SYSTEM_NAME)) + .setWho(new Reference().setDisplay(ap.getUserID())) + .setAltId(ap.getAlternativeUserID()) + .setName(ap.getUserName()) + .setRequestor(ap.isUserIsRequestor()) + .setMedia(codedValueTypeToCoding(ap.getMediaType())) + .setNetwork(new AuditEvent.AuditEventAgentNetworkComponent() + .setAddress(ap.getNetworkAccessPointID()) + .setType(auditEventNetworkType(ap.getNetworkAccessPointTypeCode()))); } protected AuditEvent.AuditEventAgentNetworkType auditEventNetworkType(NetworkAccessPointTypeCode naptc) { @@ -160,23 +159,35 @@ protected AuditEvent.AuditEventAction getAuditEventAction(EventActionCode eventA protected Coding codeToCoding(String codeSystem, T code, Function valueSupplier) { return (code != null) ? - new Coding() - .setCode(String.valueOf(valueSupplier.apply(code))) - .setSystem(codeSystem) : - null; + new Coding() + .setCode(String.valueOf(valueSupplier.apply(code))) + .setSystem(codeSystem) : + null; } protected Coding codedValueTypeToCoding(CodedValueType cvt) { return cvt != null ? - new Coding(cvt.getCodeSystemName(), - cvt.getCode(), - cvt.getOriginalText()) : - null; + codedValueTypeToCoding(cvt, cvt.getCodeSystemName()) : + null; + } + + protected Coding codedValueTypeToCoding(CodedValueType cvt, String codeSystem) { + return cvt != null ? + new Coding(codeSystem, + cvt.getCode(), + cvt.getOriginalText()) : + null; } protected CodeableConcept codedValueTypeToCodeableConcept(CodedValueType cvt) { return cvt != null ? - new CodeableConcept().addCoding(codedValueTypeToCoding(cvt)) : - null; + codedValueTypeToCodeableConcept(cvt, cvt.getCodeSystemName()) : + null; + } + + protected CodeableConcept codedValueTypeToCodeableConcept(CodedValueType cvt, String codeSystem) { + return cvt != null ? + new CodeableConcept().addCoding(codedValueTypeToCoding(cvt, codeSystem)) : + null; } } diff --git a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditJsonSerializationStrategy.java b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/BalpJsonSerializationStrategy.java similarity index 82% rename from commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditJsonSerializationStrategy.java rename to commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/BalpJsonSerializationStrategy.java index 66beba40ac..9a750774e2 100644 --- a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditJsonSerializationStrategy.java +++ b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/BalpJsonSerializationStrategy.java @@ -23,13 +23,13 @@ * @author Christian Ohr * @since 4.1 */ -public class FhirAuditJsonSerializationStrategy extends AbstractFhirAuditSerializationStrategy { +public class BalpJsonSerializationStrategy extends AbstractFhirAuditSerializationStrategy { - public FhirAuditJsonSerializationStrategy() { + public BalpJsonSerializationStrategy() { super(); } - public FhirAuditJsonSerializationStrategy(FhirContext fhirContext) { + public BalpJsonSerializationStrategy(FhirContext fhirContext) { super(fhirContext); } diff --git a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditXmlSerializationStrategy.java b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/BalpXmlSerializationStrategy.java similarity index 82% rename from commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditXmlSerializationStrategy.java rename to commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/BalpXmlSerializationStrategy.java index 3aa7816f50..c2058fb875 100644 --- a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditXmlSerializationStrategy.java +++ b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/BalpXmlSerializationStrategy.java @@ -23,13 +23,13 @@ * @author Christian Ohr * @since 4.1 */ -public class FhirAuditXmlSerializationStrategy extends AbstractFhirAuditSerializationStrategy { +public class BalpXmlSerializationStrategy extends AbstractFhirAuditSerializationStrategy { - public FhirAuditXmlSerializationStrategy() { + public BalpXmlSerializationStrategy() { super(); } - public FhirAuditXmlSerializationStrategy(FhirContext fhirContext) { + public BalpXmlSerializationStrategy(FhirContext fhirContext) { super(fhirContext); } diff --git a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditJsonEvent.java b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditJsonEvent.java index 78cf11391d..83803599da 100644 --- a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditJsonEvent.java +++ b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditJsonEvent.java @@ -23,7 +23,7 @@ * @deprecated use FhirAuditJsonSerializationStrategy */ @Deprecated -public class FhirAuditJsonEvent extends FhirAuditJsonSerializationStrategy { +public class FhirAuditJsonEvent extends BalpJsonSerializationStrategy { public FhirAuditJsonEvent() { super(); diff --git a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditXmlEvent.java b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditXmlEvent.java index ae8388d229..8e15b6938d 100644 --- a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditXmlEvent.java +++ b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/FhirAuditXmlEvent.java @@ -23,7 +23,7 @@ * @deprecated use FhirAuditJsonSerializationStrategy */ @Deprecated -public class FhirAuditXmlEvent extends FhirAuditXmlSerializationStrategy { +public class FhirAuditXmlEvent extends BalpXmlSerializationStrategy { public FhirAuditXmlEvent() { super(); diff --git a/commons/ihe/fhir/r4/pixpdq/src/main/groovy/org/openehealth/ipf/commons/ihe/fhir/iti78/PdqmRequestToPdqQueryTranslator.groovy b/commons/ihe/fhir/r4/pixpdq/src/main/groovy/org/openehealth/ipf/commons/ihe/fhir/iti78/PdqmRequestToPdqQueryTranslator.groovy index 1c24de79d7..9590168ac3 100644 --- a/commons/ihe/fhir/r4/pixpdq/src/main/groovy/org/openehealth/ipf/commons/ihe/fhir/iti78/PdqmRequestToPdqQueryTranslator.groovy +++ b/commons/ihe/fhir/r4/pixpdq/src/main/groovy/org/openehealth/ipf/commons/ihe/fhir/iti78/PdqmRequestToPdqQueryTranslator.groovy @@ -179,6 +179,7 @@ class PdqmRequestToPdqQueryTranslator implements FhirTranslator { '@PID.3.4.3': searchIdentifier.map({ it.oid ? 'ISO' : null }).orElse(null), '@PID.5.1' : firstOrNull(searchStringList(searchParameters.family, false)), '@PID.5.2' : firstOrNull(searchStringList(searchParameters.given, false)), + '@PID.6' : searchString(searchParameters.mothersMaidenName, true), '@PID.7' : convertBirthDate(searchParameters.birthDate), '@PID.8' : genderString, '@PID.11.1' : searchString(searchParameters.address, true), @@ -200,7 +201,7 @@ class PdqmRequestToPdqQueryTranslator implements FhirTranslator { } protected String convertBirthDate(DateAndListParam birthDateParam) { - Date birthDate = firstOrNull(searchDateList(birthDateParam)) + var birthDate = firstOrNull(searchDateList(birthDateParam)) return birthDate ? FastDateFormat.getInstance('yyyyMMdd').format(birthDate) : null } @@ -218,7 +219,7 @@ class PdqmRequestToPdqQueryTranslator implements FhirTranslator { param?.valuesAsQueryTokens?.collect { searchString(it.valuesAsQueryTokens.find(), forceExactSearch) } } - protected List searchDateList(DateAndListParam param) { + protected List searchDateList(DateAndListParam param) { param?.valuesAsQueryTokens?.collect { searchDate(it.valuesAsQueryTokens.find()) } } diff --git a/platform-camel/ihe/fhir/r4/mhd/pom.xml b/platform-camel/ihe/fhir/r4/mhd/pom.xml index a37ff1e89f..512a3b86f1 100644 --- a/platform-camel/ihe/fhir/r4/mhd/pom.xml +++ b/platform-camel/ihe/fhir/r4/mhd/pom.xml @@ -43,6 +43,13 @@ test-jar test + + org.openehealth.ipf.commons + ipf-commons-ihe-fhir-core + ${project.version} + test-jar + test + org.apache.camel camel-http @@ -118,6 +125,11 @@ methanol test + + io.undertow + undertow-servlet + test + diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti105/TestIti105WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti105/TestIti105WithBalpAudit.java new file mode 100644 index 0000000000..2866191db6 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti105/TestIti105WithBalpAudit.java @@ -0,0 +1,142 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openehealth.ipf.platform.camel.ihe.fhir.iti105; + +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import org.hl7.fhir.r4.model.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.IpfFhirServlet; +import org.openehealth.ipf.commons.ihe.fhir.SslAwareMethanolRestfulClientFactory; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; +import org.openehealth.ipf.commons.ihe.fhir.mhd.model.SimplifiedPublishDocumentReference; +import org.openehealth.ipf.platform.camel.ihe.fhir.test.FhirTestContainer; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.openehealth.ipf.commons.ihe.fhir.Constants.URN_IETF_RFC_3986; + +/** + * + */ +@ExtendWith(FhirAuditRepository.class) +public class TestIti105WithBalpAudit extends FhirTestContainer { + + private static final String CONTEXT_DESCRIPTOR = "iti-105-balp.xml"; + + @BeforeAll + public static void setUpClass() { + startServer(CONTEXT_DESCRIPTOR); + } + + public static void startServer(String contextDescriptor) { + var servlet = new IpfFhirServlet(FhirVersionEnum.R4); + startServer(servlet, contextDescriptor, false, DEMO_APP_PORT, "FhirServlet"); + + var loggingInterceptor = new LoggingInterceptor(); + loggingInterceptor.setLogRequestSummary(false); + loggingInterceptor.setLogRequestBody(true); + loggingInterceptor.setLogResponseBody(true); + startClient(String.format("http://localhost:%d/", DEMO_APP_PORT), fhirContext -> { + var clientFactory = new SslAwareMethanolRestfulClientFactory(fhirContext); + clientFactory.setAsync(true); + fhirContext.setRestfulClientFactory(clientFactory); + }).registerInterceptor(loggingInterceptor); + } + + @BeforeEach + public void beforeEach() { + FhirAuditRepository.getAuditEvents().clear(); + } + + @Test + public void testSendRemoteSimplifiedPublish() { + var result = client.create() + .resource(documentReference()) + .encodedXml() + .execute(); + assertNotNull(result); + assertTrue(result.getCreated()); + + // Check ATNA Audit + var auditEvents = FhirAuditRepository.getAuditEvents(); + assertEquals(1, auditEvents.size()); + var auditEvent = auditEvents.get(0); + + assertEquals("110107", auditEvent.getType().getCode()); + assertEquals("ITI-105", auditEvent.getSubtypeFirstRep().getCode()); + assertEquals("C", auditEvent.getAction().toCode()); + assertEquals("Import", auditEvent.getType().getDisplay()); + assertEquals("0", auditEvent.getOutcome().toCode()); + } + + @Test + public void testSendOverDirectEndpointSimplifiedPublish() { + producerTemplate.requestBody("direct:input", documentReference(), MethodOutcome.class); + assertEquals(2, FhirAuditRepository.getAuditEvents().size()); + } + + private static DocumentReference documentReference() { + var documentContent = "Hello IHE World".getBytes(); + var practitioner = new Practitioner(); + practitioner.setId("987"); + practitioner.addName(new HumanName() + .setFamily("Soo") + .setGiven(Collections.singletonList(new StringType("Yun")))); + var patient = new Patient(); + patient.setId("123"); + patient.getName().add(new HumanName() + .setFamily("Foo") + .setGiven(Collections.singletonList(new StringType("Barbara")))); + var timestamp = Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)); + var reference = new SimplifiedPublishDocumentReference(); + reference.getMeta().setLastUpdated(timestamp); + + reference + .setUniqueIdIdentifier(URN_IETF_RFC_3986, "urn:oid:129.6.58.92.88336") + .setContent("text/plain", documentContent) + .addIdentifier(new Identifier() + .setSystem(URN_IETF_RFC_3986) + .setValue("urn:oid:129.6.58.92.88336")) + .setDate(timestamp) // creation of document reference resource + .setDescription("Physical") + .setSubject(new Reference(patient)) + .addAuthor(new Reference(practitioner)) + .setStatus(Enumerations.DocumentReferenceStatus.CURRENT); + + reference.getText().setStatus(Narrative.NarrativeStatus.EMPTY); + reference.getText().setDivAsString("
empty
"); + reference.getType().addCoding() + .setSystem("http://ihe.net/connectathon/classCodes") + .setCode("History and Physical") + .setDisplay("History and Physical"); + reference.getContentFirstRep() + .setFormat(new Coding("urn:oid:1.3.6.1.4.1.19376.1.2.3", "urn:ihe:pcc:handp:2008", null)); + return reference; + } + +} diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/TestIti65WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/TestIti65WithBalpAudit.java new file mode 100644 index 0000000000..3af3fc4dc4 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/TestIti65WithBalpAudit.java @@ -0,0 +1,89 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openehealth.ipf.platform.camel.ihe.fhir.iti65.v421; + +import org.hl7.fhir.r4.model.AuditEvent; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +@ExtendWith(FhirAuditRepository.class) +public class TestIti65WithBalpAudit extends AbstractTestIti65 { + + private static final String CONTEXT_DESCRIPTOR = "iti-65-balp.xml"; + + @BeforeAll + public static void setUpClass() { + setup(CONTEXT_DESCRIPTOR); + } + + @BeforeEach + public void beforeEach() { + FhirAuditRepository.getAuditEvents().clear(); + } + + @Test + public void testSendManualMhd() throws Exception { + sendManually(provideAndRegister()); + + // Check ATNA Audit + var auditEvents = FhirAuditRepository.getAuditEvents(); + assertEquals(1, auditEvents.size()); + AuditEvent auditEvent = auditEvents.get(0); + assertEquals("110107", auditEvent.getType().getCode()); + assertEquals("Import", auditEvent.getType().getDisplay()); + assertEquals("ITI-65", auditEvent.getSubtypeFirstRep().getCode()); + assertEquals("C", auditEvent.getAction().toCode()); + assertEquals("0", auditEvent.getOutcome().toCode()); + Optional sourceRole = findRoleAgentWithCode(auditEvent, "110153"); + assertTrue(sourceRole.isPresent()); + assertTrue(sourceRole.get().getRequestor()); + Optional destinationRole = findRoleAgentWithCode(auditEvent, "110152"); + assertTrue(destinationRole.isPresent()); + assertFalse(destinationRole.get().getRequestor()); + assertEquals(1, auditEvent.getEntity().stream() + .filter(event -> event.getType().getCode().equals("2") && event.getRole().getCode().equals("20")) + .count()); + } + + @Test + public void testSendEndpointMhd() throws Exception { + var bundle = provideAndRegister(); + sendViaProducer(bundle); + + // Check ATNA Audit + var auditEvents = FhirAuditRepository.getAuditEvents(); + assertEquals(2, auditEvents.size()); + } + + private Optional findRoleAgentWithCode(AuditEvent auditEvent, String code) { + return auditEvent.getAgent().stream() + .filter(p -> p.getType().getCodingFirstRep().getCode().equals(code)) + .findFirst(); + } +} diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti66/v421/TestIti66WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti66/v421/TestIti66WithBalpAudit.java new file mode 100644 index 0000000000..4cf4e5abf7 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti66/v421/TestIti66WithBalpAudit.java @@ -0,0 +1,102 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openehealth.ipf.platform.camel.ihe.fhir.iti66.v421; + +import org.hl7.fhir.r4.model.AuditEvent; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.ListResource; +import org.hl7.fhir.r4.model.ResourceType; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; + +import java.util.Objects; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +@ExtendWith(FhirAuditRepository.class) +public class TestIti66WithBalpAudit extends AbstractTestIti66 { + + private static final String CONTEXT_DESCRIPTOR = "v421/iti-66-balp.xml"; + + @BeforeAll + public static void setUpClass() { + startServer(CONTEXT_DESCRIPTOR); + } + + @BeforeEach + public void beforeEach() { + FhirAuditRepository.getAuditEvents().clear(); + } + + @Test + public void testSendManualIti66() { + var result = sendManually(listPatientIdentifierParameter(), statusParameter()); + + assertEquals(Bundle.BundleType.SEARCHSET, result.getType()); + assertEquals(ResourceType.Bundle, result.getResourceType()); + assertEquals(1, result.getTotal()); + + var p = (ListResource) result.getEntry().get(0).getResource(); + assertEquals("9bc72458-49b0-11e6-8a1c-3c1620524153", p.getIdElement().getIdPart()); + + assertEquals(1, FhirAuditRepository.getAuditEvents().size()); + AuditEvent auditEvent = FhirAuditRepository.getAuditEvents().get(0); + assertEquals("110112", auditEvent.getType().getCode()); + assertEquals("Query", auditEvent.getType().getDisplay()); + assertEquals("ITI-66", auditEvent.getSubtypeFirstRep().getCode()); + assertEquals("E", auditEvent.getAction().toCode()); + assertEquals("0", auditEvent.getOutcome().toCode()); + Optional sourceRole = findRoleAgentWithCode(auditEvent, "110153"); + assertTrue(sourceRole.isPresent()); + assertTrue(sourceRole.get().getRequestor()); + Optional destinationRole = findRoleAgentWithCode(auditEvent, "110152"); + assertTrue(destinationRole.isPresent()); + assertFalse(destinationRole.get().getRequestor()); + assertEquals(1, auditEvent.getEntity().stream() + .filter(event -> event.getType().getCode().equals("2") && event.getRole().getCode().equals("24")) + .map(AuditEvent.AuditEventEntityComponent::getQuery) + .filter(Objects::nonNull) + .count()); + } + + @Test + public void testSendIti66WithPatientReference() { + var result = sendViaProducer(listPatientReferenceParameter(), statusParameter()); + assertEquals(Bundle.BundleType.SEARCHSET, result.getType()); + assertEquals(ResourceType.Bundle, result.getResourceType()); + assertEquals(1, result.getTotal()); + + var p = (ListResource) result.getEntry().get(0).getResource(); + assertEquals("9bc72458-49b0-11e6-8a1c-3c1620524153", p.getIdElement().getIdPart()); + assertEquals(2, FhirAuditRepository.getAuditEvents().size()); + } + + private Optional findRoleAgentWithCode(AuditEvent auditEvent, String code) { + return auditEvent.getAgent().stream() + .filter(p -> p.getType().getCodingFirstRep().getCode().equals(code)) + .findFirst(); + } +} \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti67/TestIti67WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti67/TestIti67WithBalpAudit.java new file mode 100644 index 0000000000..3428b3cacd --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti67/TestIti67WithBalpAudit.java @@ -0,0 +1,111 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openehealth.ipf.platform.camel.ihe.fhir.iti67; + +import org.hl7.fhir.r4.model.AuditEvent; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DocumentReference; +import org.hl7.fhir.r4.model.ResourceType; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +@ExtendWith(FhirAuditRepository.class) +public class TestIti67WithBalpAudit extends AbstractTestIti67 { + + private static final String CONTEXT_DESCRIPTOR = "iti-67-balp.xml"; + + @BeforeAll + public static void setUpClass() { + startServer(CONTEXT_DESCRIPTOR); + } + + @BeforeEach + public void beforeEach() { + FhirAuditRepository.getAuditEvents().clear(); + } + + @Test + public void testSendManualIti67() { + var result = sendManually(referencePatientIdentifierParameter()); + + assertEquals(Bundle.BundleType.SEARCHSET, result.getType()); + assertEquals(ResourceType.Bundle, result.getResourceType()); + assertEquals(1, result.getTotal()); + + var p = (DocumentReference) result.getEntry().get(0).getResource(); + assertEquals("63ab1c29-4225-11e6-9b33-0050569b0094", p.getIdElement().getIdPart()); + + // Check ATNA Audit + var auditEvents = FhirAuditRepository.getAuditEvents(); + assertEquals(1, auditEvents.size()); + var auditEvent = auditEvents.get(0); + + assertEquals("110112", auditEvent.getType().getCode()); + assertEquals("ITI-67", auditEvent.getSubtypeFirstRep().getCode()); + assertEquals("E", auditEvent.getAction().toCode()); + assertEquals("Query", auditEvent.getType().getDisplay()); + assertEquals("0", auditEvent.getOutcome().toCode()); + Optional sourceRole = findRoleAgentWithCode(auditEvent, "110153"); + assertTrue(sourceRole.isPresent()); + assertTrue(sourceRole.get().getRequestor()); + Optional destinationRole = findRoleAgentWithCode(auditEvent, "110152"); + assertTrue(destinationRole.isPresent()); + assertFalse(destinationRole.get().getRequestor()); + assertEquals(1, auditEvent.getEntity().stream() + .filter(event -> event.getType().getCode().equals("2") && event.getRole().getCode().equals("24")) + .map(AuditEvent.AuditEventEntityComponent::getQuery) + .filter(Objects::nonNull) + .count()); + } + + @Test + public void testSendEndpointIti67() { + sendViaProducer(referencePatientIdentifierParameter()); + assertEquals(2, FhirAuditRepository.getAuditEvents().size()); + + List queries = FhirAuditRepository.getAuditEvents().stream().flatMap(event -> event.getEntity().stream() + .filter(entity -> entity.getType().getCode().equals("2") && entity.getRole().getCode().equals("24")) + .map(AuditEvent.AuditEventEntityComponent::getQuery) + .filter(Objects::nonNull)).collect(Collectors.toList()); + + String query = new String(queries.get(0), StandardCharsets.UTF_8); + assertTrue(query.startsWith("patient.identifier=urn:oid:2.16.840.1.113883.3.37.4.1.1.2.1.1|1")); + } + + private Optional findRoleAgentWithCode(AuditEvent auditEvent, String code) { + return auditEvent.getAgent().stream() + .filter(p -> p.getType().getCodingFirstRep().getCode().equals(code)) + .findFirst(); + } + +} diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti68/TestIti68WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti68/TestIti68WithBalpAudit.java new file mode 100644 index 0000000000..d04c5e665b --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti68/TestIti68WithBalpAudit.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openehealth.ipf.platform.camel.ihe.fhir.iti68; + +import ca.uhn.fhir.rest.gclient.ICriterion; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * + */ +@ExtendWith(FhirAuditRepository.class) +public class TestIti68WithBalpAudit extends AbstractTestIti68 { + + private static final String CONTEXT_DESCRIPTOR = "iti-68-balp.xml"; + + @BeforeAll + public static void setUpClass() { + startServer(CONTEXT_DESCRIPTOR); + } + + @BeforeEach + public void beforeEach() { + FhirAuditRepository.getAuditEvents().clear(); + } + + @Test + public void testRetrieveDocument() { + var response = sendViaProducer((ICriterion) null); + assertArrayEquals(Iti68TestRouteBuilder.DATA, response); + + // Check ATNA Audit + var auditEvents = FhirAuditRepository.getAuditEvents(); + assertEquals(1, auditEvents.size()); + var auditEvent = auditEvents.get(0); + + assertEquals("110106", auditEvent.getType().getCode()); + assertEquals("ITI-68", auditEvent.getSubtypeFirstRep().getCode()); + assertEquals("C", auditEvent.getAction().toCode()); + assertEquals("Export", auditEvent.getType().getDisplay()); + assertEquals("0", auditEvent.getOutcome().toCode()); + } + + +} diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/pharm5/TestPharm5WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/pharm5/TestPharm5WithBalpAudit.java new file mode 100644 index 0000000000..7fc0f2be73 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/pharm5/TestPharm5WithBalpAudit.java @@ -0,0 +1,81 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.platform.camel.ihe.fhir.pharm5; + +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DocumentReference; +import org.hl7.fhir.r4.model.ResourceType; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * + */ +@ExtendWith(FhirAuditRepository.class) +public class TestPharm5WithBalpAudit extends AbstractTestPharm5 { + private static final String CONTEXT_DESCRIPTOR = "pharm-5-balp.xml"; + + @BeforeAll + public static void setUpClass() { + startServer(CONTEXT_DESCRIPTOR); + } + + @BeforeEach + public void beforeEach() { + FhirAuditRepository.getAuditEvents().clear(); + } + + @Test + void testPharm5FindMedicationTreatmentPlans() { + var result = sendManually(findMedicationTreatmentPlanParameters()); + + assertEquals(Bundle.BundleType.SEARCHSET, result.getType()); + assertEquals(ResourceType.Bundle, result.getResourceType()); + assertEquals(1, result.getTotal()); + + var p = (DocumentReference) result.getEntry().get(0).getResource(); + assertEquals("63ab1c29-4225-11e6-9b33-0050569b0094", p.getIdElement().getIdPart()); + assertEquals("$find-medication-treatment-plans", p.getDescription()); + + // Check ATNA Audit + assertEquals(1, FhirAuditRepository.getAuditEvents().size()); + var auditEvent = FhirAuditRepository.getAuditEvents().get(0); + assertEquals("110112", auditEvent.getType().getCode()); + assertEquals("PHARM-5", auditEvent.getSubtypeFirstRep().getCode()); + assertEquals("E", auditEvent.getAction().toCode()); + assertEquals("Query", auditEvent.getType().getDisplay()); + assertEquals("0", auditEvent.getOutcome().toCode()); + } + + @Test + void testSendPharm5FindMedicationList() { + var result = sendManually(findMedicationListParameters()); + assertEquals(Bundle.BundleType.SEARCHSET, result.getType()); + assertEquals(ResourceType.Bundle, result.getResourceType()); + assertEquals(1, result.getTotal()); + + var p = (DocumentReference) result.getEntry().get(0).getResource(); + assertEquals("63ab1c29-4225-11e6-9b33-0050569b0094", p.getIdElement().getIdPart()); + assertEquals("$find-medication-list", p.getDescription()); + + assertEquals(1, FhirAuditRepository.getAuditEvents().size()); + } +} diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/common-fhir-balp-beans.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/common-fhir-balp-beans.xml new file mode 100644 index 0000000000..0fa7688be8 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/common-fhir-balp-beans.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-105-balp.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-105-balp.xml new file mode 100644 index 0000000000..57d8d32241 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-105-balp.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-65-balp.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-65-balp.xml new file mode 100644 index 0000000000..75c8176d30 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-65-balp.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-67-balp.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-67-balp.xml new file mode 100644 index 0000000000..0362f3ee28 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-67-balp.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-68-balp.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-68-balp.xml new file mode 100644 index 0000000000..d8c167de39 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-68-balp.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/pharm-5-balp.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/pharm-5-balp.xml new file mode 100644 index 0000000000..03f58ed71d --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/pharm-5-balp.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/security/ca.keystore b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/security/ca.keystore new file mode 100644 index 0000000000000000000000000000000000000000..4b334d2e29110a775cd1f620837ac4aa6cf13974 GIT binary patch literal 1814 zcmV+x2kH1Qf(H@;0Ru3C2D}CdDuzgg_YDCD0ic2gtptJwsW5^Dr7(g9p#}*mhDe6@ z4FLxRpn?XHFoFh-0s#Opf(C~M2`Yw2hW8Bt2LUi<1_>&LNQU+thDZTr0|Wso1Q6&Gm`2V=uW6x78tdX+S zLEK22j5lqKX|}qZ3H6)XqV<8WPu#Mun9;T7pL1Wwer%mmXyG*l^k=Q*?t6Bn6WIeC z$?AQ;LpB2}I6e{(XGEo5GwC-e5u8w*cU)S8Zp0Lf@_>zm*68njyx!7EPPy!u`}8a? zV6l^{U_)3u+N*ajh?hu)8TCqmaTr<5=I0LmSuqdt_@w3v(qfwo2t5rmtpB~M}1moof@G3CblZCx4DTZ+m11uQwoUG!YgG8Z( z)KxQdls5Eg806ulW}57kzlM z>lw#PE| zJZ~Ga9$nc&;Ai01l--N#$VKUuW0o{9;i$7cI;* zbva4z&kT06z5SsSanP`u!}}=ReHHRYyiiqK#}&zZMIZw7JCS(60y(^cdcpjTp8MWs z%2ciAeuz8lGhj*Lu9FR!Q@fyigty%J*WfJ{Tq7Is4S~ z2jg!PZeUqzc`|o_J~2w-@EX1L&p|K{a+EMZOL-UgHy||a!yX30OS3*$kp8NBf4C>z zimg$0DySo|kM9@^cOk#gns@c0;N?z-e^fcs*jMIyI*@fzU%i+70n47r4iH^+dYmnj z)58R?-!5!ihsqxKH%53=PybKSyq8k#kGrUf7#WbOi*J1g!tWe-S>gqs*j{7u|86|a zi0}`Lm^D<>j+42p3$>?SGEBWw{NC9O71OfpC00bZi9As~n zUp*R9ACO6J!8+v#fuI#!DEy*@IiHhuC1)rQMI=Q~YCwjN zkS+;7zxDs?oXhWGuf6vBUhKR5yc>q5O2!8ez|d4`L?FQ^&8Q1<05RY-n(6@%O(ph6 z7KNdSxc;?>UZaU<|45Yh0K7kqGy7*CB!@kQ3H&>f!B~JK$Uv-k4@>P8K0XN^ zjEson|F+?S2=QR7L?F8;O@Jc-5FiMo@+hs9U+8`SHe;a{tkjz~3q=#YE-szJ{;;U~ zEt7IrGOMJ|&J%-qTVN`WYZ><<*(qhHVKiDZJk``Zh!-kp!_Eew_k5}^*LD}65h!-7 z_g||!#|JzU3b=F5z@h6M>RpULRaF%OL%fbw-#yxpwAg%sKA!?*2ItwOIJjQp-heOc z-fEj*y62%=n#QabVxRZ}1PLzZYWuYB6<>bBv%XOVtXDK20$?Q;FUMbY?T==ar4DjB z(D3?`ef{k4xkBVTGTNvMsqK>zeDYmbJ=Xrc&|ElPur&LjQ(@7DjfZuR5_!Nd_uLCC2rH zU3R=z@)I>F;{&GNQntrwizPmbc!QRgs>3PeNDAd-w;?Q#r+9dsV-Q zucc@O7r)i^ZZZqu;39cedF9!q9dD4g9!kU6eU2QI@qhDAsqOx_ea^N|lWRiMp{fE7 zTXATn2yn%YEWOw6H#KJ=yD2k!#byKFt91XhnYG)2mw^+LFPwf{Zx{A$dwrHr(rS>Y zYFAoaC_L^NZ_lFbPw9qHtu8O8oZ1EurG>13VUFzT?>K-7wzrUF>q&aPm=fFhR?_{;`v^q&%C5{{r;1i5Nw;lJ{Yr=!_%G5 zY(1H0-3Hr{`HRh?)GN$j6f?#Aij4>}yq;~d{PBb;(9)PS6%Hx^pP7cm&q@3;hpe9?&!qxRS^>AyC7@@mc;6|%v!)qClfq`KKJfyF} zCfvTat8B$7B5pOQ9Woxd?7U`XQ|i!Oarw}HSMA2^$*+Z+#d-yjAbNi;f9(ROt(oudv8%w{0swYULz4d z7k?cFL7qQ7)qJqX-@ZbhC-dQ30Yp$Pif_K06s!)x66aK+qDq)O5Wsz$dN)mdsT09K z>C@u+b4I5SgWZgZpB@HzL=Q!eqbgRvv&BKqN4d}91V6G2sJ@n&A034NODtEfY##Gi|QAc?9zK9K{_%(xAM`z z3Ot>+hK5Z#8o{eEBU5AXE^_*Jrk0ORZRzzZCoec|tRkC(@>91@;f>7&(O$ftgsn9T zhzI$c#xrOAb-8L4Yq57@NB32Eq1mizO9Ugd%5-iLsB8uA_&bXBqJ)|==Vu-}e!j27 z_lrF*zlaqr)j0WAE}<}8jz#28mfbBn6Cz?cU}!M#pL_pbfPl_`XwVT14ch-xKZr>G zpDkoy0RA7d@sC*hzkq>*4kZ}>vR#n^#Ltz$PGls1{HpIuZ zLivGLJDf}+TQ5`bcMM;O%(*mA<-ER>lW~`SM8cxu;O@L25n0$pGCk;aiesSWna?ME ztcuQeBQ-Lc>5K>6*&!ve!%$_ACtr?O1#`Sh|8~Fj6_t6~H9~w~^RVFiw(DUKN^I{W zxE{J(-(pxh&y&FCS)_fo9>a@qKBucvz?6h#u z;&O}O!7qd}iDY&B*xjh&m6 zkJS2&fEvYxZN>&Zy)LGk@5;)5ePFlNd=TnNK`K#u%tzs zvgf|;PO>zvwql)`OFX^NEesl|^z56$vvw&JeW}ggu2_okWAWVuero|AM!CT@(ascM=GkCFq+izT-Ir zUs2-v=|zv%>JAzeV%byr13nZ8r~j0Wti5cXHIGZSMYOhira5~g918C^i+OLeP2TwZ zSObyx@Gf>VZ2fXfFnT39)#q;@dqcla$FuDCYb8_7>E#PU8z>%kCwtc(V=iB0{PT$! z&0%sYp32<1np#%Aw}zGu^MzURjN*NT&A(<^@^ibDXC&V@lmJ%=ypIJksN?QhQii6Q-?3kLvuiH`|+zE1fd0M|IEifVMhin?f zy3jFsi-{J18JbWslW<_`C-VW`8JTt2)(XEBH;pVR*vwoJV@}$ttFfSdLbYnmKakkQ zWyrK*nZzd;?zM9N;<>S14_RDe@lwQ*Gx_`gxXPWbUd&bq@FZ;eEYk}6m08sZPhj~-vM3dLpw?M!*kKEL40s2Q=Q$E1Y~v)|ZX)aep8 z2tN}D-a52Z*lJ2iH0xbSp7J&srxMpVG_lei{UG4-ZJjQ?QKzYfb~2-&;?QXF>KlXu zqH~>@nLSVPkudR7zIt>0RIH7zbHkw^O5%P*xcCay*I6nPsR&iB$6#oAm4J-igCw$i zDd@IoLip4AnSh~T!t`9>o9&sqh^HQ!sGX}6C|K5ycEZ&xEp$9RX@mNwltDyD>5lud3V z0{!C*lydjggX-l@wJ7vt_N~P@({mllW?9aLkW9W%zw2cTL?_XIM<8PAYve9`bz4NJ#$z zEkEIa36mIzEFp`zwwoKiG~U4~{Vv?L();=8z|LF`&z9tRoj>X>p=~@TvPy=$WtD66b+sJ# zXVi;w>KBpDnJ!Yk)i9*r4Q>3AG$#!`{0q`}>4eAhO=kFp`YRB>P>gJ6A$s@vQ2r-C>tkF8VinJ~-D zLaN=5^^3M*Nbx4$5NbgZq*&7QET>haAfaE-5!p7|{86Cp=VQx>1_G52Ai;s(xCrfH zbs8&sEN*bN-H(hay=5+2qnX@5lV``;b08rCwuT1lSEWWI8&56$LEzTkl_{MKDv`USH=wEAiswbAQJ=jpEPG{yD*2G8>eU z$X3$>DeV^>c4(Igh#xNxh%tScUgm$Q)|t}Gd~>+hYHO;J;oeHC7aDW%wE=&;{^3o* zP|S{C;uk^H8(!}5FuTEeRT?>4U(XVa3?M~tqdZm6&^dTOEcam3a+?Ym#!chJ z#vNbl`T8}je7q9k4x)5 literal 0 HcmV?d00001 diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/security/server.p12 b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/security/server.p12 new file mode 100644 index 0000000000000000000000000000000000000000..47ba4517745c70ab4f02e140bbf2be34016926db GIT binary patch literal 4259 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 literal 0 HcmV?d00001 diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/v421/iti-66-balp.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/v421/iti-66-balp.xml new file mode 100644 index 0000000000..641c177e88 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/v421/iti-66-balp.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/pixpdq/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti78/TestIti78Success.java b/platform-camel/ihe/fhir/r4/pixpdq/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti78/TestIti78Success.java index 4331bb915d..9ebc06be52 100644 --- a/platform-camel/ihe/fhir/r4/pixpdq/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti78/TestIti78Success.java +++ b/platform-camel/ihe/fhir/r4/pixpdq/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti78/TestIti78Success.java @@ -25,6 +25,7 @@ import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirParticipantObjectIdTypeCode; import org.openehealth.ipf.commons.ihe.fhir.iti78.PdqPatient; +import org.openehealth.ipf.commons.ihe.fhir.support.audit.marshal.BalpJsonSerializationStrategy; import java.nio.charset.StandardCharsets; @@ -107,6 +108,9 @@ public void testSendManualPdqm() { assertEquals(FhirParticipantObjectIdTypeCode.MobilePatientDemographicsQuery, patient.getParticipantObjectIDTypeCode()); + var fhir = new BalpJsonSerializationStrategy(serverFhirContext).marshal(event, true); + System.out.println(fhir); + } @Test @@ -126,6 +130,9 @@ public void testGetResource() { assertEquals(ParticipantObjectTypeCode.Person, patient.getParticipantObjectTypeCode()); assertEquals(ParticipantObjectTypeCodeRole.Patient, patient.getParticipantObjectTypeCodeRole()); assertEquals("Patient/4711", patient.getParticipantObjectID()); + + var fhir = new BalpJsonSerializationStrategy(serverFhirContext).marshal(event, true); + System.out.println(fhir); } diff --git a/pom.xml b/pom.xml index 6b4afd1b8a..7e9a5202c4 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,7 @@ 5.15.0 0.6 9.0.83 + 2.2.28.Final 4.24 @@ -260,6 +261,12 @@ mockserver-client-java ${mockserver-version} + + io.undertow + undertow-servlet + ${undertow-version} + test + From 68b7dfaaf61df84c66a90b3a383ab9d879cda095 Mon Sep 17 00:00:00 2001 From: Boris Stanojevic Date: Wed, 31 Jan 2024 17:28:28 +0100 Subject: [PATCH 2/2] 433: BALP audit with oauth token support --- boot/ipf-atna-spring-boot-starter/pom.xml | 4 + .../boot/atna/IpfAtnaAutoConfiguration.java | 91 +++++++++- .../atna/IpfAtnaConfigurationProperties.java | 90 +++++++++- .../atna/IpfAtnaAutoConfigurationTest.java | 1 - .../IpfAtnaBalpAutoConfigurationTest.java | 64 +++++++ .../src/test/resources/application-balp.yml | 8 + .../src/test/resources/application.yml | 1 - .../ipf/commons/audit/AuditContext.java | 2 - .../ipf/commons/audit/BalpAuditContext.java | 27 +++ .../audit/BalpJwtExtractorProperties.java | 68 +++++++ .../commons/audit/DefaultAuditContext.java | 4 - .../audit/DefaultBalpAuditContext.java | 31 ++++ commons/ihe/fhir/core/pom.xml | 4 + .../ipf/commons/ihe/fhir/Constants.java | 1 + .../fhir/audit/AbstractFhirAuditStrategy.java | 9 +- .../ihe/fhir/audit/FhirAuditDataset.java | 4 + .../fhir/audit/GenericFhirAuditStrategy.java | 3 + .../audit/auth/BalpJwtClaimsExtractor.java | 167 ++++++++++++++++++ .../ihe/fhir/audit/auth/BalpJwtDataSet.java | 27 +++ .../ihe/fhir/audit/auth/BalpJwtParser.java | 68 +++++++ .../ihe/fhir/audit/codes/Constants.java | 9 +- .../ihe/fhir/audit/events/BalpJwtUtils.java | 92 ++++++++++ .../audit/events/BalpPHIExportBuilder.java | 47 +++++ .../audit/events/BalpPHIImportBuilder.java | 41 +++++ .../events/BalpQueryInformationBuilder.java | 24 +++ .../GenericFhirAuditMessageBuilder.java | 5 + .../AbstractFhirRestTLSAuditRecordSender.java | 4 +- .../auth/BalpJwtClaimsExtractorTest.java | 108 +++++++++++ .../ihe/fhir/audit/auth/BalpJwtGenerator.java | 105 +++++++++++ ...tractFhirRestTLSSenderIntegrationTest.java | 9 +- .../fhir/audit/server/FhirAuditServer.java | 130 ++++++++++++++ .../fhir/audit/server/TLSBalpRepository.java | 118 +++++++++++++ .../fhir/extension/FhirAuditRepository.java | 74 +++----- ...bstractFhirAuditSerializationStrategy.java | 74 +++++++- .../iti105/Iti105ClientAuditStrategy.java | 4 +- .../iti105/Iti105ServerAuditStrategy.java | 4 +- .../fhir/iti65/Iti65ClientAuditStrategy.java | 4 +- .../fhir/iti65/Iti65ServerAuditStrategy.java | 4 +- .../ihe/fhir/iti66/Iti66AuditStrategy.java | 3 +- .../ihe/fhir/iti67/Iti67AuditStrategy.java | 3 +- .../fhir/iti68/Iti68ServerAuditStrategy.java | 3 +- .../Iti68BinaryServerAuditStrategy.java | 3 +- .../ihe/fhir/iti78/Iti78AuditStrategy.java | 4 +- .../ihe/fhir/iti83/Iti83AuditStrategy.java | 4 +- dependencies/pom.xml | 8 +- .../consumer/AuditInterceptorUtils.java | 24 +++ .../consumer/ConsumerAuditInterceptor.java | 8 +- platform-camel/ihe/fhir/r4/mhd/pom.xml | 4 + .../fhir/iti105/TestIti105WithBalpAudit.java | 2 +- .../fhir/iti65/v421/AbstractTestIti65.java | 20 ++- .../iti65/v421/TestIti65WithBalpAudit.java | 11 +- .../iti66/v421/TestIti66WithBalpAudit.java | 2 +- .../fhir/iti67/TestIti67WithBalpAudit.java | 2 +- .../fhir/iti68/TestIti68WithBalpAudit.java | 2 +- .../fhir/pharm5/TestPharm5WithBalpAudit.java | 2 +- .../test/resources/common-fhir-balp-beans.xml | 7 +- 56 files changed, 1532 insertions(+), 110 deletions(-) create mode 100644 boot/ipf-atna-spring-boot-starter/src/test/java/org/openehealth/ipf/boot/atna/IpfAtnaBalpAutoConfigurationTest.java create mode 100644 boot/ipf-atna-spring-boot-starter/src/test/resources/application-balp.yml create mode 100644 commons/audit/src/main/java/org/openehealth/ipf/commons/audit/BalpAuditContext.java create mode 100644 commons/audit/src/main/java/org/openehealth/ipf/commons/audit/BalpJwtExtractorProperties.java create mode 100644 commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultBalpAuditContext.java create mode 100644 commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtClaimsExtractor.java create mode 100644 commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtDataSet.java create mode 100644 commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtParser.java create mode 100644 commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpJwtUtils.java create mode 100644 commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpPHIExportBuilder.java create mode 100644 commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpPHIImportBuilder.java create mode 100644 commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpQueryInformationBuilder.java create mode 100644 commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtClaimsExtractorTest.java create mode 100644 commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtGenerator.java create mode 100644 commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/server/FhirAuditServer.java create mode 100644 commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/server/TLSBalpRepository.java diff --git a/boot/ipf-atna-spring-boot-starter/pom.xml b/boot/ipf-atna-spring-boot-starter/pom.xml index a82afb2554..51009ad4dd 100644 --- a/boot/ipf-atna-spring-boot-starter/pom.xml +++ b/boot/ipf-atna-spring-boot-starter/pom.xml @@ -37,6 +37,10 @@ org.openehealth.ipf.commons ipf-commons-audit + + org.openehealth.ipf.commons + ipf-commons-ihe-fhir-r4-core + org.springframework.boot spring-boot-actuator diff --git a/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfiguration.java b/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfiguration.java index c2b1533552..4ed7cacbfd 100644 --- a/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfiguration.java +++ b/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfiguration.java @@ -16,12 +16,20 @@ package org.openehealth.ipf.boot.atna; -import org.openehealth.ipf.commons.audit.*; +import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.AuditMessagePostProcessor; +import org.openehealth.ipf.commons.audit.AuditMetadataProvider; +import org.openehealth.ipf.commons.audit.DefaultAuditContext; +import org.openehealth.ipf.commons.audit.DefaultAuditMetadataProvider; +import org.openehealth.ipf.commons.audit.DefaultBalpAuditContext; +import org.openehealth.ipf.commons.audit.TlsParameters; import org.openehealth.ipf.commons.audit.handler.AuditExceptionHandler; import org.openehealth.ipf.commons.audit.handler.LoggingAuditExceptionHandler; import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionChannel; import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; import org.openehealth.ipf.commons.audit.queue.AuditMessageQueue; +import org.openehealth.ipf.commons.ihe.fhir.support.audit.marshal.BalpJsonSerializationStrategy; +import org.openehealth.ipf.commons.ihe.fhir.support.audit.marshal.BalpXmlSerializationStrategy; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.actuate.security.AbstractAuthenticationAuditListener; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -31,6 +39,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + /** * */ @@ -48,7 +58,27 @@ public AuditContext auditContext(IpfAtnaConfigurationProperties config, AuditExceptionHandler auditExceptionHandler, AuditMessagePostProcessor auditMessagePostProcessor, @Value("${spring.application.name}") String appName) { - var auditContext = new DefaultAuditContext(); + if (config.getBalp() != null) { + return balpConfiguration(defaultContextConfiguration(new DefaultBalpAuditContext(), config, + auditTransmissionProtocol, auditMessageQueue, tlsParameters, auditMetadataProvider, + auditExceptionHandler, auditMessagePostProcessor, appName), config); + } else { + return defaultContextConfiguration(new DefaultAuditContext(), config, auditTransmissionProtocol, + auditMessageQueue, tlsParameters, auditMetadataProvider, auditExceptionHandler, + auditMessagePostProcessor, appName); + } + } + + private T defaultContextConfiguration(T auditContext, + IpfAtnaConfigurationProperties config, + AuditTransmissionProtocol auditTransmissionProtocol, + AuditMessageQueue auditMessageQueue, + TlsParameters tlsParameters, + AuditMetadataProvider auditMetadataProvider, + AuditExceptionHandler auditExceptionHandler, + AuditMessagePostProcessor auditMessagePostProcessor, + @Value("${spring.application.name}") String appName) { + auditContext.setAuditEnabled(config.isAuditEnabled()); // Simple properties @@ -59,7 +89,6 @@ public AuditContext auditContext(IpfAtnaConfigurationProperties config, auditContext.setAuditSource(config.getAuditSourceType()); auditContext.setIncludeParticipantsFromResponse(config.isIncludeParticipantsFromResponse()); auditContext.setAuditValueIfMissing(config.getAuditValueIfMissing()); - auditContext.setAuditRepositoryContextPath(config.getAuditRepositoryContextPath()); // Strategies and complex parameters; overrideable auditContext.setTlsParameters(tlsParameters); @@ -68,7 +97,63 @@ public AuditContext auditContext(IpfAtnaConfigurationProperties config, auditContext.setAuditMessageQueue(auditMessageQueue); auditContext.setAuditExceptionHandler(auditExceptionHandler); auditContext.setAuditMessagePostProcessor(auditMessagePostProcessor); + return auditContext; + } + private DefaultBalpAuditContext balpConfiguration(DefaultBalpAuditContext auditContext, IpfAtnaConfigurationProperties config) { + if (config.getBalp() != null) { + auditContext.setAuditRepositoryContextPath(config.getBalp().getAuditRepositoryContextPath()); + + if (isNotBlank(config.getBalp().getAuditEventSerializationType())) { + auditContext.setSerializationStrategy( + config.getBalp().getAuditEventSerializationType().equalsIgnoreCase("json") ? + new BalpJsonSerializationStrategy() : new BalpXmlSerializationStrategy()); + } + if (config.getBalp().getOauth() != null) { + if (config.getBalp().getOauth().getIdPath() != null) { + auditContext.getBalpJwtExtractorProperties().setIdPath(config.getBalp().getOauth().getIdPath()); + } + if (config.getBalp().getOauth().getClientIdPath() != null) { + auditContext.getBalpJwtExtractorProperties().setClientIdPath(config.getBalp().getOauth().getClientIdPath()); + } + if (config.getBalp().getOauth().getIssuerPath() != null) { + auditContext.getBalpJwtExtractorProperties().setIssuerPath(config.getBalp().getOauth().getIssuerPath()); + } + if (config.getBalp().getOauth().getSubjectPath() != null) { + auditContext.getBalpJwtExtractorProperties().setSubjectPath(config.getBalp().getOauth().getSubjectPath()); + } + if (config.getBalp().getOauth().getSubjectNamePath() != null) { + auditContext.getBalpJwtExtractorProperties().setSubjectNamePath(config.getBalp().getOauth().getSubjectNamePath()); + } + if (config.getBalp().getOauth().getSubjectRolePath() != null) { + auditContext.getBalpJwtExtractorProperties().setSubjectRolePath(config.getBalp().getOauth().getSubjectRolePath()); + } + if (config.getBalp().getOauth().getSubjectOrganizationIdPath() != null) { + auditContext.getBalpJwtExtractorProperties().setSubjectOrganizationIdPath(config.getBalp().getOauth().getSubjectOrganizationIdPath()); + } + if (config.getBalp().getOauth().getPurposeOfUsePath() != null) { + auditContext.getBalpJwtExtractorProperties().setPurposeOfUsePath(config.getBalp().getOauth().getPurposeOfUsePath()); + } + if (config.getBalp().getOauth().getHomeCommunityIdPath() != null) { + auditContext.getBalpJwtExtractorProperties().setHomeCommunityIdPath(config.getBalp().getOauth().getHomeCommunityIdPath()); + } + if (config.getBalp().getOauth().getNationalProviderIdPath() != null) { + auditContext.getBalpJwtExtractorProperties().setNationalProviderIdPath(config.getBalp().getOauth().getNationalProviderIdPath()); + } + if (config.getBalp().getOauth().getDocIdPath() != null) { + auditContext.getBalpJwtExtractorProperties().setDocIdPath(config.getBalp().getOauth().getDocIdPath()); + } + if (config.getBalp().getOauth().getPatientIdPath() != null) { + auditContext.getBalpJwtExtractorProperties().setPatientIdPath(config.getBalp().getOauth().getPatientIdPath()); + } + if (config.getBalp().getOauth().getPersonIdPath() != null) { + auditContext.getBalpJwtExtractorProperties().setPersonIdPath(config.getBalp().getOauth().getPersonIdPath()); + } + if (config.getBalp().getOauth().getAcpPath() != null) { + auditContext.getBalpJwtExtractorProperties().setAcpPath(config.getBalp().getOauth().getAcpPath()); + } + } + } return auditContext; } diff --git a/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaConfigurationProperties.java b/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaConfigurationProperties.java index 6316f5ddbf..13f485f1d2 100644 --- a/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaConfigurationProperties.java +++ b/boot/ipf-atna-spring-boot-starter/src/main/java/org/openehealth/ipf/boot/atna/IpfAtnaConfigurationProperties.java @@ -64,12 +64,6 @@ public class IpfAtnaConfigurationProperties { @Getter @Setter private int auditRepositoryPort = 514; - /** - * Sets the context-path of the BALP audit record repository. - */ - @Getter @Setter - private String auditRepositoryContextPath = ""; - /** * Enterprise Site Id */ @@ -106,4 +100,88 @@ public class IpfAtnaConfigurationProperties { @Getter @Setter private String auditValueIfMissing = "UNKNOWN"; + + @Getter @Setter + private Balp balp; + + public static class Balp { + + /** + * Sets the context-path of the BALP audit record repository. + */ + @Getter + @Setter + private String auditRepositoryContextPath = ""; + + @Getter + @Setter + private String auditEventSerializationType = "json"; + + @Getter + @Setter + private OAuth oauth; + + public static class OAuth { + + @Getter + @Setter + private String[] idPath; + + @Getter + @Setter + private String[] issuerPath; + + @Getter + @Setter + private String[] clientIdPath; + + @Getter + @Setter + private String[] subjectPath; + + @Getter + @Setter + private String[] subjectNamePath; + + @Getter + @Setter + private String[] subjectOrganizationPath; + + @Getter + @Setter + private String[] subjectOrganizationIdPath; + + @Getter + @Setter + private String[] subjectRolePath; + + @Getter + @Setter + private String[] purposeOfUsePath; + + @Getter + @Setter + private String[] homeCommunityIdPath; + + @Getter + @Setter + private String[] nationalProviderIdPath; + + @Getter + @Setter + private String[] personIdPath; + + @Getter + @Setter + private String[] patientIdPath; + + @Getter + @Setter + private String[] docIdPath; + + @Getter + @Setter + private String[] acpPath; + } + } } diff --git a/boot/ipf-atna-spring-boot-starter/src/test/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfigurationTest.java b/boot/ipf-atna-spring-boot-starter/src/test/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfigurationTest.java index 9e01553c81..565cac25f8 100644 --- a/boot/ipf-atna-spring-boot-starter/src/test/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfigurationTest.java +++ b/boot/ipf-atna-spring-boot-starter/src/test/java/org/openehealth/ipf/boot/atna/IpfAtnaAutoConfigurationTest.java @@ -44,7 +44,6 @@ public void testAtnaSettings() throws Exception { assertEquals("mysite", auditContext.getAuditEnterpriseSiteId()); assertEquals("localhost", auditContext.getAuditRepositoryHostName()); assertEquals(1342, auditContext.getAuditRepositoryPort()); - assertEquals("fhir", auditContext.getAuditRepositoryContextPath()); assertEquals("TLS", auditContext.getAuditTransmissionProtocol().getTransportName()); assertTrue(auditContext.getAuditMessageQueue() instanceof AsynchronousAuditMessageQueue); } diff --git a/boot/ipf-atna-spring-boot-starter/src/test/java/org/openehealth/ipf/boot/atna/IpfAtnaBalpAutoConfigurationTest.java b/boot/ipf-atna-spring-boot-starter/src/test/java/org/openehealth/ipf/boot/atna/IpfAtnaBalpAutoConfigurationTest.java new file mode 100644 index 0000000000..3dd1f9d999 --- /dev/null +++ b/boot/ipf-atna-spring-boot-starter/src/test/java/org/openehealth/ipf/boot/atna/IpfAtnaBalpAutoConfigurationTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openehealth.ipf.boot.atna; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.BalpAuditContext; +import org.openehealth.ipf.commons.audit.queue.AsynchronousAuditMessageQueue; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/** + * + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest(classes = { TestApplication.class }) +@ActiveProfiles("balp") +public class IpfAtnaBalpAutoConfigurationTest { + + @Autowired + private AuditContext auditContext; + + @Test + public void testAtnaWithBalpSettings() throws Exception { + assertTrue(auditContext instanceof BalpAuditContext); + + assertEquals("atna-test", auditContext.getAuditSourceId()); + assertEquals("mysite", auditContext.getAuditEnterpriseSiteId()); + assertEquals("localhost", auditContext.getAuditRepositoryHostName()); + assertEquals(1342, auditContext.getAuditRepositoryPort()); + assertEquals("FHIR-REST-TLS", auditContext.getAuditTransmissionProtocol().getTransportName()); + assertTrue(auditContext.getAuditMessageQueue() instanceof AsynchronousAuditMessageQueue); + + assertEquals("fhir", ((BalpAuditContext)auditContext).getAuditRepositoryContextPath()); + assertArrayEquals(new String[]{"cid","client-id","my-client-id-path"}, + ((BalpAuditContext) auditContext).getBalpJwtExtractorProperties().getClientIdPath()); + assertArrayEquals(new String[]{}, + ((BalpAuditContext) auditContext).getBalpJwtExtractorProperties().getIdPath()); + } + +} diff --git a/boot/ipf-atna-spring-boot-starter/src/test/resources/application-balp.yml b/boot/ipf-atna-spring-boot-starter/src/test/resources/application-balp.yml new file mode 100644 index 0000000000..43bc65e0eb --- /dev/null +++ b/boot/ipf-atna-spring-boot-starter/src/test/resources/application-balp.yml @@ -0,0 +1,8 @@ +ipf: + atna: + audit-repository-transport: FHIR-REST-TLS + balp: + audit-repository-context-path: fhir + oauth: + clientIdPath: cid,client-id,my-client-id-path + idPath: \ No newline at end of file diff --git a/boot/ipf-atna-spring-boot-starter/src/test/resources/application.yml b/boot/ipf-atna-spring-boot-starter/src/test/resources/application.yml index 67a790bfc3..e79bc13a3c 100644 --- a/boot/ipf-atna-spring-boot-starter/src/test/resources/application.yml +++ b/boot/ipf-atna-spring-boot-starter/src/test/resources/application.yml @@ -7,7 +7,6 @@ ipf: audit-enabled: false audit-repository-host: localhost audit-repository-port: 1342 - audit-repository-context-path: fhir audit-repository-transport: TLS audit-enterprise-site-id: mysite audit-queue-class: org.openehealth.ipf.commons.audit.queue.AsynchronousAuditMessageQueue diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/AuditContext.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/AuditContext.java index 3517603100..f1df3629d8 100644 --- a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/AuditContext.java +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/AuditContext.java @@ -165,8 +165,6 @@ default String getAuditValueIfMissing() { */ boolean isIncludeParticipantsFromResponse(); - String getAuditRepositoryContextPath(); - static AuditContext noAudit() { return DefaultAuditContext.NO_AUDIT; } diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/BalpAuditContext.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/BalpAuditContext.java new file mode 100644 index 0000000000..ba2ac1307c --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/BalpAuditContext.java @@ -0,0 +1,27 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit; + +/** + * @author Boris Stanojevic + * @since 4.8 + */ +public interface BalpAuditContext extends AuditContext { + + String getAuditRepositoryContextPath(); + + BalpJwtExtractorProperties getBalpJwtExtractorProperties(); +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/BalpJwtExtractorProperties.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/BalpJwtExtractorProperties.java new file mode 100644 index 0000000000..07860ee166 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/BalpJwtExtractorProperties.java @@ -0,0 +1,68 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit; + +import lombok.Getter; +import lombok.Setter; + +public class BalpJwtExtractorProperties { + + @Getter + @Setter + private String[] idPath = new String[]{"jti"}; + + @Getter @Setter + private String[] issuerPath = new String[]{"iss"}; + + @Getter @Setter + private String[] clientIdPath = new String[]{"client_id", "cid"}; + + @Getter @Setter + private String[] subjectPath = new String[]{"sub"}; + + @Getter @Setter + private String[] subjectNamePath = new String[]{"extensions:ihe_iua:subject_name"}; + + @Getter @Setter + private String[] subjectOrganizationPath = new String[]{"extensions:ihe_iua:subject_organization"}; + + @Getter @Setter + private String[] subjectOrganizationIdPath = new String[]{"extensions:ihe_iua:subject_organization_id"}; + + @Getter @Setter + private String[] subjectRolePath = new String[]{"extensions:ihe_iua:subject_role"}; + + @Getter @Setter + private String[] purposeOfUsePath = new String[]{"extensions:ihe_iua:purpose_of_use"}; + + @Getter @Setter + private String[] homeCommunityIdPath = new String[]{"extensions:ihe_iua:home_community_id"}; + + @Getter @Setter + private String[] nationalProviderIdPath = new String[]{"extensions:ihe_iua:national_provider_identifier"}; + + @Getter @Setter + private String[] personIdPath = new String[]{"extensions:ihe_iua:person_id"}; + + @Getter @Setter + private String[] patientIdPath = new String[]{"extensions:ihe_bppc:patient_id"}; + + @Getter @Setter + private String[] docIdPath = new String[]{"extensions:ihe_bppc:doc_id"}; + + @Getter @Setter + private String[] acpPath = new String[]{"extensions:ihe_bppc:acp"}; +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultAuditContext.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultAuditContext.java index d1381bd806..f559e80f1a 100644 --- a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultAuditContext.java +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultAuditContext.java @@ -101,10 +101,6 @@ public class DefaultAuditContext implements AuditContext { @Setter private String auditValueIfMissing = "UNKNOWN"; - @Getter - @Setter - private String auditRepositoryContextPath = ""; - public String getAuditRepositoryTransport() { return auditTransmissionProtocol.getTransportName(); } diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultBalpAuditContext.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultBalpAuditContext.java new file mode 100644 index 0000000000..52275131bc --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultBalpAuditContext.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit; + +import lombok.Getter; +import lombok.Setter; + +public class DefaultBalpAuditContext extends DefaultAuditContext implements BalpAuditContext { + + @Getter + @Setter + private String auditRepositoryContextPath; + + @Getter + @Setter + private BalpJwtExtractorProperties balpJwtExtractorProperties = new BalpJwtExtractorProperties(); +} + diff --git a/commons/ihe/fhir/core/pom.xml b/commons/ihe/fhir/core/pom.xml index 39e6bb4c72..99b4bd3057 100644 --- a/commons/ihe/fhir/core/pom.xml +++ b/commons/ihe/fhir/core/pom.xml @@ -41,6 +41,10 @@ org.openehealth.ipf.commons ipf-commons-map + + com.nimbusds + nimbus-jose-jwt + com.github.mizosoft.methanol methanol diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/Constants.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/Constants.java index 7f82d31ebc..e0f5b21578 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/Constants.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/Constants.java @@ -32,6 +32,7 @@ public interface Constants { String FHIR_REQUEST_DETAILS = "FhirRequestDetails"; // Parameter information from the HttpServletRequest + String HTTP_AUTHORIZATION = "Authorization"; String HTTP_URI = "FhirHttpUri"; String HTTP_URL = "FhirHttpUrl"; String HTTP_SCHEME = "FhirHttpScheme"; diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/AbstractFhirAuditStrategy.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/AbstractFhirAuditStrategy.java index 4070198dcb..745806f0bb 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/AbstractFhirAuditStrategy.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/AbstractFhirAuditStrategy.java @@ -25,7 +25,11 @@ import java.util.Map; -import static org.openehealth.ipf.commons.ihe.fhir.Constants.*; +import static org.openehealth.ipf.commons.ihe.fhir.Constants.FHIR_CONTEXT; +import static org.openehealth.ipf.commons.ihe.fhir.Constants.HTTP_AUTHORIZATION; +import static org.openehealth.ipf.commons.ihe.fhir.Constants.HTTP_CLIENT_IP_ADDRESS; +import static org.openehealth.ipf.commons.ihe.fhir.Constants.HTTP_URI; +import static org.openehealth.ipf.commons.ihe.fhir.Constants.HTTP_URL; /** * Generic Audit Strategy for FHIR transactions @@ -55,6 +59,9 @@ public T enrichAuditDatasetFromRequest(T auditDataset, Object request, Map extractId(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getIdPath())); + } + + public Optional extractClientId(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getClientIdPath())); + } + + public Optional extractSubject(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getSubjectPath())); + } + + public Optional extractIssuer(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getIssuerPath())); + } + + public Optional extractSubjectName(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getSubjectNamePath())); + } + + public Optional extractSubjectOrganization(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getSubjectOrganizationPath())); + } + + public Optional extractSubjectOrganizationId(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getSubjectOrganizationIdPath())); + } + + public Optional> extractSubjectRole(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractListClaimFromJWT(jwt, balpJwtExtractorProperties.getSubjectRolePath())); + } + + public Optional> extractPurposeOfUse(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractListClaimFromJWT(jwt, balpJwtExtractorProperties.getPurposeOfUsePath())); + } + + public Optional extractHomeCommunityId(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getHomeCommunityIdPath())); + } + + public Optional extractNationalProviderIdentifier(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getNationalProviderIdPath())); + } + + public Optional extractPersonId(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getPersonIdPath())); + } + + public Optional extractBppcPatientId(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getPatientIdPath())); + } + + public Optional extractBppcDocId(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getDocIdPath())); + } + + public Optional extractBppcAcp(JWT jwt, BalpJwtExtractorProperties balpJwtExtractorProperties) { + return Optional.ofNullable(extractStringClaimFromJWT(jwt, balpJwtExtractorProperties.getAcpPath())); + } + + private String extractStringClaimFromJWT(JWT jwt, String[] expressions){ + Optional finalClaimForExpression = getFinalClaimSet(jwt, expressions); + if (finalClaimForExpression.isPresent()) { + JWTClaimsSet claimsSet = finalClaimForExpression.get().getJwtClaimsSet(); + String expression = finalClaimForExpression.get().getExpression(); + try { + return claimsSet.getStringClaim(expression); + } catch (ParseException pe) { + LOG.warn("Not string claims present for expression key '" + expression + "'", pe); + } + } + return null; + } + + private Set extractListClaimFromJWT(JWT jwt, String[] expressions){ + Optional finalClaimForExpression = getFinalClaimSet(jwt, expressions); + if (finalClaimForExpression.isPresent()) { + JWTClaimsSet claimsSet = finalClaimForExpression.get().getJwtClaimsSet(); + String expression = finalClaimForExpression.get().getExpression(); + try { + List values = claimsSet.getListClaim(expression); + if (values != null && !values.isEmpty()) { + return values.stream().map(Objects::toString).collect(Collectors.toSet()); + } + } catch (ParseException pe) { + LOG.warn("Not list claims present for expression key '" + expression + "'", pe); + } + } + return null; + } + + private Optional getFinalClaimSet(JWT jwt, String[] expressions) { + if (expressions == null) { + return Optional.empty(); + } + for (var expression: expressions) { + try { + if (expression.contains(":")) { + JWTClaimsSet extracted = jwt.getJWTClaimsSet(); + List structure = List.of(expression.split("\\:")); + Iterator structureIterator = structure.listIterator(); + String subExpression = null; + while (structureIterator.hasNext()) { + subExpression = structureIterator.next(); + if (structureIterator.hasNext()) { + if (!containsClaim(extracted, subExpression)) { + break; + } + extracted = JWTClaimsSet.parse(extracted.getJSONObjectClaim(subExpression)); + } + } + if (extracted != null && isNotBlank(subExpression) && + extracted.getClaims().containsKey(subExpression)) { + return Optional.of(new ClaimSetPair(subExpression, extracted)); + } + } else { + if (jwt.getJWTClaimsSet().getClaims().containsKey(expression)) { + return Optional.of(new ClaimSetPair(expression, jwt.getJWTClaimsSet())); + } + } + } catch (ParseException pe) { + LOG.debug("Not claimset present for expression key: " + pe.getMessage()); + } + } + return Optional.empty(); + } + + private boolean containsClaim(JWTClaimsSet claimsSet, String name) { + return claimsSet.getClaim(name) != null; + } + + private static final class ClaimSetPair { + @Getter + private final String expression; + @Getter + private final JWTClaimsSet jwtClaimsSet; + + public ClaimSetPair(String expression, JWTClaimsSet jwtClaimsSet) { + this.expression = expression; + this.jwtClaimsSet = jwtClaimsSet; + } + } + +} diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtDataSet.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtDataSet.java new file mode 100644 index 0000000000..b81ee7b07a --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtDataSet.java @@ -0,0 +1,27 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.auth; + +import lombok.Data; + +import java.util.Set; + +@Data +public class BalpJwtDataSet { + + String opaqueJwt; + String issuer; + String subject; + String audience; + String jwtId; + String clientId; + String iheIuaSubjectName; + String iheIuaSubjectOrganization; + String iheIuaSubjectOrganizationId; + Set iheIuaSubjectRole; + Set iheIuaPurposeOfUse; + String iheIuaHomeCommunityId; + String iheIuaNationalProviderIdentifier; + String iheIuaPersonId; + String iheBppcPatientId; + String iheBppcDocId; + String iheBppcAcp; +} diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtParser.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtParser.java new file mode 100644 index 0000000000..fd2d438ade --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtParser.java @@ -0,0 +1,68 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.auth; + +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; +import org.openehealth.ipf.commons.audit.BalpJwtExtractorProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.text.ParseException; +import java.util.Optional; + +import static org.apache.commons.lang3.StringUtils.isBlank; + +public class BalpJwtParser { + + private static final BalpJwtClaimsExtractor claimsExtractor = new BalpJwtClaimsExtractor(); + + private static final Logger LOG = LoggerFactory.getLogger(BalpJwtParser.class); + + public static Optional parseAuthorizationToBalpDataSet(String authenticationHeader, + BalpJwtExtractorProperties balpJwtExtractorProperties) { + Optional jwt = parseAuthenticationToJWT(authenticationHeader); + return jwt.map(value -> { + BalpJwtDataSet balpJwtDataSet = parseJwtToBalpDataSet(value, claimsExtractor, balpJwtExtractorProperties); + balpJwtDataSet.setOpaqueJwt(authenticationHeader.substring(authenticationHeader.length() - 32)); + return balpJwtDataSet; + }); + } + + public static Optional parseAuthenticationToJWT(String authenticationHeader) { + if (isBlank(authenticationHeader) || + !authenticationHeader.toLowerCase().startsWith("bearer ")) return Optional.empty(); + + String bearer = authenticationHeader.replaceAll("^[Bb][Ee][Aa][Rr][Ee][Rr][ ]+", ""); + try { + return Optional.of(JWTParser.parse(bearer)); + } catch (ParseException pe) { + LOG.debug("Invalid JWT token", pe); + return Optional.empty(); + } + } + + public static BalpJwtDataSet parseJwtToBalpDataSet(JWT jwt, + BalpJwtClaimsExtractor claimsExtractor, + BalpJwtExtractorProperties balpJwtExtractorProperties) { + BalpJwtDataSet balpJwtDataSet = new BalpJwtDataSet(); + + claimsExtractor.extractIssuer(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setIssuer); + claimsExtractor.extractId(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setJwtId); + claimsExtractor.extractClientId(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setClientId); + claimsExtractor.extractSubject(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setSubject); + + claimsExtractor.extractBppcAcp(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setIheBppcAcp); + claimsExtractor.extractBppcDocId(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setIheBppcDocId); + claimsExtractor.extractBppcPatientId(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setIheBppcPatientId); + + claimsExtractor.extractSubjectName(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setIheIuaSubjectName); + claimsExtractor.extractSubjectOrganization(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setIheIuaSubjectOrganization); + claimsExtractor.extractSubjectOrganizationId(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setIheIuaSubjectOrganizationId); + claimsExtractor.extractSubjectRole(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setIheIuaSubjectRole); + claimsExtractor.extractHomeCommunityId(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setIheIuaHomeCommunityId); + claimsExtractor.extractPurposeOfUse(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setIheIuaPurposeOfUse); + claimsExtractor.extractNationalProviderIdentifier(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setIheIuaNationalProviderIdentifier); + claimsExtractor.extractPersonId(jwt, balpJwtExtractorProperties).ifPresent(balpJwtDataSet::setIheIuaPersonId); + + return balpJwtDataSet; + } +} diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/codes/Constants.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/codes/Constants.java index e9b1812e98..b10d5c9307 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/codes/Constants.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/codes/Constants.java @@ -22,10 +22,15 @@ public class Constants { public static final String EHS_SYSTEM_NAME = "e-health-suisse"; public static final String DCM_SYSTEM_NAME = "http://dicom.nema.org/resources/ontology/DCM"; + public static final String DCM_OCLIENT_CODE = "110150"; public static final String SECURITY_SOURCE_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/security-source-type"; public static final String AUDIT_ENTITY_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/audit-entity-type"; public static final String OBJECT_ROLE_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/object-role"; public static final String AUDIT_LIFECYCLE_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/dicom-audit-lifecycle"; - - + public static final String OUSER_AGENT_TYPE_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/v3-ParticipationType"; + public static final String OUSER_AGENT_TYPE_CODE = "IRCP"; + public static final String OUSER_AGENT_PURPOSE_OF_USE_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/v3-ActReason"; + public static final String OUSER_AGENT_ROLE_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/v3-RoleClass"; + public static final String OUSER_AGENT_TYPE_OPAQUE_SYSTEM_NAME = "https://profiles.ihe.net/ITI/BALP/CodeSystem/UserAgentTypes"; + public static final String OUSER_AGENT_TYPE_OPAQUE_CODE = "UserOauthAgent"; } diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpJwtUtils.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpJwtUtils.java new file mode 100644 index 0000000000..64929d1349 --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpJwtUtils.java @@ -0,0 +1,92 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.ihe.fhir.audit.events; + +import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.BalpAuditContext; +import org.openehealth.ipf.commons.audit.BalpJwtExtractorProperties; +import org.openehealth.ipf.commons.audit.DefaultBalpAuditContext; +import org.openehealth.ipf.commons.audit.event.BaseAuditMessageBuilder; +import org.openehealth.ipf.commons.audit.model.ActiveParticipantType; +import org.openehealth.ipf.commons.audit.types.ActiveParticipantRoleId; +import org.openehealth.ipf.commons.audit.types.CodedValueType; +import org.openehealth.ipf.commons.ihe.fhir.audit.FhirAuditDataset; +import org.openehealth.ipf.commons.ihe.fhir.audit.auth.BalpJwtDataSet; +import org.openehealth.ipf.commons.ihe.fhir.audit.auth.BalpJwtParser; + +import java.util.Optional; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.openehealth.ipf.commons.ihe.fhir.audit.codes.Constants.DCM_SYSTEM_NAME; +import static org.openehealth.ipf.commons.ihe.fhir.audit.codes.Constants.OUSER_AGENT_PURPOSE_OF_USE_SYSTEM_NAME; +import static org.openehealth.ipf.commons.ihe.fhir.audit.codes.Constants.OUSER_AGENT_ROLE_SYSTEM_NAME; +import static org.openehealth.ipf.commons.ihe.fhir.audit.codes.Constants.OUSER_AGENT_TYPE_OPAQUE_SYSTEM_NAME; +import static org.openehealth.ipf.commons.ihe.fhir.audit.codes.Constants.OUSER_AGENT_TYPE_SYSTEM_NAME; + +public class BalpJwtUtils { + + private static final BalpJwtExtractorProperties DEFAULT_BALP_JWT_EXTRACTOR_PROPERTIES = new BalpJwtExtractorProperties(); + + public static > void addJwtParticipant(D delegate, + FhirAuditDataset auditDataset, + AuditContext auditContext) { + BalpJwtExtractorProperties balpJwtExtractorProperties = (auditContext instanceof BalpAuditContext)? + ((BalpAuditContext)auditContext).getBalpJwtExtractorProperties() : DEFAULT_BALP_JWT_EXTRACTOR_PROPERTIES; + Optional balpDataSet = BalpJwtParser.parseAuthorizationToBalpDataSet( + auditDataset.getAuthorization(), balpJwtExtractorProperties); + balpDataSet.ifPresent(dataSet -> { + if (isNotBlank(dataSet.getJwtId())) { + ActiveParticipantType ap = new ActiveParticipantType(dataSet.getSubject(), true); + ap.getRoleIDCodes().add( + ActiveParticipantRoleId.of(CodedValueType.of(dataSet.getJwtId(), + OUSER_AGENT_TYPE_SYSTEM_NAME, "oAuth Token ID"))); + ap.setUserName(dataSet.getIheIuaSubjectName()); + if (isNotBlank(dataSet.getIssuer())) { + ap.setAlternativeUserID(dataSet.getIssuer()); + } + if (dataSet.getIheIuaPurposeOfUse() != null && !dataSet.getIheIuaPurposeOfUse().isEmpty()) { + dataSet.getIheIuaPurposeOfUse().forEach(purpose -> ap.getRoleIDCodes().add( + ActiveParticipantRoleId.of(CodedValueType.of(purpose, + OUSER_AGENT_PURPOSE_OF_USE_SYSTEM_NAME, "oAuth Token Purpose of Use")))); + } + if (dataSet.getIheIuaSubjectRole() != null && !dataSet.getIheIuaSubjectRole().isEmpty()) { + dataSet.getIheIuaSubjectRole().forEach(role -> ap.getRoleIDCodes().add( + ActiveParticipantRoleId.of(CodedValueType.of(role, + OUSER_AGENT_ROLE_SYSTEM_NAME, "oAuth Token User Role")))); + } + delegate.addActiveParticipant(ap); + if (isNotBlank(dataSet.getClientId())) { + ActiveParticipantType clientAp = new ActiveParticipantType( + dataSet.getClientId(), !auditDataset.isServerSide()); + clientAp.getRoleIDCodes().add( + ActiveParticipantRoleId.of(CodedValueType.of(dataSet.getClientId(), + DCM_SYSTEM_NAME, "oAuth Token Client ID"))); + delegate.addActiveParticipant(clientAp); + } + } else if (isNotBlank(dataSet.getOpaqueJwt())) { + ActiveParticipantType ap = new ActiveParticipantType(dataSet.getSubject(), true); + ap.getRoleIDCodes().add( + ActiveParticipantRoleId.of(CodedValueType.of(dataSet.getOpaqueJwt(), + OUSER_AGENT_TYPE_OPAQUE_SYSTEM_NAME, "oAuth Opaque Token"))); + } + }); + } + + private BalpAuditContext balpAuditContext(AuditContext auditContext) { + return auditContext instanceof BalpAuditContext? (BalpAuditContext) auditContext : null; + } + +} \ No newline at end of file diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpPHIExportBuilder.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpPHIExportBuilder.java new file mode 100644 index 0000000000..7ed7285a46 --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpPHIExportBuilder.java @@ -0,0 +1,47 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.events; + +import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.BalpAuditContext; +import org.openehealth.ipf.commons.audit.codes.EventActionCode; +import org.openehealth.ipf.commons.audit.codes.EventOutcomeIndicator; +import org.openehealth.ipf.commons.audit.types.EventType; +import org.openehealth.ipf.commons.audit.types.PurposeOfUse; +import org.openehealth.ipf.commons.ihe.core.atna.event.PHIExportBuilder; +import org.openehealth.ipf.commons.ihe.fhir.audit.FhirAuditDataset; + +import static org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpJwtUtils.addJwtParticipant; + +public class BalpPHIExportBuilder extends PHIExportBuilder { + + public BalpPHIExportBuilder(AuditContext auditContext, + FhirAuditDataset auditDataset, + EventType eventType, + PurposeOfUse... purposesOfUse) { + super(auditContext, auditDataset, eventType, purposesOfUse); + addJwtId(auditDataset); + } + + public BalpPHIExportBuilder(AuditContext auditContext, + FhirAuditDataset auditDataset, + EventActionCode eventActionCode, + EventType eventType, + PurposeOfUse... purposesOfUse) { + super(auditContext, auditDataset, eventActionCode, eventType, purposesOfUse); + addJwtId(auditDataset); + } + + public BalpPHIExportBuilder(AuditContext auditContext, + FhirAuditDataset auditDataset, + EventOutcomeIndicator eventOutcomeIndicator, + String eventOutcomeDescription, + EventActionCode eventActionCode, + EventType eventType, + PurposeOfUse... purposesOfUse) { + super(auditContext, auditDataset, eventOutcomeIndicator, eventOutcomeDescription, eventActionCode, eventType, purposesOfUse); + addJwtId(auditDataset); + } + + public void addJwtId(FhirAuditDataset auditDataset) { + addJwtParticipant(delegate, auditDataset, getAuditContext()); + } +} diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpPHIImportBuilder.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpPHIImportBuilder.java new file mode 100644 index 0000000000..bb9221cbfc --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpPHIImportBuilder.java @@ -0,0 +1,41 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.events; + +import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.codes.EventActionCode; +import org.openehealth.ipf.commons.audit.codes.EventOutcomeIndicator; +import org.openehealth.ipf.commons.audit.types.EventType; +import org.openehealth.ipf.commons.audit.types.PurposeOfUse; +import org.openehealth.ipf.commons.ihe.core.atna.event.PHIImportBuilder; +import org.openehealth.ipf.commons.ihe.fhir.audit.FhirAuditDataset; + +import static org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpJwtUtils.addJwtParticipant; + +public class BalpPHIImportBuilder extends PHIImportBuilder { + + + public BalpPHIImportBuilder(AuditContext auditContext, + FhirAuditDataset auditDataset, + EventType eventType, + PurposeOfUse... purposesOfUse) { + super(auditContext, auditDataset, eventType, purposesOfUse); + addJwtId(auditDataset); + } + + public BalpPHIImportBuilder(AuditContext auditContext, + FhirAuditDataset auditDataset, + EventActionCode eventActionCode, + EventType eventType, + PurposeOfUse... purposesOfUse) { + super(auditContext, auditDataset, eventActionCode, eventType, purposesOfUse); + addJwtId(auditDataset); + } + + public BalpPHIImportBuilder(AuditContext auditContext, FhirAuditDataset auditDataset, EventOutcomeIndicator eventOutcomeIndicator, String eventOutcomeDescription, EventActionCode eventActionCode, EventType eventType, PurposeOfUse... purposesOfUse) { + super(auditContext, auditDataset, eventOutcomeIndicator, eventOutcomeDescription, eventActionCode, eventType, purposesOfUse); + addJwtId(auditDataset); + } + + public void addJwtId(FhirAuditDataset auditDataset) { + addJwtParticipant(delegate, auditDataset, getAuditContext()); + } +} diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpQueryInformationBuilder.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpQueryInformationBuilder.java new file mode 100644 index 0000000000..f78cfd02bd --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/BalpQueryInformationBuilder.java @@ -0,0 +1,24 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.events; + +import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.types.EventType; +import org.openehealth.ipf.commons.audit.types.PurposeOfUse; +import org.openehealth.ipf.commons.ihe.core.atna.event.QueryInformationBuilder; +import org.openehealth.ipf.commons.ihe.fhir.audit.FhirAuditDataset; + +import static org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpJwtUtils.addJwtParticipant; + +public class BalpQueryInformationBuilder extends QueryInformationBuilder { + + public BalpQueryInformationBuilder(AuditContext auditContext, + FhirAuditDataset auditDataset, + EventType eventType, + PurposeOfUse... purposesOfUse) { + super(auditContext, auditDataset, eventType, purposesOfUse); + addJwtId(auditDataset); + } + + public void addJwtId(FhirAuditDataset auditDataset) { + addJwtParticipant(delegate, auditDataset, getAuditContext()); + } +} \ No newline at end of file diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/GenericFhirAuditMessageBuilder.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/GenericFhirAuditMessageBuilder.java index baa9dc78bd..81f68afc50 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/GenericFhirAuditMessageBuilder.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/events/GenericFhirAuditMessageBuilder.java @@ -132,6 +132,11 @@ public GenericFhirAuditMessageBuilder addResourceParticipantObject(GenericFhirAu return self(); } + public GenericFhirAuditMessageBuilder addJwtParticipants(GenericFhirAuditDataset auditDataset) { + BalpJwtUtils.addJwtParticipant(delegate, auditDataset, getAuditContext()); + return self(); + } + private static EventActionCode eventActionCode(RestOperationTypeEnum operation) { if (operation == null) diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSAuditRecordSender.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSAuditRecordSender.java index 3dee3309dd..0df47d13be 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSAuditRecordSender.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSAuditRecordSender.java @@ -21,6 +21,7 @@ import ca.uhn.fhir.rest.client.impl.RestfulClientFactory; import org.openehealth.ipf.commons.audit.AuditContext; import org.openehealth.ipf.commons.audit.AuditMetadataProvider; +import org.openehealth.ipf.commons.audit.BalpAuditContext; import org.openehealth.ipf.commons.audit.FhirContextHolder; import org.openehealth.ipf.commons.audit.TlsParameters; import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionChannel; @@ -78,7 +79,8 @@ public void send(AuditContext auditContext, String baseUrl = String.format(BASE_URL_FORMAT, auditContext.getAuditRepositoryHostName(), auditContext.getAuditRepositoryPort(), - auditContext.getAuditRepositoryContextPath()); + (auditContext instanceof BalpAuditContext)? + ((BalpAuditContext)auditContext).getAuditRepositoryContextPath() : ""); createClient(baseUrl); } MethodOutcome outcome = client diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtClaimsExtractorTest.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtClaimsExtractorTest.java new file mode 100644 index 0000000000..dfd0bd7ff6 --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtClaimsExtractorTest.java @@ -0,0 +1,108 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.auth; + +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; +import org.junit.jupiter.api.Test; +import org.openehealth.ipf.commons.audit.BalpJwtExtractorProperties; + +import java.text.ParseException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BalpJwtClaimsExtractorTest { + + private final BalpJwtClaimsExtractor balpJwtClaimsExtractor = new BalpJwtClaimsExtractor(); + private final BalpJwtExtractorProperties balpJwtExtractorProperties = new BalpJwtExtractorProperties(); + + private final BalpJwtGenerator balpJwtGenerator = new BalpJwtGenerator(); + + @Test + void testExtractor() throws Exception { + String generatedJwt = balpJwtGenerator.next(); + + JWT jwt = parseJWT(generatedJwt); + assertNotNull(jwt); + + assertTrue(balpJwtClaimsExtractor.extractIssuer(jwt, balpJwtExtractorProperties).isPresent()); + assertEquals("https://localhost:8443/auth/realms/master", balpJwtClaimsExtractor.extractIssuer(jwt, balpJwtExtractorProperties).get()); + + assertTrue(balpJwtClaimsExtractor.extractId(jwt, balpJwtExtractorProperties).isPresent()); + assertTrue(balpJwtClaimsExtractor.extractSubject(jwt, balpJwtExtractorProperties).isPresent()); + assertTrue(balpJwtClaimsExtractor.extractClientId(jwt, balpJwtExtractorProperties).isPresent()); + + assertTrue(balpJwtClaimsExtractor.extractPersonId(jwt, balpJwtExtractorProperties).isPresent()); + assertTrue(balpJwtClaimsExtractor.extractHomeCommunityId(jwt, balpJwtExtractorProperties).isPresent()); + assertTrue(balpJwtClaimsExtractor.extractNationalProviderIdentifier(jwt, balpJwtExtractorProperties).isPresent()); + + assertTrue(balpJwtClaimsExtractor.extractSubjectName(jwt, balpJwtExtractorProperties).isPresent()); + assertEquals("Dr. John Smith", balpJwtClaimsExtractor.extractSubjectName(jwt, balpJwtExtractorProperties).get()); + assertTrue(balpJwtClaimsExtractor.extractSubjectOrganization(jwt, balpJwtExtractorProperties).isPresent()); + assertTrue(balpJwtClaimsExtractor.extractSubjectOrganizationId(jwt, balpJwtExtractorProperties).isPresent()); + assertTrue(balpJwtClaimsExtractor.extractSubjectRole(jwt, balpJwtExtractorProperties).isPresent()); + assertEquals(2, balpJwtClaimsExtractor.extractSubjectRole(jwt, balpJwtExtractorProperties).get().size()); + assertTrue(balpJwtClaimsExtractor.extractSubjectRole(jwt, balpJwtExtractorProperties).get().contains("my-role-1")); + assertTrue(balpJwtClaimsExtractor.extractSubjectRole(jwt, balpJwtExtractorProperties).get().contains("my-role-2")); + assertTrue(balpJwtClaimsExtractor.extractPurposeOfUse(jwt, balpJwtExtractorProperties).isPresent()); + assertEquals(2, balpJwtClaimsExtractor.extractPurposeOfUse(jwt, balpJwtExtractorProperties).get().size()); + + assertTrue(balpJwtClaimsExtractor.extractBppcAcp(jwt, balpJwtExtractorProperties).isPresent()); + assertTrue(balpJwtClaimsExtractor.extractBppcDocId(jwt, balpJwtExtractorProperties).isPresent()); + assertTrue(balpJwtClaimsExtractor.extractBppcPatientId(jwt, balpJwtExtractorProperties).isPresent()); + + balpJwtExtractorProperties.setIssuerPath(new String[]{"blah"}); + balpJwtExtractorProperties.setAcpPath(new String[]{"extensions:ihe_blah"}); + assertTrue(balpJwtClaimsExtractor.extractIssuer(jwt, balpJwtExtractorProperties).isEmpty()); + assertTrue(balpJwtClaimsExtractor.extractBppcAcp(jwt, balpJwtExtractorProperties).isEmpty()); + + balpJwtExtractorProperties.setAcpPath(null); + assertTrue(balpJwtClaimsExtractor.extractBppcAcp(jwt, balpJwtExtractorProperties).isEmpty()); + + balpJwtExtractorProperties.setAcpPath(new String[]{""}); + assertTrue(balpJwtClaimsExtractor.extractBppcAcp(jwt, balpJwtExtractorProperties).isEmpty()); + + } + + private JWT parseJWT(String jwt) { + try { + return JWTParser.parse(jwt); + } catch (ParseException pe) { + return null; + } + } + + private static final String jwtAsString = "{\n" + + " \"aud\": \"master-realm\",\n" + + " \"sub\": \"f7fc9091-7b8a-42e0-a829-6c4ba22d38b2\",\n" + + " \"extensions\": {\n" + + " \"ihe_iua\": {\n" + + " \"subject_organization_id\": \"urn:oid:1.2.3.19161\",\n" + + " \"home_community_id\": \"urn:oid:1.2.3.43740\",\n" + + " \"national_provider_identifier\": \"urn:oid:1.2.3.48200\",\n" + + " \"subject_role\": [\n" + + " \"my-role-1\",\n" + + " \"my-role-2\"\n" + + " ],\n" + + " \"purpose_of_use\": [\n" + + " \"1.0.14265.1\",\n" + + " \"1.0.14265.2\"\n" + + " ],\n" + + " \"subject_name\": \"Dr. John Smith\",\n" + + " \"subject_organization\": \"Central Hospital\",\n" + + " \"person_id\": \"ABC9586\"\n" + + " },\n" + + " \"ihe_bppc\": {\n" + + " \"patient_id\": \"31494^^^&1.2.840.113619.6.197&ISO\",\n" + + " \"doc_id\": \"urn:oid:1.2.3.29380\",\n" + + " \"acp\": \"urn:oid:1.2.3.32574\"\n" + + " }\n" + + " },\n" + + " \"nbf\": 1706531233,\n" + + " \"iss\": \"https://localhost:8443/auth/realms/master\",\n" + + " \"typ\": \"Bearer\",\n" + + " \"exp\": 1706531353,\n" + + " \"jti\": \"e2093a98-9dcd-4947-b5cb-ee5b47c089c5\",\n" + + " \"client_id\": \"pbrBkyXksp\"\n" + + "}"; +} diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtGenerator.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtGenerator.java new file mode 100644 index 0000000000..782292aa97 --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/auth/BalpJwtGenerator.java @@ -0,0 +1,105 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.auth; + +import com.nimbusds.jose.JOSEObjectType; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import net.java.quickcheck.Generator; +import net.java.quickcheck.generator.PrimitiveGenerators; + +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class BalpJwtGenerator implements Generator { + + private static final Generator strings = PrimitiveGenerators.letterStrings(10, 10); + + private static final Generator integers = PrimitiveGenerators.integers(1000, 99999); + + @Override + public String next() { + try { + RSAKey jwk = new RSAKeyGenerator(2048) + .keyUse(KeyUse.SIGNATURE) + .keyID(UUID.randomUUID().toString()) + .generate(); + + SignedJWT signedJWT = new SignedJWT(jwsHeader(jwk), jwsClaimsSet()); + signedJWT.sign(new RSASSASigner(jwk.toPrivateKey())); + return signedJWT.serialize(); + } catch (Exception e) { + return null; + } + } + + public static String anyValidJwtWithRSAKey(RSAKey rsaKey){ + try { + SignedJWT signedJWT = new SignedJWT(jwsHeader(rsaKey), jwsClaimsSet()); + signedJWT.sign(new RSASSASigner(rsaKey.toPrivateKey())); + return signedJWT.serialize(); + } catch (Exception e) { + return null; + } + } + + private static JWSHeader jwsHeader(RSAKey rsaKey) { + return new JWSHeader.Builder(JWSAlgorithm.RS256) + .type(JOSEObjectType.JWT) + .keyID(rsaKey.getKeyID()) + .build(); + } + + private static JWTClaimsSet jwsClaimsSet(){ + return new JWTClaimsSet.Builder() + .issuer("https://localhost:8443/auth/realms/master") + .audience("master-realm") + .subject(UUID.randomUUID().toString()) + .jwtID(UUID.randomUUID().toString()) + .claim("client_id", strings.next()) + .claim("typ", "Bearer") + .claim("extensions", jwsIheExtensions()) + .notBeforeTime(Date.from(Instant.now())) + .expirationTime(Date.from(Instant.now().plusSeconds(120))) + .build(); + } + + private static Map> jwsIheExtensions() { + Map> extensions = new HashMap<>(); + extensions.put("ihe_iua", jwsIheIuaExtensions()); + extensions.put("ihe_bppc", jwsIheBppcExtensions()); + + return extensions; + } + + private static Map jwsIheIuaExtensions() { + Map iheIuaMap = new HashMap<>(); + iheIuaMap.put("subject_name", "Dr. John Smith"); + iheIuaMap.put("subject_organization", "Central Hospital"); + iheIuaMap.put("subject_organization_id", "urn:oid:1.2.3." + integers.next()); + iheIuaMap.put("subject_role", List.of("my-role-1", "my-role-2")); + iheIuaMap.put("purpose_of_use", List.of("1.0.14265.1", "1.0.14265.2")); + iheIuaMap.put("home_community_id", "urn:oid:1.2.3." + integers.next()); + iheIuaMap.put("national_provider_identifier", "urn:oid:1.2.3." + integers.next()); + iheIuaMap.put("person_id", "ABC" + integers.next()); + return iheIuaMap; + } + + private static Map jwsIheBppcExtensions() { + Map bppcMap = new HashMap<>(); + bppcMap.put("patient_id", integers.next() + "^^^&1.2.840.113619.6.197&ISO"); + bppcMap.put("doc_id", "urn:oid:1.2.3." + integers.next()); + bppcMap.put("acp", "urn:oid:1.2.3." + integers.next()); + return bppcMap; + } + +} + diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSSenderIntegrationTest.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSSenderIntegrationTest.java index bb50f46d44..767a3dd40f 100644 --- a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSSenderIntegrationTest.java +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSSenderIntegrationTest.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.openehealth.ipf.commons.audit.CustomTlsParameters; -import org.openehealth.ipf.commons.audit.DefaultAuditContext; +import org.openehealth.ipf.commons.audit.DefaultBalpAuditContext; import org.openehealth.ipf.commons.audit.TlsParameters; import org.openehealth.ipf.commons.audit.codes.EventOutcomeIndicator; import org.openehealth.ipf.commons.audit.event.ApplicationActivityBuilder; @@ -25,14 +25,15 @@ @ExtendWith(FhirAuditRepository.class) public abstract class AbstractFhirRestTLSSenderIntegrationTest { - protected DefaultAuditContext auditContext; + protected DefaultBalpAuditContext auditContext; private static final Logger LOG = LoggerFactory.getLogger(AbstractFhirRestTLSSenderIntegrationTest.class); @BeforeEach public void setup() { - this.auditContext = new DefaultAuditContext(); + this.auditContext = new DefaultBalpAuditContext(); auditContext.setAuditRepositoryPort(FhirAuditRepository.getServerHttpsPort()); + auditContext.setAuditRepositoryContextPath(FhirAuditRepository.getServerContextPath()); auditContext.setAuditRepositoryHost("localhost"); auditContext.setAuditEnabled(true); auditContext.setSerializationStrategy((auditMessage, writer, pretty) -> writer.write("")); @@ -57,7 +58,7 @@ TlsParameters setupDefaultTlsParameter() { @AfterEach public void tearDown() { LOG.info("FhirAuditRepository size: " + FhirAuditRepository.getAuditEvents().size() + ". Cleanup...."); - FhirAuditRepository.getAuditEvents().clear(); + FhirAuditRepository.clearAuditEvents(); LOG.info("FhirAuditRepository size: " + FhirAuditRepository.getAuditEvents().size()); } diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/server/FhirAuditServer.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/server/FhirAuditServer.java new file mode 100644 index 0000000000..7f7e5e0c3f --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/server/FhirAuditServer.java @@ -0,0 +1,130 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.ihe.fhir.audit.server; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.Delete; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.OptionalParam; +import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import org.hl7.fhir.dstu3.model.Organization; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.AuditEvent; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.ResourceType; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +public class FhirAuditServer extends RestfulServer implements IResourceProvider { + + private final Map auditEvents = Collections.synchronizedMap(new HashMap<>()); + + public FhirAuditServer() { + setFhirContext(FhirContext.forR4()); + setResourceProviders(this); + } + + public List getAuditEvents() { + return new ArrayList<>(auditEvents.values()); + } + + public void clearAuditEvents() { + auditEvents.clear(); + } + + @Read() + public AuditEvent read(@IdParam IdType theId) { + AuditEvent auditEvent = auditEvents.get(theId.getIdPart()); + if (auditEvent == null) { + throw new ResourceNotFoundException(theId); + } + return auditEvent; + } + + @Delete() + public MethodOutcome delete(@IdParam IdType theId) { + AuditEvent auditEvent = auditEvents.remove(theId.getIdPart()); + if (auditEvent == null) { + throw new ResourceNotFoundException(theId); + } + return new MethodOutcome(theId); + } + + @Create() + public MethodOutcome create(@ResourceParam AuditEvent auditEvent) { + String id = UUID.randomUUID().toString(); + IdType idType = new IdType(ResourceType.AuditEvent.name(), id); + auditEvent.setId(idType); + auditEvents.put(id, auditEvent); + return new MethodOutcome(idType, true); + } + + @Search + public List list(@OptionalParam(name= AuditEvent.SP_TYPE) TokenParam type, + @OptionalParam(name= AuditEvent.SP_SUBTYPE) TokenParam subtype) { + Stream allAuditEvents = getAuditEvents().stream(); + if (type != null) { + if (isNotBlank(type.getSystem()) && isNotBlank(type.getValue())) { + allAuditEvents = allAuditEvents.filter(auditEvent -> + auditEvent.getType().hasSystem() && auditEvent.getType().getSystem().equals(type.getSystem()) && + auditEvent.getType().hasCode() && auditEvent.getType().getCode().equals(type.getValue())); + } else if (isNotBlank(type.getSystem()) && isBlank(type.getValue())){ + allAuditEvents = allAuditEvents.filter(auditEvent -> + auditEvent.getType().hasSystem() && auditEvent.getType().getSystem().equals(type.getSystem())); + } else if (isBlank(type.getSystem()) && isNotBlank(type.getValue())){ + allAuditEvents = allAuditEvents.filter(auditEvent -> + auditEvent.getType().hasCode() && auditEvent.getType().getCode().equals(type.getValue())); + } + } + if (subtype != null) { + if (isNotBlank(subtype.getSystem()) && isNotBlank(subtype.getValue())) { + allAuditEvents = allAuditEvents.filter(auditEvent -> + auditEvent.getSubtypeFirstRep().hasSystem() && auditEvent.getSubtypeFirstRep().getSystem().equals(subtype.getSystem()) && + auditEvent.getSubtypeFirstRep().hasCode() && auditEvent.getSubtypeFirstRep().getCode().equals(subtype.getValue())); + } else if (isNotBlank(subtype.getSystem()) && isBlank(subtype.getValue())){ + allAuditEvents = allAuditEvents.filter(auditEvent -> + auditEvent.getSubtypeFirstRep().hasSystem() && auditEvent.getSubtypeFirstRep().getSystem().equals(subtype.getSystem())); + } else if (isBlank(subtype.getSystem()) && isNotBlank(subtype.getValue())){ + allAuditEvents = allAuditEvents.filter(auditEvent -> + auditEvent.getSubtypeFirstRep().hasCode() && auditEvent.getSubtypeFirstRep().getCode().equals(subtype.getValue())); + } + } + return allAuditEvents.collect(Collectors.toList()); + } + + @Override + public Class getResourceType() { + return AuditEvent.class; + } +} diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/server/TLSBalpRepository.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/server/TLSBalpRepository.java new file mode 100644 index 0000000000..a9acf2ecf7 --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/server/TLSBalpRepository.java @@ -0,0 +1,118 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.ihe.fhir.audit.server; + +import io.undertow.Handlers; +import io.undertow.Undertow; +import io.undertow.UndertowOptions; +import io.undertow.server.HttpHandler; +import io.undertow.server.handlers.PathHandler; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.InstanceFactory; +import io.undertow.servlet.api.InstanceHandle; +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletException; + +import java.io.Closeable; +import java.io.IOException; + +import static io.undertow.servlet.Servlets.defaultContainer; +import static io.undertow.servlet.Servlets.deployment; +import static io.undertow.servlet.Servlets.servlet; + +public class TLSBalpRepository implements Closeable { + + private static final Logger LOG = LoggerFactory.getLogger(TLSBalpRepository.class); + protected final TlsParameters tlsParameters; + private Undertow server; + private final int httpsPort; + + public TLSBalpRepository(TlsParameters tlsParameters, int httpsPort) { + this.tlsParameters = tlsParameters; + this.httpsPort = httpsPort; + } + + public TLSBalpRepository(int httpsPort) { + this.tlsParameters = TlsParameters.getDefault(); + this.httpsPort = httpsPort; + } + + @Override + public void close() throws IOException { + stop(); + } + + public void stop() { + if (server != null) server.stop(); + LOG.info("successfully stopped FHIR Audit Server"); + } + + public Undertow start() throws ServletException { + DeploymentInfo servletBuilder = deployment() + .setClassLoader(FhirAuditRepository.class.getClassLoader()) + .setContextPath("/fhir") + .setDeploymentName("FHIR-Deployment") + .addServlets( + servlet("FhirAuditServer", FhirAuditServer.class, new FhirServletInitiator(new FhirAuditServer())) + .addMapping("/*")); + + DeploymentManager manager = defaultContainer().addDeployment(servletBuilder); + manager.deploy(); + + HttpHandler servletHandler = manager.start(); + PathHandler path = Handlers + .path(Handlers.redirect("/")) + .addPrefixPath("/", servletHandler); + server = Undertow.builder() + .setServerOption(UndertowOptions.ENABLE_HTTP2, true) + .addHttpsListener( + httpsPort,"localhost", tlsParameters.getSSLContext(true)) + .setHandler(path) + .build(); + server.start(); + LOG.info("successfully started FHIR Audit Server on port {}", httpsPort); + return server; + } + static class FhirServletInitiator implements InstanceFactory { + + private final FhirAuditServer fhirAuditServer; + + public FhirServletInitiator(FhirAuditServer fhirAuditServer) { + this.fhirAuditServer = fhirAuditServer; + } + + @Override + public InstanceHandle createInstance() throws InstantiationException { + return new InstanceHandle<>() { + @Override + public FhirAuditServer getInstance() { + return fhirAuditServer; + } + + @Override + public void release() { + + } + }; + } + } + +} diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/extension/FhirAuditRepository.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/extension/FhirAuditRepository.java index a8351b909a..0bc3380a26 100644 --- a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/extension/FhirAuditRepository.java +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/extension/FhirAuditRepository.java @@ -1,11 +1,5 @@ package org.openehealth.ipf.commons.ihe.fhir.extension; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.rest.annotation.Create; -import ca.uhn.fhir.rest.annotation.ResourceParam; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; import io.undertow.Handlers; import io.undertow.Undertow; import io.undertow.UndertowOptions; @@ -15,15 +9,14 @@ import io.undertow.servlet.api.DeploymentManager; import io.undertow.servlet.api.InstanceFactory; import io.undertow.servlet.api.InstanceHandle; -import org.hl7.fhir.instance.model.api.IBaseResource; +import net.java.quickcheck.generator.PrimitiveGenerators; import org.hl7.fhir.r4.model.AuditEvent; -import org.hl7.fhir.r4.model.IdType; -import org.hl7.fhir.r4.model.ResourceType; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import org.openehealth.ipf.commons.audit.CustomTlsParameters; import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.ihe.fhir.audit.server.FhirAuditServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.ClassPathResource; @@ -32,8 +25,6 @@ import java.net.URI; import java.nio.file.Paths; import java.util.List; -import java.util.UUID; -import java.util.concurrent.CopyOnWriteArrayList; import static io.undertow.servlet.Servlets.defaultContainer; import static io.undertow.servlet.Servlets.deployment; @@ -45,7 +36,8 @@ public class FhirAuditRepository implements BeforeAllCallback, BeforeEachCallbac private ExtensionContext extensionContext; private static FhirAuditServer fhirAuditServer; private static final String STORE_KEY = "undertow"; - private static int httpsPort ; + private static int httpsPort; + private static String contextPath; static final String SERVER_KEY_STORE; static final String SERVER_KEY_STORE_PASS = "init"; static final String TRUST_STORE; @@ -80,6 +72,7 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception { if (hasStartedUndertow()) return; httpsPort = freePort(); + contextPath = PrimitiveGenerators.letterStrings(10, 10).next(); registerShutdownHook(); } @@ -95,12 +88,12 @@ public void beforeEach(ExtensionContext extensionContext) throws Exception { if (server == null) { fhirAuditServer = new FhirAuditServer(); DeploymentInfo servletBuilder = deployment() - .setClassLoader(FhirAuditRepository.class.getClassLoader()) - .setContextPath("/") - .setDeploymentName("FHIR-Deployment") - .addServlets( - servlet("FhirAuditServer", FhirAuditServer.class, new FhirServletInitiator(fhirAuditServer)) - .addMapping("/*")); + .setClassLoader(FhirAuditRepository.class.getClassLoader()) + .setContextPath("/" + contextPath) + .setDeploymentName("FHIR-Deployment") + .addServlets( + servlet("FhirAuditServer", FhirAuditServer.class, new FhirServletInitiator(fhirAuditServer)) + .addMapping("/*")); DeploymentManager manager = defaultContainer().addDeployment(servletBuilder); manager.deploy(); @@ -110,11 +103,11 @@ public void beforeEach(ExtensionContext extensionContext) throws Exception { .path(Handlers.redirect("/")) .addPrefixPath("/", servletHandler); server = Undertow.builder() - .setServerOption(UndertowOptions.ENABLE_HTTP2, true) - .addHttpsListener( - httpsPort,"localhost", setupDefaultTlsParameter().getSSLContext(true)) - .setHandler(path) - .build(); + .setServerOption(UndertowOptions.ENABLE_HTTP2, true) + .addHttpsListener( + httpsPort,"localhost", setupDefaultTlsParameter().getSSLContext(true)) + .setHandler(path) + .build(); server.start(); } } @@ -131,39 +124,20 @@ private void registerShutdownHook() { public static int getServerHttpsPort(){ return httpsPort; } + + public static String getServerContextPath(){ + return contextPath; + } public static List getAuditEvents() { return fhirAuditServer.getAuditEvents(); } - private boolean hasStartedUndertow(){ - return extensionContext.getRoot().getStore(GLOBAL).get(STORE_KEY) != null; + public static void clearAuditEvents() { + fhirAuditServer.clearAuditEvents(); } - static class FhirAuditServer extends RestfulServer implements IResourceProvider { - - private final List auditEvents = new CopyOnWriteArrayList<>(); - - public FhirAuditServer() { - setFhirContext(FhirContext.forR4()); - setResourceProviders(this); - } - - public List getAuditEvents() { - return auditEvents; - } - - @Create() - public MethodOutcome create(@ResourceParam AuditEvent auditEvent) { - auditEvents.add(auditEvent); - return new MethodOutcome( - new IdType(ResourceType.AuditEvent.name(), - UUID.randomUUID().toString()), true); - } - - @Override - public Class getResourceType() { - return AuditEvent.class; - } + private boolean hasStartedUndertow(){ + return extensionContext.getRoot().getStore(GLOBAL).get(STORE_KEY) != null; } static class FhirServletInitiator implements InstanceFactory { diff --git a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/AbstractFhirAuditSerializationStrategy.java b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/AbstractFhirAuditSerializationStrategy.java index 1b884bf0cd..f7c3762273 100644 --- a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/AbstractFhirAuditSerializationStrategy.java +++ b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/AbstractFhirAuditSerializationStrategy.java @@ -20,20 +20,34 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.AuditEvent; +import org.hl7.fhir.r4.model.Base64BinaryType; +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.Reference; import org.openehealth.ipf.commons.audit.AuditException; -import org.openehealth.ipf.commons.audit.codes.*; +import org.openehealth.ipf.commons.audit.codes.EventActionCode; +import org.openehealth.ipf.commons.audit.codes.EventOutcomeIndicator; +import org.openehealth.ipf.commons.audit.codes.NetworkAccessPointTypeCode; +import org.openehealth.ipf.commons.audit.codes.ParticipantObjectDataLifeCycle; +import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCode; +import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCodeRole; import org.openehealth.ipf.commons.audit.marshal.SerializationStrategy; import org.openehealth.ipf.commons.audit.model.ActiveParticipantType; import org.openehealth.ipf.commons.audit.model.AuditMessage; import org.openehealth.ipf.commons.audit.model.AuditSourceIdentificationType; import org.openehealth.ipf.commons.audit.model.ParticipantObjectIdentificationType; +import org.openehealth.ipf.commons.audit.types.ActiveParticipantRoleId; import org.openehealth.ipf.commons.audit.types.CodedValueType; import java.io.IOException; import java.io.Writer; import java.sql.Date; +import java.util.List; +import java.util.Optional; import java.util.function.Function; +import java.util.stream.Collectors; import static org.openehealth.ipf.commons.ihe.fhir.audit.codes.Constants.*; @@ -104,7 +118,6 @@ protected AuditEvent.AuditEventEntityComponent participantObjectIdentificationTo entity.setWhat(new Reference(poit.getParticipantObjectID())); } return entity; - } protected AuditEvent.AuditEventSourceComponent auditSourceIdentificationToEventSource(AuditSourceIdentificationType asit) { @@ -117,6 +130,40 @@ protected AuditEvent.AuditEventSourceComponent auditSourceIdentificationToEventS } protected AuditEvent.AuditEventAgentComponent activeParticipantToAgent(ActiveParticipantType ap) { + Optional oUser = getOAuthAttrFromKnownRoleIdCode(ap.getRoleIDCodes(), OUSER_AGENT_TYPE_SYSTEM_NAME); + if (oUser.isPresent()) { + AuditEvent.AuditEventAgentComponent agent = new AuditEvent.AuditEventAgentComponent() + .setType(systemAndCodeToCodeableConcept(OUSER_AGENT_TYPE_SYSTEM_NAME, OUSER_AGENT_TYPE_CODE, "information recipient")) + .addPolicy(oUser.get()) + .setName(ap.getUserName()) + .setWho( + new Reference(ap.getUserID()) + .setIdentifier(new Identifier().setSystem(ap.getAlternativeUserID()).setValue(ap.getUserID())) + .setDisplay(ap.getUserName())) + .setRequestor(ap.isUserIsRequestor()); + getOAuthListAttrFromKnownRoleIdCode(ap.getRoleIDCodes(), OUSER_AGENT_PURPOSE_OF_USE_SYSTEM_NAME) + .forEach(purpose -> agent.getPurposeOfUse().add( + systemAndCodeToCodeableConcept(OUSER_AGENT_PURPOSE_OF_USE_SYSTEM_NAME, purpose, ""))); + getOAuthListAttrFromKnownRoleIdCode(ap.getRoleIDCodes(), OUSER_AGENT_ROLE_SYSTEM_NAME) + .forEach(purpose -> agent.getRole().add( + systemAndCodeToCodeableConcept(OUSER_AGENT_ROLE_SYSTEM_NAME, purpose, ""))); + return agent; + } + Optional oClient = getOAuthAttrFromKnownRoleIdCode(ap.getRoleIDCodes(), DCM_SYSTEM_NAME); + if (oClient.isPresent()) { + return new AuditEvent.AuditEventAgentComponent() + .setType(systemAndCodeToCodeableConcept(DCM_SYSTEM_NAME, DCM_OCLIENT_CODE, "Application")) + .setRequestor(ap.isUserIsRequestor()) + .setWho(new Reference().setIdentifier(new Identifier().setValue(oClient.get()))); + } + Optional opaqueToken = getOAuthAttrFromKnownRoleIdCode(ap.getRoleIDCodes(), + OUSER_AGENT_TYPE_OPAQUE_SYSTEM_NAME); + if (opaqueToken.isPresent()) { + return new AuditEvent.AuditEventAgentComponent() + .setType(new CodeableConcept( + new Coding(OUSER_AGENT_TYPE_OPAQUE_SYSTEM_NAME, OUSER_AGENT_TYPE_OPAQUE_CODE, ""))) + .setRequestor(true); + } return new AuditEvent.AuditEventAgentComponent() .setType(codedValueTypeToCodeableConcept(ap.getRoleIDCodes().get(0), DCM_SYSTEM_NAME)) .setWho(new Reference().setDisplay(ap.getUserID())) @@ -129,16 +176,29 @@ protected AuditEvent.AuditEventAgentComponent activeParticipantToAgent(ActivePar .setType(auditEventNetworkType(ap.getNetworkAccessPointTypeCode()))); } + private Optional getOAuthAttrFromKnownRoleIdCode(List roleCodes, + String knownCodeSystem) { + return roleCodes.stream().filter(p -> p.getCodeSystemName().equals(knownCodeSystem)) + .findFirst() + .map(CodedValueType::getCode); + } + + private List getOAuthListAttrFromKnownRoleIdCode(List roleCodes, + String knownCodeSystem) { + return roleCodes.stream().filter(p -> p.getCodeSystemName().equals(knownCodeSystem)) + .map(CodedValueType::getCode).collect(Collectors.toList()); + } + protected AuditEvent.AuditEventAgentNetworkType auditEventNetworkType(NetworkAccessPointTypeCode naptc) { try { - return AuditEvent.AuditEventAgentNetworkType.fromCode(String.valueOf(naptc.getValue())); + return naptc != null? + AuditEvent.AuditEventAgentNetworkType.fromCode(String.valueOf(naptc.getValue())) : null; } catch (FHIRException e) { // should never happen throw new AuditException(e); } } - protected AuditEvent.AuditEventOutcome getAuditEventOutcome(EventOutcomeIndicator eventOutcomeIndicator) { try { return AuditEvent.AuditEventOutcome.fromCode(String.valueOf(eventOutcomeIndicator.getValue())); @@ -190,4 +250,8 @@ protected CodeableConcept codedValueTypeToCodeableConcept(CodedValueType cvt, St new CodeableConcept().addCoding(codedValueTypeToCoding(cvt, codeSystem)) : null; } + + protected CodeableConcept systemAndCodeToCodeableConcept(String codeSystem, String code, String displayName) { + return new CodeableConcept().addCoding(new Coding(codeSystem, code, displayName)); + } } diff --git a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti105/Iti105ClientAuditStrategy.java b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti105/Iti105ClientAuditStrategy.java index f176fb035d..47a05d5b4a 100644 --- a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti105/Iti105ClientAuditStrategy.java +++ b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti105/Iti105ClientAuditStrategy.java @@ -20,8 +20,8 @@ import org.openehealth.ipf.commons.audit.codes.ParticipantObjectIdTypeCode; import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCodeRole; import org.openehealth.ipf.commons.audit.model.AuditMessage; -import org.openehealth.ipf.commons.ihe.core.atna.event.PHIExportBuilder; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode; +import org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpPHIExportBuilder; import java.util.Collections; @@ -37,7 +37,7 @@ public Iti105ClientAuditStrategy() { @Override public AuditMessage[] makeAuditMessage(AuditContext auditContext, Iti105AuditDataset auditDataset) { - return new PHIExportBuilder<>(auditContext, auditDataset, FhirEventTypeCode.SimplifiedPublish) + return new BalpPHIExportBuilder(auditContext, auditDataset, FhirEventTypeCode.SimplifiedPublish) .setPatient(auditDataset.getPatientId()) .addExportedEntity( auditDataset.getDocumentReferenceId() != null ? diff --git a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti105/Iti105ServerAuditStrategy.java b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti105/Iti105ServerAuditStrategy.java index 117c39dacd..14ffa1b8b1 100644 --- a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti105/Iti105ServerAuditStrategy.java +++ b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti105/Iti105ServerAuditStrategy.java @@ -20,8 +20,8 @@ import org.openehealth.ipf.commons.audit.codes.ParticipantObjectIdTypeCode; import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCodeRole; import org.openehealth.ipf.commons.audit.model.AuditMessage; -import org.openehealth.ipf.commons.ihe.core.atna.event.PHIImportBuilder; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode; +import org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpPHIImportBuilder; import java.util.Collections; @@ -37,7 +37,7 @@ public Iti105ServerAuditStrategy() { @Override public AuditMessage[] makeAuditMessage(AuditContext auditContext, Iti105AuditDataset auditDataset) { - return new PHIImportBuilder<>(auditContext, auditDataset, FhirEventTypeCode.SimplifiedPublish) + return new BalpPHIImportBuilder(auditContext, auditDataset, FhirEventTypeCode.SimplifiedPublish) .setPatient(auditDataset.getPatientId()) .addImportedEntity( auditDataset.getDocumentReferenceId(), diff --git a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti65/Iti65ClientAuditStrategy.java b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti65/Iti65ClientAuditStrategy.java index 516427c624..0bda35750c 100644 --- a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti65/Iti65ClientAuditStrategy.java +++ b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti65/Iti65ClientAuditStrategy.java @@ -20,8 +20,8 @@ import org.openehealth.ipf.commons.audit.codes.ParticipantObjectIdTypeCode; import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCodeRole; import org.openehealth.ipf.commons.audit.model.AuditMessage; -import org.openehealth.ipf.commons.ihe.core.atna.event.PHIExportBuilder; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode; +import org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpPHIExportBuilder; import java.util.Collections; @@ -37,7 +37,7 @@ public Iti65ClientAuditStrategy() { @Override public AuditMessage[] makeAuditMessage(AuditContext auditContext, Iti65AuditDataset auditDataset) { - return new PHIExportBuilder<>(auditContext, auditDataset, FhirEventTypeCode.ProvideDocumentBundle) + return new BalpPHIExportBuilder(auditContext, auditDataset, FhirEventTypeCode.ProvideDocumentBundle) .setPatient(auditDataset.getPatientId()) .addExportedEntity( auditDataset.getSubmissionSetUuid() != null ? diff --git a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti65/Iti65ServerAuditStrategy.java b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti65/Iti65ServerAuditStrategy.java index e086e7d1df..75829a4c0b 100644 --- a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti65/Iti65ServerAuditStrategy.java +++ b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti65/Iti65ServerAuditStrategy.java @@ -20,8 +20,8 @@ import org.openehealth.ipf.commons.audit.codes.ParticipantObjectIdTypeCode; import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCodeRole; import org.openehealth.ipf.commons.audit.model.AuditMessage; -import org.openehealth.ipf.commons.ihe.core.atna.event.PHIImportBuilder; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode; +import org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpPHIImportBuilder; import java.util.Collections; @@ -37,7 +37,7 @@ public Iti65ServerAuditStrategy() { @Override public AuditMessage[] makeAuditMessage(AuditContext auditContext, Iti65AuditDataset auditDataset) { - return new PHIImportBuilder<>(auditContext, auditDataset, FhirEventTypeCode.ProvideDocumentBundle) + return new BalpPHIImportBuilder(auditContext, auditDataset, FhirEventTypeCode.ProvideDocumentBundle) .setPatient(auditDataset.getPatientId()) .addImportedEntity( auditDataset.getSubmissionSetUuid(), diff --git a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti66/Iti66AuditStrategy.java b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti66/Iti66AuditStrategy.java index 82b33d7491..8e587b9654 100644 --- a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti66/Iti66AuditStrategy.java +++ b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti66/Iti66AuditStrategy.java @@ -22,6 +22,7 @@ import org.openehealth.ipf.commons.ihe.fhir.audit.FhirQueryAuditStrategy; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirParticipantObjectIdTypeCode; +import org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpQueryInformationBuilder; /** @@ -37,7 +38,7 @@ public Iti66AuditStrategy(boolean serverSide) { @Override public AuditMessage[] makeAuditMessage(AuditContext auditContext, FhirQueryAuditDataset auditDataset) { - return new QueryInformationBuilder<>(auditContext, auditDataset, FhirEventTypeCode.MobileDocumentManifestQuery) + return new BalpQueryInformationBuilder(auditContext, auditDataset, FhirEventTypeCode.MobileDocumentManifestQuery) .addPatients(auditDataset.getPatientIds()) .setQueryParameters("MobileDocumentManifestQuery", FhirParticipantObjectIdTypeCode.MobileDocumentManifestQuery, diff --git a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti67/Iti67AuditStrategy.java b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti67/Iti67AuditStrategy.java index 6ba6a3f059..91ab8a6355 100644 --- a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti67/Iti67AuditStrategy.java +++ b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti67/Iti67AuditStrategy.java @@ -22,6 +22,7 @@ import org.openehealth.ipf.commons.ihe.fhir.audit.FhirQueryAuditStrategy; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirParticipantObjectIdTypeCode; +import org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpQueryInformationBuilder; /** * @author Christian Ohr @@ -35,7 +36,7 @@ public Iti67AuditStrategy(boolean serverSide) { @Override public AuditMessage[] makeAuditMessage(AuditContext auditContext, FhirQueryAuditDataset auditDataset) { - return new QueryInformationBuilder(auditContext, auditDataset, FhirEventTypeCode.MobileDocumentReferenceQuery) + return new BalpQueryInformationBuilder(auditContext, auditDataset, FhirEventTypeCode.MobileDocumentReferenceQuery) .addPatients(auditDataset.getPatientIds()) .setQueryParameters("MobileDocumentReferenceQuery", FhirParticipantObjectIdTypeCode.MobileDocumentReferenceQuery, diff --git a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti68/Iti68ServerAuditStrategy.java b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti68/Iti68ServerAuditStrategy.java index 8360d4db4e..4bcfe8ce7e 100644 --- a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti68/Iti68ServerAuditStrategy.java +++ b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti68/Iti68ServerAuditStrategy.java @@ -24,6 +24,7 @@ import org.openehealth.ipf.commons.ihe.core.atna.AuditStrategySupport; import org.openehealth.ipf.commons.ihe.core.atna.event.PHIExportBuilder; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode; +import org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpPHIExportBuilder; import java.util.Map; @@ -45,7 +46,7 @@ public Iti68AuditDataset enrichAuditDatasetFromRequest(Iti68AuditDataset auditDa @Override public AuditMessage[] makeAuditMessage(AuditContext auditContext, Iti68AuditDataset auditDataset) { - PHIExportBuilder builder = new PHIExportBuilder<>(auditContext, auditDataset, + BalpPHIExportBuilder builder = new BalpPHIExportBuilder(auditContext, auditDataset, EventActionCode.Create, FhirEventTypeCode.MobileDocumentRetrieval) .setPatient(auditDataset.getPatientId()); diff --git a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti68bin/Iti68BinaryServerAuditStrategy.java b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti68bin/Iti68BinaryServerAuditStrategy.java index 2a3b3e1927..b6fcee9a98 100644 --- a/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti68bin/Iti68BinaryServerAuditStrategy.java +++ b/commons/ihe/fhir/r4/mhd/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti68bin/Iti68BinaryServerAuditStrategy.java @@ -23,6 +23,7 @@ import org.openehealth.ipf.commons.ihe.core.atna.event.PHIExportBuilder; import org.openehealth.ipf.commons.ihe.fhir.audit.FhirAuditDataset; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode; +import org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpPHIExportBuilder; import org.openehealth.ipf.commons.ihe.fhir.iti68.Iti68AuditDataset; import java.util.Map; @@ -45,7 +46,7 @@ public FhirAuditDataset enrichAuditDatasetFromRequest(FhirAuditDataset auditData @Override public AuditMessage[] makeAuditMessage(AuditContext auditContext, FhirAuditDataset auditDataset) { - PHIExportBuilder builder = new PHIExportBuilder<>(auditContext, auditDataset, + BalpPHIExportBuilder builder = new BalpPHIExportBuilder(auditContext, auditDataset, EventActionCode.Create, FhirEventTypeCode.MobileDocumentRetrieval) .setPatient(auditDataset.getPatientId()); diff --git a/commons/ihe/fhir/r4/pixpdq/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti78/Iti78AuditStrategy.java b/commons/ihe/fhir/r4/pixpdq/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti78/Iti78AuditStrategy.java index 66385d26ae..2208ff4746 100644 --- a/commons/ihe/fhir/r4/pixpdq/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti78/Iti78AuditStrategy.java +++ b/commons/ihe/fhir/r4/pixpdq/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti78/Iti78AuditStrategy.java @@ -18,11 +18,11 @@ import org.hl7.fhir.r4.model.IdType; import org.openehealth.ipf.commons.audit.AuditContext; import org.openehealth.ipf.commons.audit.model.AuditMessage; -import org.openehealth.ipf.commons.ihe.core.atna.event.QueryInformationBuilder; import org.openehealth.ipf.commons.ihe.fhir.audit.FhirQueryAuditDataset; import org.openehealth.ipf.commons.ihe.fhir.audit.FhirQueryAuditStrategy; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirParticipantObjectIdTypeCode; +import org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpQueryInformationBuilder; import java.util.Map; @@ -40,7 +40,7 @@ protected Iti78AuditStrategy(boolean serverSide) { @Override public AuditMessage[] makeAuditMessage(AuditContext auditContext, FhirQueryAuditDataset auditDataset) { - return new QueryInformationBuilder<>(auditContext, auditDataset, FhirEventTypeCode.MobilePatientDemographicsQuery) + return new BalpQueryInformationBuilder(auditContext, auditDataset, FhirEventTypeCode.MobilePatientDemographicsQuery) .addPatients(auditDataset.getPatientIds()) .setQueryParameters( "MobilePatientDemographicsQuery", diff --git a/commons/ihe/fhir/r4/pixpdq/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti83/Iti83AuditStrategy.java b/commons/ihe/fhir/r4/pixpdq/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti83/Iti83AuditStrategy.java index b64648554f..284e9dba96 100644 --- a/commons/ihe/fhir/r4/pixpdq/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti83/Iti83AuditStrategy.java +++ b/commons/ihe/fhir/r4/pixpdq/src/main/java/org/openehealth/ipf/commons/ihe/fhir/iti83/Iti83AuditStrategy.java @@ -20,12 +20,12 @@ import org.hl7.fhir.r4.model.StringType; import org.openehealth.ipf.commons.audit.AuditContext; import org.openehealth.ipf.commons.audit.model.AuditMessage; -import org.openehealth.ipf.commons.ihe.core.atna.event.QueryInformationBuilder; import org.openehealth.ipf.commons.ihe.fhir.Constants; import org.openehealth.ipf.commons.ihe.fhir.audit.FhirQueryAuditDataset; import org.openehealth.ipf.commons.ihe.fhir.audit.FhirQueryAuditStrategy; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirParticipantObjectIdTypeCode; +import org.openehealth.ipf.commons.ihe.fhir.audit.events.BalpQueryInformationBuilder; import java.util.Map; @@ -43,7 +43,7 @@ public Iti83AuditStrategy(boolean serverSide) { @Override public AuditMessage[] makeAuditMessage(AuditContext auditContext, FhirQueryAuditDataset auditDataset) { - return new QueryInformationBuilder<>(auditContext, auditDataset, FhirEventTypeCode.MobilePatientIdentifierCrossReferenceQuery) + return new BalpQueryInformationBuilder(auditContext, auditDataset, FhirEventTypeCode.MobilePatientIdentifierCrossReferenceQuery) .addPatients(auditDataset.getPatientIds()) .setQueryParameters( "PIXmQuery", diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 02ed567724..e053778255 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -27,6 +27,7 @@ 2.1.0 2.0.6.1 1.7.0 + 9.37.3 5.6.5 9.5.5 11.4 @@ -100,7 +101,7 @@ jaxb2-basics-tools - commons-beanutils commons-beanutils @@ -305,6 +306,11 @@ methanol ${methanol-version} + + com.nimbusds + nimbus-jose-jwt + ${nimbus-jose-jwt-version} + org.apache.cxf cxf-rt-transports-http-jetty diff --git a/platform-camel/ihe/fhir/core/src/main/java/org/openehealth/ipf/platform/camel/ihe/fhir/core/intercept/consumer/AuditInterceptorUtils.java b/platform-camel/ihe/fhir/core/src/main/java/org/openehealth/ipf/platform/camel/ihe/fhir/core/intercept/consumer/AuditInterceptorUtils.java index 1f719e0b6c..dd05a03dfa 100644 --- a/platform-camel/ihe/fhir/core/src/main/java/org/openehealth/ipf/platform/camel/ihe/fhir/core/intercept/consumer/AuditInterceptorUtils.java +++ b/platform-camel/ihe/fhir/core/src/main/java/org/openehealth/ipf/platform/camel/ihe/fhir/core/intercept/consumer/AuditInterceptorUtils.java @@ -24,6 +24,10 @@ import javax.naming.ldap.LdapName; import javax.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; /** * @author Christian Ohr @@ -51,4 +55,24 @@ public static void extractClientCertificateCommonName(Exchange exchange, AuditDa } } } + + public static Optional extractAuthorizationHeader(Exchange exchange) { + if (exchange.getIn().getHeader(Constants.HTTP_INCOMING_HEADERS) != null) { + Map> httpHeaders = exchange.getIn().getHeader(Constants.HTTP_INCOMING_HEADERS, Map.class); + if (!httpHeaders.isEmpty() + && httpHeaders.keySet().stream().anyMatch(Constants.HTTP_AUTHORIZATION::equalsIgnoreCase)) { + + List values = httpHeaders.entrySet().stream() + .filter(entry -> Constants.HTTP_AUTHORIZATION.equalsIgnoreCase(entry.getKey())) + .findFirst() + .map(Map.Entry::getValue) + .orElse(new ArrayList<>()); + + if (!values.isEmpty()) { + return Optional.of(values.get(0)); + } + } + } + return Optional.empty(); + } } diff --git a/platform-camel/ihe/fhir/core/src/main/java/org/openehealth/ipf/platform/camel/ihe/fhir/core/intercept/consumer/ConsumerAuditInterceptor.java b/platform-camel/ihe/fhir/core/src/main/java/org/openehealth/ipf/platform/camel/ihe/fhir/core/intercept/consumer/ConsumerAuditInterceptor.java index 39ad8e73d6..2cf1b60f56 100644 --- a/platform-camel/ihe/fhir/core/src/main/java/org/openehealth/ipf/platform/camel/ihe/fhir/core/intercept/consumer/ConsumerAuditInterceptor.java +++ b/platform-camel/ihe/fhir/core/src/main/java/org/openehealth/ipf/platform/camel/ihe/fhir/core/intercept/consumer/ConsumerAuditInterceptor.java @@ -28,6 +28,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + import static java.util.Objects.requireNonNull; @@ -113,7 +117,7 @@ private AuditDatasetType createAndEnrichAuditDatasetFromRequest(AuditStrategyundertow-servlet test + + net.java.quickcheck + quickcheck + diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti105/TestIti105WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti105/TestIti105WithBalpAudit.java index 2866191db6..de90e188e3 100644 --- a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti105/TestIti105WithBalpAudit.java +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti105/TestIti105WithBalpAudit.java @@ -70,7 +70,7 @@ public static void startServer(String contextDescriptor) { @BeforeEach public void beforeEach() { - FhirAuditRepository.getAuditEvents().clear(); + FhirAuditRepository.clearAuditEvents(); } @Test diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/AbstractTestIti65.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/AbstractTestIti65.java index b2e894a92a..8dc3b2f682 100644 --- a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/AbstractTestIti65.java +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/AbstractTestIti65.java @@ -18,6 +18,8 @@ import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import org.apache.camel.Exchange; +import org.apache.camel.component.http.HttpConstants; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.*; import org.ietf.jgss.Oid; @@ -25,6 +27,7 @@ import org.openehealth.ipf.commons.ihe.fhir.Constants; import org.openehealth.ipf.commons.ihe.fhir.IpfFhirServlet; import org.openehealth.ipf.commons.ihe.fhir.SslAwareMethanolRestfulClientFactory; +import org.openehealth.ipf.commons.ihe.fhir.audit.auth.BalpJwtGenerator; import org.openehealth.ipf.commons.ihe.fhir.mhd.MhdProfile; import org.openehealth.ipf.commons.ihe.fhir.mhd.model.ComprehensiveDocumentReference; import org.openehealth.ipf.commons.ihe.fhir.mhd.model.ComprehensiveProvideDocumentBundle; @@ -34,6 +37,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.net.http.HttpHeaders; import java.security.MessageDigest; import java.time.LocalDate; import java.time.LocalTime; @@ -144,13 +148,27 @@ protected Bundle thisSucks() { } protected Bundle sendManually(Bundle bundle) { - return client.transaction().withBundle(bundle).encodedXml().execute(); + return client.transaction().withBundle(bundle) + .encodedXml().execute(); + } + + protected Bundle sendManuallyWithJwt(Bundle bundle) { + var headerValue = "Bearer " + new BalpJwtGenerator().next(); + return client.transaction().withBundle(bundle) + .withAdditionalHeader("Authorization", headerValue) + .encodedXml().execute(); } protected Bundle sendViaProducer(Bundle bundle) { return producerTemplate.requestBody("direct:input", bundle, Bundle.class); } + protected Bundle sendViaProducerWithJwtAuthorization(Bundle bundle) { + var headerValue = "Bearer " + new BalpJwtGenerator().next(); + return producerTemplate.requestBodyAndHeader("direct:input", bundle, "Authorization", + headerValue, Bundle.class); + } + protected void printAsXML(IBaseResource resource) { LOG.info(clientFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(resource)); } diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/TestIti65WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/TestIti65WithBalpAudit.java index 3af3fc4dc4..8ddd0366af 100644 --- a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/TestIti65WithBalpAudit.java +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/TestIti65WithBalpAudit.java @@ -21,6 +21,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.audit.codes.Constants; import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; import java.util.Optional; @@ -44,12 +45,12 @@ public static void setUpClass() { @BeforeEach public void beforeEach() { - FhirAuditRepository.getAuditEvents().clear(); + FhirAuditRepository.clearAuditEvents(); } @Test public void testSendManualMhd() throws Exception { - sendManually(provideAndRegister()); + sendManuallyWithJwt(provideAndRegister()); // Check ATNA Audit var auditEvents = FhirAuditRepository.getAuditEvents(); @@ -69,12 +70,16 @@ public void testSendManualMhd() throws Exception { assertEquals(1, auditEvent.getEntity().stream() .filter(event -> event.getType().getCode().equals("2") && event.getRole().getCode().equals("20")) .count()); + assertTrue(auditEvent.getAgent().stream() + .anyMatch(p -> p.getType().getCodingFirstRep().getCode().equals(Constants.OUSER_AGENT_TYPE_CODE) + && p.getType().getCodingFirstRep().getSystem().equals(Constants.OUSER_AGENT_TYPE_SYSTEM_NAME) + && p.hasPolicy())); } @Test public void testSendEndpointMhd() throws Exception { var bundle = provideAndRegister(); - sendViaProducer(bundle); + sendViaProducerWithJwtAuthorization(bundle); // Check ATNA Audit var auditEvents = FhirAuditRepository.getAuditEvents(); diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti66/v421/TestIti66WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti66/v421/TestIti66WithBalpAudit.java index 4cf4e5abf7..3964d5404f 100644 --- a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti66/v421/TestIti66WithBalpAudit.java +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti66/v421/TestIti66WithBalpAudit.java @@ -48,7 +48,7 @@ public static void setUpClass() { @BeforeEach public void beforeEach() { - FhirAuditRepository.getAuditEvents().clear(); + FhirAuditRepository.clearAuditEvents(); } @Test diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti67/TestIti67WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti67/TestIti67WithBalpAudit.java index 3428b3cacd..6ddb20aee1 100644 --- a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti67/TestIti67WithBalpAudit.java +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti67/TestIti67WithBalpAudit.java @@ -51,7 +51,7 @@ public static void setUpClass() { @BeforeEach public void beforeEach() { - FhirAuditRepository.getAuditEvents().clear(); + FhirAuditRepository.clearAuditEvents(); } @Test diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti68/TestIti68WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti68/TestIti68WithBalpAudit.java index d04c5e665b..5da93b9ed7 100644 --- a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti68/TestIti68WithBalpAudit.java +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti68/TestIti68WithBalpAudit.java @@ -41,7 +41,7 @@ public static void setUpClass() { @BeforeEach public void beforeEach() { - FhirAuditRepository.getAuditEvents().clear(); + FhirAuditRepository.clearAuditEvents(); } @Test diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/pharm5/TestPharm5WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/pharm5/TestPharm5WithBalpAudit.java index 7fc0f2be73..d379e73adb 100644 --- a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/pharm5/TestPharm5WithBalpAudit.java +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/pharm5/TestPharm5WithBalpAudit.java @@ -40,7 +40,7 @@ public static void setUpClass() { @BeforeEach public void beforeEach() { - FhirAuditRepository.getAuditEvents().clear(); + FhirAuditRepository.clearAuditEvents(); } @Test diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/common-fhir-balp-beans.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/common-fhir-balp-beans.xml index 0fa7688be8..1e756f40c4 100644 --- a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/common-fhir-balp-beans.xml +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/common-fhir-balp-beans.xml @@ -35,7 +35,7 @@ http://openehealth.org/schema/ipf-commons-core.xsd"> - + @@ -44,6 +44,7 @@ http://openehealth.org/schema/ipf-commons-core.xsd"> + @@ -75,6 +76,10 @@ http://openehealth.org/schema/ipf-commons-core.xsd"> class="org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository" factory-method="getServerHttpsPort" /> + +