View Javadoc
1   /*
2    * Copyright 2017 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.platform.camel.ihe.mllp.core.intercept;
18  
19  import ca.uhn.hl7v2.model.Message;
20  import ca.uhn.hl7v2.util.Terser;
21  import org.apache.camel.Exchange;
22  import org.apache.camel.component.mina2.Mina2Constants;
23  import org.apache.commons.lang3.ArrayUtils;
24  import org.apache.commons.lang3.StringUtils;
25  import org.apache.mina.core.session.IoSession;
26  import org.apache.mina.filter.ssl.SslFilter;
27  import org.openehealth.ipf.commons.audit.AuditContext;
28  import org.openehealth.ipf.commons.audit.codes.EventOutcomeIndicator;
29  import org.openehealth.ipf.commons.ihe.hl7v2.audit.AuditUtils;
30  import org.openehealth.ipf.commons.ihe.hl7v2.audit.MllpAuditDataset;
31  import org.openehealth.ipf.modules.hl7.message.MessageUtils;
32  import org.openehealth.ipf.platform.camel.ihe.atna.interceptor.AuditInterceptor;
33  import org.openehealth.ipf.platform.camel.ihe.core.InterceptorSupport;
34  import org.openehealth.ipf.platform.camel.ihe.mllp.core.MllpTransactionEndpoint;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  import javax.naming.ldap.LdapName;
39  import javax.naming.ldap.Rdn;
40  import javax.net.ssl.SSLSession;
41  import java.security.Principal;
42  
43  import static java.util.Objects.requireNonNull;
44  import static org.openehealth.ipf.platform.camel.core.util.Exchanges.resultMessage;
45  
46  /**
47   * Common audit interceptor support for both consumer and producer side of MLLP endpoints
48   *
49   * @author Christian Ohr
50   */
51  public abstract class MllpAuditInterceptorSupport<AuditDatasetType extends MllpAuditDataset> extends InterceptorSupport<MllpTransactionEndpoint<AuditDatasetType>>
52          implements AuditInterceptor<AuditDatasetType, MllpTransactionEndpoint<AuditDatasetType>> {
53  
54      private static final Logger LOG = LoggerFactory.getLogger(MllpAuditInterceptorSupport.class);
55      private final AuditContext auditContext;
56  
57      public MllpAuditInterceptorSupport(AuditContext auditContext) {
58          this.auditContext = requireNonNull(auditContext);
59      }
60  
61      @Override
62      public void process(Exchange exchange) throws Exception {
63          Message msg = exchange.getIn().getBody(Message.class);
64  
65          // skip auditing in case of non-auditable message types
66          if (!isAuditable(msg)) {
67              getWrappedProcessor().process(exchange);
68              return;
69          }
70  
71          AuditDatasetType auditDataset = createAndEnrichAuditDatasetFromRequest(exchange, msg);
72          determineParticipantsAddresses(exchange, auditDataset);
73          extractSslClientUser(exchange, auditDataset);
74  
75          boolean failed = false;
76          try {
77              getWrappedProcessor().process(exchange);
78              Message result = resultMessage(exchange).getBody(Message.class);
79              enrichAuditDatasetFromResponse(auditDataset, result);
80              failed = !AuditUtils.isPositiveAck(result);
81          } catch (Exception e) {
82              failed = true;
83              if (auditDataset != null) {
84                  auditDataset.setEventOutcomeDescription(e.getMessage());
85              }
86              throw e;
87          } finally {
88              if (auditDataset != null) {
89                  auditDataset.setEventOutcomeIndicator(failed ?
90                          EventOutcomeIndicator.MajorFailure :
91                          EventOutcomeIndicator.Success);
92                  getAuditStrategy().doAudit(auditContext, auditDataset);
93              } else {
94                  LOG.warn("Audit dataset is not initialized, no auditing happens");
95              }
96          }
97      }
98  
99      private void extractSslClientUser(Exchange exchange, AuditDatasetType auditDataset) {
100         IoSession ioSession = exchange.getIn().getHeader(Mina2Constants.MINA_IOSESSION, IoSession.class);
101         if (ioSession != null) {
102             SSLSession sslSession = (SSLSession) ioSession.getAttribute(SslFilter.SSL_SESSION);
103             if (sslSession != null) {
104                 try {
105                     Principal principal = sslSession.getPeerPrincipal();
106                     if (principal != null) {
107                         String dn = principal.getName();
108                         LdapName ldapDN = new LdapName(dn);
109                         for (Rdn rdn : ldapDN.getRdns()) {
110                             if (rdn.getType().equalsIgnoreCase("CN")) {
111                                 auditDataset.setSourceUserName((String) rdn.getValue());
112                                 break;
113                             }
114                         }
115                     }
116                 } catch (Exception e) {
117                     LOG.info("Could not extract CN from client certificate", e);
118                 }
119             }
120         }
121     }
122 
123 
124     /**
125      * Creates a new audit dataset and enriches it with data from the request
126      * message.  All exception are ignored.
127      *
128      * @return newly created audit dataset or <code>null</code> when creation failed.
129      */
130     private AuditDatasetType createAndEnrichAuditDatasetFromRequest(Exchange exchange, Message msg) {
131         try {
132             AuditDatasetType auditDataset = getAuditStrategy().createAuditDataset();
133             AuditUtils.enrichGenericAuditDatasetFromRequest(auditDataset, msg);
134             return getAuditStrategy().enrichAuditDatasetFromRequest(auditDataset, msg, exchange.getIn().getHeaders());
135         } catch (Exception e) {
136             LOG.error("Exception when enriching audit dataset from request", e);
137             return null;
138         }
139     }
140 
141     /**
142      * Enriches the given audit dataset with data from the response message.
143      * All exception are ignored.
144      */
145     private void enrichAuditDatasetFromResponse(AuditDatasetType auditDataset, Message msg) {
146         try {
147             getAuditStrategy().enrichAuditDatasetFromResponse(auditDataset, msg, auditContext);
148         } catch (Exception e) {
149             LOG.error("Exception when enriching audit dataset from response", e);
150         }
151     }
152 
153     /**
154      * Checks whether the given message should be audited.
155      * All exceptions are ignored.
156      */
157     private boolean isAuditable(Message message) {
158         try {
159             // no audit for fragments 2..n
160             Terser terser = new Terser(message);
161             return (!ArrayUtils.contains(message.getNames(), "DSC") ||
162                     !StringUtils.isNotEmpty(terser.get("DSC-1"))) &&
163                     getEndpoint().getHl7v2TransactionConfiguration().isAuditable(MessageUtils.eventType(message));
164         } catch (Exception e) {
165             LOG.error("Exception when determining message auditability, no audit will be performed", e);
166             return false;
167         }
168     }
169 }