1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.openehealth.ipf.commons.ihe.xua;
17
18 import lombok.extern.slf4j.Slf4j;
19 import org.apache.commons.lang3.StringUtils;
20 import org.apache.cxf.binding.soap.SoapMessage;
21 import org.apache.cxf.headers.Header;
22 import org.openehealth.ipf.commons.audit.types.ActiveParticipantRoleId;
23 import org.openehealth.ipf.commons.audit.types.PurposeOfUse;
24 import org.openehealth.ipf.commons.ihe.ws.cxf.audit.AbstractAuditInterceptor;
25 import org.openehealth.ipf.commons.ihe.ws.cxf.audit.WsAuditDataset;
26 import org.openehealth.ipf.commons.ihe.ws.cxf.audit.XuaProcessor;
27 import org.opensaml.core.config.ConfigurationService;
28 import org.opensaml.core.config.InitializationException;
29 import org.opensaml.core.config.InitializationService;
30 import org.opensaml.core.xml.XMLObject;
31 import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
32 import org.opensaml.core.xml.io.Unmarshaller;
33 import org.opensaml.core.xml.io.UnmarshallerFactory;
34 import org.opensaml.core.xml.io.UnmarshallingException;
35 import org.opensaml.saml.common.xml.SAMLConstants;
36 import org.opensaml.saml.saml2.core.*;
37 import org.opensaml.soap.wssecurity.WSSecurityConstants;
38 import org.w3c.dom.Document;
39 import org.w3c.dom.Element;
40 import org.w3c.dom.Node;
41 import org.w3c.dom.NodeList;
42
43 import javax.xml.namespace.QName;
44 import java.util.*;
45
46 import static org.openehealth.ipf.commons.audit.types.ActiveParticipantRoleId.of;
47 import static org.openehealth.ipf.commons.ihe.ws.utils.SoapUtils.SOAP_NS_URIS;
48 import static org.openehealth.ipf.commons.ihe.ws.utils.SoapUtils.getElementNS;
49
50
51
52
53
54
55 @Slf4j
56 public class BasicXuaProcessor implements XuaProcessor {
57
58
59
60
61
62
63
64
65 private static final String XUA_SAML_ASSERTION = AbstractAuditInterceptor.class.getName() + ".XUA_SAML_ASSERTION";
66
67 private static final Set<String> WSSE_NS_URIS = new HashSet<>(Arrays.asList(
68 WSSecurityConstants.WSSE_NS,
69 WSSecurityConstants.WSSE11_NS));
70
71 private static final String PURPOSE_OF_USE_ATTRIBUTE_NAME = "urn:oasis:names:tc:xspa:1.0:subject:purposeofuse";
72 private static final String SUBJECT_NAME_ATTRIBUTE_NAME = "urn:oasis:names:tc:xspa:1.0:subject:subject-id";
73 private static final String SUBJECT_ROLE_ATTRIBUTE_NAME = "urn:oasis:names:tc:xacml:2.0:subject:role";
74 private static final String PATIENT_ID_ATTRIBUTE_NAME = "urn:oasis:names:tc:xacml:2.0:resource:resource-id";
75
76 private static final QName PURPOSE_OF_USE_ELEMENT_NAME = new QName("urn:hl7-org:v3", "PurposeOfUse");
77 private static final QName SUBJECT_ROLE_ELEMENT_NAME = new QName("urn:hl7-org:v3", "Role");
78
79
80 public static final Map<ActiveParticipantRoleId, ActiveParticipantRoleId> PRINCIPAL_ASSISTANT_ROLE_RELATIONSHIPS;
81 static {
82 PRINCIPAL_ASSISTANT_ROLE_RELATIONSHIPS = new HashMap<>();
83
84 for (String codingSystemId : new String[]{"2.16.756.5.30.1.127.3.10.4", "2.16.756.5.30.1.127.3.10.6"}) {
85 PRINCIPAL_ASSISTANT_ROLE_RELATIONSHIPS.put(of("PAT", codingSystemId, ""), of("REP", codingSystemId, "Representative"));
86 PRINCIPAL_ASSISTANT_ROLE_RELATIONSHIPS.put(of("HCP", codingSystemId, ""), of("ASSISTANT", codingSystemId, "Assistant"));
87 }
88 }
89
90 private static final UnmarshallerFactory SAML_UNMARSHALLER_FACTORY;
91 static {
92 try {
93 InitializationService.initialize();
94 XMLObjectProviderRegistry registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
95 SAML_UNMARSHALLER_FACTORY = registry.getUnmarshallerFactory();
96 } catch (InitializationException e) {
97 throw new RuntimeException(e);
98 }
99 }
100
101 private static Element extractAssertionElementFromCxfMessage(SoapMessage message, Header.Direction headerDirection) {
102 Header header = message.getHeader(new QName(WSSecurityConstants.WSSE_NS, "Security"));
103 if (!((header != null) &&
104 headerDirection.equals(header.getDirection()) &&
105 (header.getObject() instanceof Element))) {
106 return null;
107 }
108
109 Element headerElem = (Element) header.getObject();
110 NodeList nodeList = headerElem.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "Assertion");
111 return (Element) nodeList.item(0);
112 }
113
114 private static Element extractAssertionElementFromDom(SoapMessage message) {
115 Document document = (Document) message.getContent(Node.class);
116 if (document == null) {
117 return null;
118 }
119 Element element = getElementNS(document.getDocumentElement(), SOAP_NS_URIS, "Header");
120 element = getElementNS(element, WSSE_NS_URIS, "Security");
121 return getElementNS(element, Collections.singleton(SAMLConstants.SAML20_NS), "Assertion");
122 }
123
124
125
126
127
128
129
130
131
132 public void extractXuaUserNameFromSaml2Assertion(
133 SoapMessage message,
134 Header.Direction headerDirection,
135 WsAuditDataset auditDataset)
136 {
137 Assertion assertion = null;
138
139
140 Object o = message.getContextualProperty(XUA_SAML_ASSERTION);
141 if (o instanceof Assertion) {
142 assertion = (Assertion) o;
143 }
144
145
146 if (assertion == null) {
147 Element assertionElem = extractAssertionElementFromCxfMessage(message, headerDirection);
148 if (assertionElem == null) {
149 assertionElem = extractAssertionElementFromDom(message);
150 }
151 if (assertionElem == null) {
152 return;
153 }
154
155 Unmarshaller unmarshaller = SAML_UNMARSHALLER_FACTORY.getUnmarshaller(assertionElem);
156 try {
157 assertion = (Assertion) unmarshaller.unmarshall(assertionElem);
158 } catch (UnmarshallingException e) {
159 log.warn("Cannot extract SAML assertion from the WS-Security SOAP header", e);
160 return;
161 }
162
163 message.getExchange().put(XUA_SAML_ASSERTION, assertion);
164 }
165
166 WsAuditDataset.HumanUser mainUser = new WsAuditDataset.HumanUser();
167 WsAuditDataset.HumanUser assistantUser = new WsAuditDataset.HumanUser();
168
169
170 if (assertion.getSubject() != null) {
171 mainUser.setId(createXuaUserId(assertion.getIssuer(), assertion.getSubject().getNameID()));
172
173
174 for (SubjectConfirmation subjectConfirmation : assertion.getSubject().getSubjectConfirmations()) {
175 assistantUser.setId(createXuaUserId(assertion.getIssuer(), subjectConfirmation.getNameID()));
176 AttributeStatement statement = new AttributeStatementExtractor().extractAttributeStatement(subjectConfirmation);
177 statement.getAttributes().stream()
178 .filter(attr -> SUBJECT_NAME_ATTRIBUTE_NAME.equals(attr.getName()))
179 .findAny()
180 .ifPresent(attr -> assistantUser.setName(extractSingleStringAttributeValue(attr)));
181 }
182 }
183
184
185 for (AttributeStatement statement : assertion.getAttributeStatements()) {
186 for (Attribute attribute : statement.getAttributes()) {
187 switch (attribute.getName()) {
188 case PURPOSE_OF_USE_ATTRIBUTE_NAME:
189 auditDataset.setPurposesOfUse(extractPurposeOfUse(attribute, PURPOSE_OF_USE_ELEMENT_NAME));
190 break;
191 case SUBJECT_NAME_ATTRIBUTE_NAME:
192 mainUser.setName(extractSingleStringAttributeValue(attribute));
193 break;
194 case SUBJECT_ROLE_ATTRIBUTE_NAME:
195 extractActiveParticipantRoleId(attribute, SUBJECT_ROLE_ELEMENT_NAME).forEach(mainUserRoleId -> {
196 mainUser.getRoles().add(mainUserRoleId);
197 ActiveParticipantRoleId normalizedMainUserRoleId = of(mainUserRoleId.getCode(), mainUserRoleId.getCodeSystemName(), "");
198 ActiveParticipantRoleId assistantUserRoleId = PRINCIPAL_ASSISTANT_ROLE_RELATIONSHIPS.get(normalizedMainUserRoleId);
199 if (assistantUserRoleId != null) {
200 assistantUser.getRoles().add(assistantUserRoleId);
201 }
202 });
203 break;
204 case PATIENT_ID_ATTRIBUTE_NAME:
205 auditDataset.setXuaPatientId(extractSingleStringAttributeValue(attribute));
206 break;
207 }
208 }
209 }
210
211 if (!mainUser.isEmpty()) {
212 auditDataset.getHumanUsers().add(mainUser);
213 }
214 if (!assistantUser.isEmpty()) {
215 auditDataset.getHumanUsers().add(assistantUser);
216 }
217 }
218
219 private static String createXuaUserId(Issuer issuer, NameID nameID) {
220 String userName = (nameID != null) ? nameID.getValue() : null;
221 String issuerName = (issuer != null) ? issuer.getValue() : null;
222 String spProvidedId = (nameID != null) ? StringUtils.stripToEmpty(nameID.getSPProvidedID()) : null;
223
224 return StringUtils.isNoneEmpty(issuerName, userName)
225 ? spProvidedId + '<' + userName + '@' + issuerName + '>'
226 : null;
227 }
228
229 private static String extractSingleStringAttributeValue(Attribute attribute) {
230 List<XMLObject> attributeValues = attribute.getAttributeValues();
231 return ((attributeValues != null) && (!attributeValues.isEmpty()) && (attributeValues.get(0) != null) && (attributeValues.get(0).getDOM() != null))
232 ? attributeValues.get(0).getDOM().getTextContent()
233 : null;
234 }
235
236 private static PurposeOfUse[] extractPurposeOfUse(Attribute attribute, QName valueElementName) {
237 List<PurposeOfUse> targetCollection = new ArrayList<>();
238 for (XMLObject value : attribute.getAttributeValues()) {
239 if (value.getDOM() != null) {
240 NodeList nodeList = value.getDOM().getElementsByTagNameNS(valueElementName.getNamespaceURI(), valueElementName.getLocalPart());
241 for (int i = 0; i < nodeList.getLength(); ++i) {
242 Element elem = (Element) nodeList.item(i);
243 targetCollection.add(elementToPurposeOfUse(elem));
244 }
245 }
246 }
247 return targetCollection.toArray(new PurposeOfUse[targetCollection.size()]);
248
249 }
250
251 private static PurposeOfUse elementToPurposeOfUse(Element element) {
252 return PurposeOfUse.of(
253 element.getAttribute("code"),
254 element.getAttribute("codeSystem"),
255 element.getAttribute("displayName")
256 );
257 }
258
259 private static List<ActiveParticipantRoleId> extractActiveParticipantRoleId(Attribute attribute, QName valueElementName) {
260 List<ActiveParticipantRoleId> result = new ArrayList<>();
261 for (XMLObject value : attribute.getAttributeValues()) {
262 if (value.getDOM() != null) {
263 NodeList nodeList = value.getDOM().getElementsByTagNameNS(valueElementName.getNamespaceURI(), valueElementName.getLocalPart());
264 for (int i = 0; i < nodeList.getLength(); ++i) {
265 Element elem = (Element) nodeList.item(i);
266 result.add(elementToActiveParticipantRoleId(elem));
267 }
268 }
269 }
270 return result;
271 }
272
273 private static ActiveParticipantRoleId elementToActiveParticipantRoleId(Element element) {
274 return ActiveParticipantRoleId.of(
275 element.getAttribute("code"),
276 element.getAttribute("codeSystem"),
277 element.getAttribute("displayName")
278 );
279 }
280 }