View Javadoc
1   /*
2    * Copyright 2018 the original author or authors.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package org.openehealth.ipf.commons.ihe.fhir.support;
18  
19  
20  import ca.uhn.fhir.rest.api.MethodOutcome;
21  import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
22  import ca.uhn.fhir.rest.param.TokenParam;
23  import org.hl7.fhir.dstu3.model.Reference;
24  import org.hl7.fhir.instance.model.api.IBaseCoding;
25  import org.hl7.fhir.instance.model.api.IDomainResource;
26  import org.hl7.fhir.instance.model.api.IIdType;
27  import org.openehealth.ipf.commons.audit.AuditContext;
28  import org.openehealth.ipf.commons.audit.codes.EventOutcomeIndicator;
29  import org.openehealth.ipf.commons.audit.model.AuditMessage;
30  import org.openehealth.ipf.commons.ihe.fhir.Constants;
31  import org.openehealth.ipf.commons.ihe.fhir.FhirSearchParameters;
32  import org.openehealth.ipf.commons.ihe.fhir.RequestDetailProvider;
33  import org.openehealth.ipf.commons.ihe.fhir.audit.GenericFhirAuditDataset;
34  import org.openehealth.ipf.commons.ihe.fhir.audit.events.GenericFhirAuditMessageBuilder;
35  
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Optional;
39  import java.util.function.Function;
40  import java.util.stream.Collectors;
41  
42  import static java.util.Objects.requireNonNull;
43  import static org.openehealth.ipf.commons.ihe.fhir.Constants.*;
44  
45  /**
46   * Generic Audit Strategy for FHIR interfaces. The audit written is built alongside what is
47   * defined for IHE FHIR transactions, with the resource type (e.g. Encounter) as event type code.
48   *
49   * @author Christian Ohr
50   */
51  public class GenericFhirAuditStrategy<T extends IDomainResource> extends FhirAuditStrategy<GenericFhirAuditDataset> {
52  
53      private Function<T, Optional<Reference>> patientIdExtractor;
54  
55      /**
56       * @param serverSide         server side auditing
57       * @param patientIdExtractor function that extracts a patient reference from a domain resource
58       */
59      public GenericFhirAuditStrategy(boolean serverSide, Function<T, Optional<Reference>> patientIdExtractor) {
60          super(serverSide);
61          this.patientIdExtractor = requireNonNull(patientIdExtractor);
62      }
63  
64      @Override
65      public GenericFhirAuditDataset createAuditDataset() {
66          return new GenericFhirAuditDataset(isServerSide());
67      }
68  
69      @Override
70      public GenericFhirAuditDataset enrichAuditDatasetFromRequest(GenericFhirAuditDataset auditDataset, Object request, Map<String, Object> parameters) {
71          super.enrichAuditDatasetFromRequest(auditDataset, request, parameters);
72  
73          String resourceType = (String) parameters.get(FHIR_RESOURCE_TYPE_HEADER);
74          if (resourceType == null && RequestDetailProvider.getRequestDetails() != null) {
75              resourceType = RequestDetailProvider.getRequestDetails().getResourceName();
76          }
77          auditDataset.setAffectedResourceType(resourceType);
78  
79          RestOperationTypeEnum operation = (RestOperationTypeEnum) parameters.get(FHIR_OPERATION_HEADER);
80          if (operation == null && RequestDetailProvider.getRequestDetails() != null) {
81              operation = RequestDetailProvider.getRequestDetails().getRestOperationType();
82          }
83          auditDataset.setOperation(operation);
84  
85          // Domain Resource in the request? Extract Patient ID and Sensitivity at this point
86          if (request instanceof IDomainResource) {
87              addResourceData(auditDataset, (T) request);
88          } else if (request instanceof IIdType) {
89              auditDataset.setResourceId((IIdType) request);
90          }
91  
92          if (parameters.containsKey(Constants.FHIR_REQUEST_PARAMETERS)) {
93              String query = (String) parameters.get(HTTP_QUERY);
94              auditDataset.setQueryString(query);
95  
96              FhirSearchParameters searchParameter = (FhirSearchParameters) parameters.get(Constants.FHIR_REQUEST_PARAMETERS);
97              if (searchParameter != null) {
98                  List<TokenParam> tokenParams = searchParameter.getPatientIdParam();
99                  if (tokenParams != null) {
100                     auditDataset.getPatientIds().addAll(
101                             tokenParams.stream()
102                                     .map(t -> t.getValueAsQueryToken(searchParameter.getFhirContext()))
103                                     .collect(Collectors.toList()));
104                 }
105             }
106         }
107         return auditDataset;
108     }
109 
110     @Override
111     public boolean enrichAuditDatasetFromResponse(GenericFhirAuditDataset auditDataset, Object response, AuditContext auditContext) {
112         // Domain Resource in the request? Extract Patient ID and Sensitivity at this point
113         if (response instanceof IDomainResource) {
114             addResourceData(auditDataset, (T) response);
115         }
116         if (response instanceof MethodOutcome) {
117             MethodOutcome methodOutcome = (MethodOutcome) response;
118             if (methodOutcome.getCreated() != null && methodOutcome.getCreated()) {
119                 auditDataset.setEventOutcomeIndicator(EventOutcomeIndicator.Success);
120             }
121             if (methodOutcome.getOperationOutcome() != null) {
122                 super.enrichAuditDatasetFromResponse(auditDataset, methodOutcome.getOperationOutcome(), auditContext);
123             } else {
124                 auditDataset.setEventOutcomeIndicator(EventOutcomeIndicator.Success);
125             }
126             if (methodOutcome.getResource() != null && methodOutcome.getResource() instanceof IDomainResource) {
127                 addResourceData(auditDataset, (T) methodOutcome.getResource());
128             } else if (methodOutcome.getId() != null) {
129                 auditDataset.setResourceId(methodOutcome.getId());
130                 if (methodOutcome.getId().hasResourceType()) {
131                     auditDataset.setAffectedResourceType(methodOutcome.getId().getResourceType());
132                 }
133             }
134         }
135         return super.enrichAuditDatasetFromResponse(auditDataset, response, auditContext);
136     }
137 
138     @Override
139     public AuditMessage[] makeAuditMessage(AuditContext auditContext, GenericFhirAuditDataset auditDataset) {
140         GenericFhirAuditMessageBuilder builder = new GenericFhirAuditMessageBuilder(auditContext, auditDataset)
141                 .addPatients(auditDataset);
142         if (auditDataset.getAffectedResourceType() != null && auditDataset.getQueryString() != null) {
143             builder.addQueryParticipantObject(auditDataset);
144         } else if (auditDataset.getResourceId() != null) {
145             builder.addResourceParticipantObject(auditDataset);
146         }
147         return builder.getMessages();
148 
149     }
150 
151     private void addResourceData(GenericFhirAuditDataset auditDataset, T resource) {
152         auditDataset.setResourceId(resource.getIdElement());
153         if (resource.getIdElement().hasResourceType()) {
154             auditDataset.setAffectedResourceType(resource.getIdElement().getResourceType());
155         }
156         patientIdExtractor.apply(resource).ifPresent(patient ->
157                 auditDataset.getPatientIds().add(patient.getResource() != null ?
158                         patient.getResource().getIdElement().toUnqualifiedVersionless().getValue() :
159                         patient.getReference()));
160 
161         List<? extends IBaseCoding> securityLabels = resource.getMeta().getSecurity();
162         if (!securityLabels.isEmpty()) {
163             auditDataset.setSecurityLabel(securityLabels.get(0).getCode());
164         }
165     }
166 }