Skip to content

Commit

Permalink
#449: trace context ID in FHIR based transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
unixoid committed Aug 1, 2024
1 parent 8bcc76d commit c19797f
Show file tree
Hide file tree
Showing 17 changed files with 181 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,17 @@ public AuditContext auditContext(IpfAtnaConfigurationProperties config,
AuditExceptionHandler auditExceptionHandler,
AuditMessagePostProcessor auditMessagePostProcessor,
WsAuditDatasetEnricher wsAuditDatasetEnricher,
FhirAuditDatasetEnricher fhirAuditDatasetEnricher,
@Value("${spring.application.name}") String appName) {
if (config.getBalp() != null) {
return balpConfiguration(defaultContextConfiguration(new DefaultBalpAuditContext(), config,
auditTransmissionProtocol, auditMessageQueue, tlsParameters, auditMetadataProvider,
auditExceptionHandler, auditMessagePostProcessor, wsAuditDatasetEnricher, appName), config);
auditExceptionHandler, auditMessagePostProcessor, wsAuditDatasetEnricher,
fhirAuditDatasetEnricher, appName), config);
} else {
return defaultContextConfiguration(new DefaultAuditContext(), config, auditTransmissionProtocol,
auditMessageQueue, tlsParameters, auditMetadataProvider, auditExceptionHandler,
auditMessagePostProcessor, wsAuditDatasetEnricher, appName);
auditMessagePostProcessor, wsAuditDatasetEnricher, fhirAuditDatasetEnricher, appName);
}
}

Expand All @@ -73,6 +75,7 @@ private <T extends DefaultAuditContext> T defaultContextConfiguration(T auditCon
AuditExceptionHandler auditExceptionHandler,
AuditMessagePostProcessor auditMessagePostProcessor,
WsAuditDatasetEnricher wsAuditDatasetEnricher,
FhirAuditDatasetEnricher fhirAuditDatasetEnricher,
@Value("${spring.application.name}") String appName) {

auditContext.setAuditEnabled(config.isAuditEnabled());
Expand All @@ -97,6 +100,9 @@ private <T extends DefaultAuditContext> T defaultContextConfiguration(T auditCon
if (wsAuditDatasetEnricher != WsAuditDatasetEnricher.NONE) {
auditContext.setWsAuditDatasetEnricher(wsAuditDatasetEnricher);
}
if (fhirAuditDatasetEnricher != FhirAuditDatasetEnricher.NONE) {
auditContext.setFhirAuditDatasetEnricher(fhirAuditDatasetEnricher);
}

return auditContext;
}
Expand Down Expand Up @@ -223,6 +229,15 @@ public WsAuditDatasetEnricher wsAuditDatasetEnricher(IpfAtnaConfigurationPropert
return WsAuditDatasetEnricher.NONE;
}

@Bean
@ConditionalOnMissingBean
public FhirAuditDatasetEnricher fhirAuditDatasetEnricher(IpfAtnaConfigurationProperties config) throws Exception {
if (config.getFhirAuditDatasetEnricherClass() != null) {
return config.getFhirAuditDatasetEnricherClass().getConstructor().newInstance();
}
return FhirAuditDatasetEnricher.NONE;
}

// Some audit event listeners

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import lombok.Getter;
import lombok.Setter;
import org.openehealth.ipf.commons.audit.AuditMessagePostProcessor;
import org.openehealth.ipf.commons.audit.FhirAuditDatasetEnricher;
import org.openehealth.ipf.commons.audit.WsAuditDatasetEnricher;
import org.openehealth.ipf.commons.audit.codes.AuditSourceType;
import org.openehealth.ipf.commons.audit.handler.AuditExceptionHandler;
Expand Down Expand Up @@ -108,6 +109,12 @@ public class IpfAtnaConfigurationProperties {
@Getter @Setter
private Class<? extends WsAuditDatasetEnricher> wsAuditDatasetEnricherClass;

/**
* Class of the optional audit dataset enricher for FHIR based transactions.
*/
@Getter @Setter
private Class<? extends FhirAuditDatasetEnricher> fhirAuditDatasetEnricherClass;

@Getter @Setter
private Balp balp;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ public interface AuditContext {
*/
<T extends WsAuditDatasetEnricher> T getWsAuditDatasetEnricher();

/**
* @return Audit dataset enricher for FHIR based transactions.
*/
<T extends FhirAuditDatasetEnricher> T getFhirAuditDatasetEnricher();

/**
* @return a post-processor for audit messages (defaults to a NO-OP implementation
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ public class DefaultAuditContext implements AuditContext {
@Setter
private WsAuditDatasetEnricher wsAuditDatasetEnricher;

@Setter
private FhirAuditDatasetEnricher fhirAuditDatasetEnricher;

public String getAuditRepositoryTransport() {
return auditTransmissionProtocol.getTransportName();
}
Expand Down Expand Up @@ -146,4 +149,10 @@ public <T extends WsAuditDatasetEnricher> T getWsAuditDatasetEnricher() {
return (T) wsAuditDatasetEnricher;
}

@SuppressWarnings("unchecked")
@Override
public <T extends FhirAuditDatasetEnricher> T getFhirAuditDatasetEnricher() {
return (T) fhirAuditDatasetEnricher;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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;

/**
* Marker interface for FHIR audit dataset enrichers.
*/
public interface FhirAuditDatasetEnricher {

FhirAuditDatasetEnricher NONE = new FhirAuditDatasetEnricher() {};

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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;

import org.openehealth.ipf.commons.ihe.core.atna.AuditDataset;

import java.util.Map;

/**
* Interface for FHIR ATNA audit dataset enrichers.
* Each implementing class shall have a default constructor and be thread-safe.
*
* @author Dmytro Rud
*/
public interface FhirAuditDatasetEnricher extends org.openehealth.ipf.commons.audit.FhirAuditDatasetEnricher {

void enrichAuditDataset(AuditDataset auditDataset, Object request, Map<String, Object> parameters);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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;

import org.openehealth.ipf.commons.ihe.core.atna.AuditDataset;

import java.util.List;
import java.util.Map;

import static org.openehealth.ipf.commons.ihe.fhir.Constants.HTTP_INCOMING_HEADERS;
import static org.openehealth.ipf.commons.ihe.fhir.Constants.HTTP_OUTGOING_HEADERS;

/**
* Audit dataset enricher for FHIR based transactions which implements requirements
* of the Swiss Electronic Patient Record.
*
* @author Dmytro Rud
*/
public class SwissEprFhirAuditDatasetEnricher implements FhirAuditDatasetEnricher {

@Override
public void enrichAuditDataset(AuditDataset auditDataset, Object request, Map<String, Object> parameters) {
// the outbound value, whenever present, shall override the incoming one
extractW3cTraceContextId(parameters, HTTP_INCOMING_HEADERS, auditDataset);
extractW3cTraceContextId(parameters, HTTP_OUTGOING_HEADERS, auditDataset);
}

private static void extractW3cTraceContextId(Map<String, Object> parameters, String key, AuditDataset auditDataset) {
Object value = parameters.get(key);
if (value != null) {
Map<String, List<String>> headers = (Map<String, List<String>>) value;
for (String name : headers.keySet()) {
if ("traceparent".equalsIgnoreCase(name)) {
auditDataset.setW3cTraceContextId(headers.get(name).get(0));
return;
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ public ChPpqmAuditDataset enrichAuditDatasetFromRequest(ChPpqmAuditDataset audit
case "DELETE":
auditDataset.setAction(EventActionCode.Delete);
auditDataset.getPolicyAndPolicySetIds().add((String) request);
// TODO: take patient ID from XUA/IUA token
break;
default:
log.error("Unsupported HTTP method '{}'", method);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ class SwissEprWsAuditDatasetEnricher extends XuaWsAuditDatasetEnricher {
GPathResult xuaToken = extractXuaToken(message, headerDirection)
if (xuaToken != null) {
extractXuaTokenElements(xuaToken, auditDataset)
def iheUser = auditDataset.humanUsers[0]
conditionallyAddHumanUser(createMainEprUser(xuaToken, iheUser), auditDataset)
conditionallyAddHumanUser(createAdditionalEprUser(xuaToken, iheUser, auditDataset.purposesOfUse), auditDataset)
if (!auditDataset.humanUsers.empty) {
def iheUser = auditDataset.humanUsers[0]
conditionallyAddHumanUser(createMainEprUser(xuaToken, iheUser), auditDataset)
conditionallyAddHumanUser(createAdditionalEprUser(xuaToken, iheUser, auditDataset.purposesOfUse), auditDataset)
}
}

extractW3cTraceContextId(message, auditDataset)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package org.openehealth.ipf.platform.camel.ihe.fhir.core.intercept.consumer;

import org.apache.camel.Exchange;
import org.openehealth.ipf.commons.audit.AuditContext;
import org.openehealth.ipf.commons.ihe.core.atna.AuditDataset;
import org.openehealth.ipf.commons.ihe.fhir.Constants;
import org.openehealth.ipf.commons.ihe.fhir.audit.FhirAuditDatasetEnricher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -75,4 +77,12 @@ public static Optional<String> extractAuthorizationHeader(Exchange exchange) {
}
return Optional.empty();
}

public static void enrichAuditDataset(AuditDataset auditDataset, AuditContext auditContext, Exchange exchange) {
if (auditContext.getFhirAuditDatasetEnricher() != null) {
FhirAuditDatasetEnricher enricher = auditContext.getFhirAuditDatasetEnricher();
enricher.enrichAuditDataset(auditDataset, exchange.getIn().getBody(), exchange.getIn().getHeaders());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@
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;


Expand Down Expand Up @@ -118,6 +114,7 @@ private AuditDatasetType createAndEnrichAuditDatasetFromRequest(AuditStrategy<Au
// TODO Also extract basic auth user?
AuditInterceptorUtils.extractClientCertificateCommonName(exchange, auditDataset);
AuditInterceptorUtils.extractAuthorizationHeader(exchange).ifPresent(auditDataset::setAuthorization);
AuditInterceptorUtils.enrichAuditDataset(auditDataset, auditContext, exchange);
return strategy.enrichAuditDatasetFromRequest(auditDataset, msg, exchange.getIn().getHeaders());
} catch (Exception e) {
LOG.error("Exception when enriching audit dataset from request", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.openehealth.ipf.platform.camel.ihe.atna.interceptor.AuditInterceptor;
import org.openehealth.ipf.platform.camel.ihe.core.InterceptorSupport;
import org.openehealth.ipf.platform.camel.ihe.fhir.core.FhirEndpoint;
import org.openehealth.ipf.platform.camel.ihe.fhir.core.intercept.consumer.AuditInterceptorUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -109,6 +110,7 @@ private AuditDatasetType createAndEnrichAuditDatasetFromRequest(AuditStrategy<Au
try {
var auditDataset = strategy.createAuditDataset();
auditDataset.setSourceUserId(auditContext.getAuditValueIfMissing());
AuditInterceptorUtils.enrichAuditDataset(auditDataset, auditContext, exchange);
return strategy.enrichAuditDatasetFromRequest(auditDataset, msg, exchange.getIn().getHeaders());
} catch (Exception e) {
LOG.error("Exception when enriching audit dataset from request", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ public void testUpdate1() throws Exception {

@Test
public void testUpdate2() throws Exception {
String traceContextId = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01";

Consent consent = create201Consent(createUuid(), "123456789012345678");
consent.setId(createUuid());

Expand All @@ -122,11 +124,17 @@ public void testUpdate2() throws Exception {
.withAdditionalHeader("Header2", "Value2")
.withAdditionalHeader("Authorization", "Bearer d2h5IGFyZSB5b3UgcmVhZGluZyB0aGlzPw==")
.withAdditionalHeader("Header2", "Value3")
.withAdditionalHeader("TraceParent", traceContextId)
.withAdditionalHeader("Header2", "Value1")
.execute();

List<AuditMessage> auditMessages = auditSender.getMessages();
assertEquals(1, auditMessages.size());

AuditMessage auditMessage = auditMessages.get(0);
assertEquals(3, auditMessage.getParticipantObjectIdentifications().size());
assertEquals(traceContextId, auditMessage.getParticipantObjectIdentifications().get(0).getParticipantObjectID());

log.info("");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ http://openehealth.org/schema/ipf-commons-core.xsd">
<property name="auditMessageQueue" ref="mockedSender"/>
<property name="auditSourceId" value="IPF"/>
<property name="auditEnterpriseSiteId" value="IPF"/>
<property name="fhirAuditDatasetEnricher">
<bean class="org.openehealth.ipf.commons.ihe.fhir.audit.SwissEprFhirAuditDatasetEnricher"/>
</property>
</bean>

<bean id="mockedSender" class="org.openehealth.ipf.commons.audit.queue.RecordingAuditMessageQueue"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ private Iti68AuditDataset createAndEnrichAuditDatasetFromRequest(AuditStrategy<I

// TODO Also extract basic auth user?
AuditInterceptorUtils.extractClientCertificateCommonName(exchange, auditDataset);
AuditInterceptorUtils.enrichAuditDataset(auditDataset, auditContext, exchange);

return strategy.enrichAuditDatasetFromRequest(auditDataset, msg, exchange.getIn().getHeaders());
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ private FhirAuditDataset createAndEnrichAuditDatasetFromRequest(AuditStrategy<Fh

// TODO Also extract basic auth user?
AuditInterceptorUtils.extractClientCertificateCommonName(exchange, auditDataset);
AuditInterceptorUtils.enrichAuditDataset(auditDataset, auditContext, exchange);

return strategy.enrichAuditDatasetFromRequest(auditDataset, msg, exchange.getIn().getHeaders());
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ private Iti68AuditDataset createAndEnrichAuditDatasetFromRequest(AuditStrategy<I

// TODO Also extract basic auth user?
AuditInterceptorUtils.extractClientCertificateCommonName(exchange, auditDataset);
AuditInterceptorUtils.enrichAuditDataset(auditDataset, auditContext, exchange);

return strategy.enrichAuditDatasetFromRequest(auditDataset, msg, exchange.getIn().getHeaders());
} catch (Exception e) {
Expand Down

0 comments on commit c19797f

Please sign in to comment.