Skip to content

Commit

Permalink
#431: better error handling for PPQ-2
Browse files Browse the repository at this point in the history
  • Loading branch information
unixoid committed Oct 31, 2023
1 parent defda7b commit 07f4419
Show file tree
Hide file tree
Showing 14 changed files with 192 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import org.hl7.fhir.r4.model.Consent
import org.hl7.fhir.r4.model.IdType
import org.hl7.fhir.r4.model.OperationOutcome
import org.openehealth.ipf.commons.ihe.fhir.chppqm.ChPpqmUtils
import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Status
import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Utils
import org.openehealth.ipf.commons.ihe.xacml20.model.PpqConstants
import org.openehealth.ipf.commons.ihe.xacml20.stub.UnknownPolicySetIdFaultMessage
Expand Down Expand Up @@ -254,23 +255,23 @@ class XacmlToFhirTranslator {
}

static final Map<String, OperationOutcome.IssueType> SAML_STATUS_CODE_TO_FHIR_ISSUE_TYPE_CODE_MAPPING = [
'urn:oasis:names:tc:SAML:2.0:status:Requester' : OperationOutcome.IssueType.INVALID,
'urn:oasis:names:tc:SAML:2.0:status:Responder' : OperationOutcome.IssueType.INVALID,
'urn:oasis:names:tc:SAML:2.0:status:VersionMismatch': OperationOutcome.IssueType.STRUCTURE,
(Xacml20Status.REQUESTER_ERROR.code) : OperationOutcome.IssueType.INVALID,
(Xacml20Status.RESPONDER_ERROR.code) : OperationOutcome.IssueType.INVALID,
(Xacml20Status.VERSION_MISMATCH.code): OperationOutcome.IssueType.STRUCTURE,
]

static final Map<String, Integer> SAML_STATUS_CODE_TO_HTTP_STATUS_CODE_MAPPING = [
'urn:oasis:names:tc:SAML:2.0:status:Requester' : 400,
'urn:oasis:names:tc:SAML:2.0:status:Responder' : 500,
'urn:oasis:names:tc:SAML:2.0:status:VersionMismatch': 500,
(Xacml20Status.REQUESTER_ERROR.code) : 400,
(Xacml20Status.RESPONDER_ERROR.code) : 500,
(Xacml20Status.VERSION_MISMATCH.code): 500,
]

/**
* Translates a CH:PPQ-2 response into a CH:PPQ-5 response.
*/
static List<Consent> translatePpq2To5Response(ResponseType ppq2Response) {
def statusCode = ppq2Response.status.statusCode.value
if (statusCode == Xacml20Utils.SAML20_STATUS_SUCCESS) {
if (statusCode == Xacml20Status.SUCCESS.code) {
def assertion = ppq2Response.assertionOrEncryptedAssertion[0] as AssertionType
def statement = assertion.statementOrAuthnStatementOrAuthzDecisionStatement[0] as XACMLPolicyStatementType
return statement.policyOrPolicySet.collect { toConsent(it as PolicySetType) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@
import org.openehealth.ipf.commons.ihe.fhir.chppqm.chppq4.ChPpq4Validator;
import org.openehealth.ipf.commons.ihe.fhir.chppqm.translation.FhirToXacmlTranslator;
import org.openehealth.ipf.commons.ihe.fhir.chppqm.translation.XacmlToFhirTranslator;
import org.openehealth.ipf.commons.ihe.xacml20.ChPpqMessageCreator;
import org.openehealth.ipf.commons.ihe.xacml20.Xacml20MessageValidator;
import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Utils;
import org.openehealth.ipf.commons.ihe.xacml20.*;
import org.openehealth.ipf.commons.ihe.xacml20.model.PpqConstants;
import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.AddPolicyRequest;
import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.DeletePolicyRequest;
Expand Down Expand Up @@ -259,7 +257,7 @@ public void testPpq2To5ResponseTranslation1() {
public void testPpq2To5ResponseTranslation2() {
boolean correct = false;
try {
ResponseType ppq2Response = PPQ_MESSAGE_CREATOR.createNegativePolicyQueryResponse("urn:oasis:names:tc:SAML:2.0:status:Requester");
ResponseType ppq2Response = PPQ_MESSAGE_CREATOR.createNegativePolicyQueryResponse(new Xacml20Exception(Xacml20Status.REQUESTER_ERROR));
XacmlToFhirTranslator.translatePpq2To5Response(ppq2Response);
} catch (UnclassifiedServerFailureException e) {
assertEquals(400, e.getStatusCode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class ChPpqMessageCreator {
private final String homeCommunityId

ChPpqMessageCreator(String homeCommunityId) {
this.homeCommunityId = Validate.notEmpty(homeCommunityId)
this.homeCommunityId = Validate.notEmpty(homeCommunityId as String, 'Home community ID shall be provided')
}

private AssertionType createAssertion() {
Expand Down Expand Up @@ -143,32 +143,35 @@ class ChPpqMessageCreator {
return query
}

ResponseType createPositivePolicyQueryResponse(List<PolicySetType> policySets) {
def assertion = createAssertion()
assertion.statementOrAuthnStatementOrAuthzDecisionStatement << new XACMLPolicyStatementType(
policyOrPolicySet: policySets,
)
private static ResponseType createResponse(Xacml20Status status, String statusMessage, AssertionType assertion) {
return new ResponseType(
ID: '_' + UUID.randomUUID(),
issueInstant: XML_OBJECT_FACTORY.newXMLGregorianCalendar(new GregorianCalendar()),
version: '2.0',
status: new StatusType(
statusCode: new StatusCodeType(value: Xacml20Utils.SAML20_STATUS_SUCCESS),
statusCode: new StatusCodeType(value: status.code),
statusMessage: statusMessage,
),
assertionOrEncryptedAssertion: [assertion],
)
}

ResponseType createNegativePolicyQueryResponse(String statusCode) {
return new ResponseType(
ID: '_' + UUID.randomUUID(),
issueInstant: XML_OBJECT_FACTORY.newXMLGregorianCalendar(new GregorianCalendar()),
version: '2.0',
status: new StatusType(
statusCode: new StatusCodeType(value: statusCode),
),
assertionOrEncryptedAssertion: [createAssertion()],
ResponseType createPositivePolicyQueryResponse(List<PolicySetType> policySets) {
def assertion = createAssertion()
assertion.statementOrAuthnStatementOrAuthzDecisionStatement << new XACMLPolicyStatementType(
policyOrPolicySet: policySets,
)
return createResponse(Xacml20Status.SUCCESS, null, assertion)
}

ResponseType createNegativePolicyQueryResponse(Xacml20Status status, String statusMessage) {
return createResponse(status, statusMessage, createAssertion())
}

ResponseType createNegativePolicyQueryResponse(Exception exception) {
return (exception instanceof Xacml20Exception)
? createNegativePolicyQueryResponse(exception.status, exception.message)
: createNegativePolicyQueryResponse(Xacml20Status.RESPONDER_ERROR, exception.message)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.commons.ihe.xacml20;

import lombok.Getter;

import java.util.Objects;

public class Xacml20Exception extends Exception {

@Getter
private final Xacml20Status status;

public Xacml20Exception(Xacml20Status status, String message, Throwable cause) {
super(message, cause);
this.status = Objects.requireNonNull(status, "Status code shall be provided");
}

public Xacml20Exception(Xacml20Status status) {
this(status, null, null);
}

public Xacml20Exception(Xacml20Status status, String message) {
this(status, message, null);
}

public Xacml20Exception(Xacml20Status status, Throwable cause) {
this(status, cause.getMessage(), cause);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.commons.ihe.xacml20;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public enum Xacml20Status {

SUCCESS("urn:oasis:names:tc:SAML:2.0:status:Success"),
REQUESTER_ERROR("urn:oasis:names:tc:SAML:2.0:status:Requester"),
RESPONDER_ERROR("urn:oasis:names:tc:SAML:2.0:status:Responder"),
VERSION_MISMATCH("urn:oasis:names:tc:SAML:2.0:status:VersionMismatch"),
;

@Getter
private final String code;

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,10 @@
import org.openehealth.ipf.commons.ihe.xacml20.herasaf.Hl7v3DataTypesInitializer;
import org.openehealth.ipf.commons.ihe.xacml20.herasaf.Hl7v3FunctionsInitializer;
import org.openehealth.ipf.commons.ihe.xacml20.herasaf.types.IiDataTypeAttribute;
import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.AddPolicyRequest;
import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.AssertionBasedRequestType;
import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.DeletePolicyRequest;
import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.UpdatePolicyRequest;
import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.XACMLPolicySetIdReferenceStatementType;
import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.*;
import org.openehealth.ipf.commons.ihe.xacml20.stub.hl7v3.II;
import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.assertion.AssertionType;
import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.ResponseType;
import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.StatusCodeType;
import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.StatusType;
import org.openehealth.ipf.commons.ihe.xacml20.stub.xacml20.saml.assertion.XACMLPolicyStatementType;
import org.openehealth.ipf.commons.ihe.xacml20.stub.xacml20.saml.protocol.XACMLPolicyQueryType;

Expand All @@ -52,8 +46,6 @@
*/
public class Xacml20Utils {

public static final String SAML20_STATUS_SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success";

public static final String ATTRIBUTE_TYPE_PATIENT_ID = "urn:e-health-suisse:2015:epr-spid";
public static final String ELEMENT_NAME_PATIENT_ID = "InstanceIdentifier";

Expand Down Expand Up @@ -99,16 +91,6 @@ public static void initializeHerasaf(Initializer... customInitializers) {
InitializerExecutor.runInitializers();
}

public static ResponseType createXacmlQueryResponse(String status) {
var statusCodeType = new StatusCodeType();
statusCodeType.setValue(status);
var statusType = new StatusType();
statusType.setStatusCode(statusCodeType);
var responseType = new ResponseType();
responseType.setStatus(statusType);
return responseType;
}

/**
* Creates a stream of all policies and policy sets contained in the given PPQ response object.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.openehealth.ipf.commons.audit.types.ParticipantObjectIdType;
import org.openehealth.ipf.commons.ihe.core.atna.AuditStrategySupport;
import org.openehealth.ipf.commons.ihe.core.atna.event.QueryInformationBuilder;
import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Status;
import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Utils;
import org.openehealth.ipf.commons.ihe.xacml20.audit.ChPpqAuditDataset;
import org.openehealth.ipf.commons.ihe.xacml20.audit.codes.PpqEventTypeCodes;
Expand Down Expand Up @@ -86,7 +87,7 @@ public boolean enrichAuditDatasetFromResponse(ChPpqAuditDataset auditDataset, Ob
public EventOutcomeIndicator getEventOutcomeIndicator(ChPpqAuditDataset auditDataset, Object responseObject) {
var response = (ResponseType) responseObject;
try {
if (!Xacml20Utils.SAML20_STATUS_SUCCESS.equals(response.getStatus().getStatusCode().getValue())) {
if (!Xacml20Status.SUCCESS.getCode().equals(response.getStatus().getStatusCode().getValue())) {
return EventOutcomeIndicator.SeriousFailure;
}
if (response.getAssertionOrEncryptedAssertion().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<saml:Assertion ID="id2" Version="2.0" IssueInstant="2016-02-09T09:30:10.5Z">
<saml:Issuer NameQualifier="urn:e-health-suisse:community-index">urn:oid:2.999.1</saml:Issuer>
<saml:Issuer NameQualifier="urn:e-health-suisse:community-index">urn:oid:1.2.3</saml:Issuer>
<saml:Statement xsi:type="xacml-saml:XACMLPolicyStatementType">
<ns5:PolicySet xmlns:ns3="urn:ihe-d:hl7-org:v3"
xmlns:ns5="urn:oasis:names:tc:xacml:2.0:policy:schema:os"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,11 @@
package org.openehealth.ipf.platform.camel.ihe.xacml20.chppq2;

import org.apache.camel.Endpoint;
import org.openehealth.ipf.commons.ihe.ws.JaxWsClientFactory;
import org.openehealth.ipf.commons.ihe.ws.WsInteractionId;
import org.openehealth.ipf.commons.ihe.ws.WsTransactionConfiguration;
import org.openehealth.ipf.commons.ihe.xacml20.CH_PPQ;
import org.openehealth.ipf.commons.ihe.xacml20.audit.ChPpqAuditDataset;
import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.ResponseType;
import org.openehealth.ipf.commons.ihe.xacml20.stub.xacml20.saml.protocol.XACMLPolicyQueryType;
import org.openehealth.ipf.platform.camel.ihe.ws.AbstractWsComponent;
import org.openehealth.ipf.platform.camel.ihe.ws.AbstractWsEndpoint;
import org.openehealth.ipf.platform.camel.ihe.ws.AbstractWsProducer;
import org.openehealth.ipf.platform.camel.ihe.ws.SimpleWsProducer;
import org.openehealth.ipf.platform.camel.ihe.xacml20.Xacml20Endpoint;

import java.util.Map;

Expand All @@ -43,12 +36,7 @@ public ChPpq2Component() {

@Override
protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) {
return new Xacml20Endpoint(uri, remaining, this, parameters, ChPpq2Service.class) {
@Override
public AbstractWsProducer<ChPpqAuditDataset, WsTransactionConfiguration<ChPpqAuditDataset>, ?, ?> getProducer(AbstractWsEndpoint<ChPpqAuditDataset, WsTransactionConfiguration<ChPpqAuditDataset>> endpoint, JaxWsClientFactory<ChPpqAuditDataset> clientFactory) {
return new SimpleWsProducer<>(this, clientFactory, XACMLPolicyQueryType.class, ResponseType.class);
}
};
return new ChPpq2Endpoint(uri, remaining, this, parameters);
}

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

import org.openehealth.ipf.commons.ihe.ws.JaxWsClientFactory;
import org.openehealth.ipf.commons.ihe.ws.WsInteractionId;
import org.openehealth.ipf.commons.ihe.ws.WsTransactionConfiguration;
import org.openehealth.ipf.commons.ihe.xacml20.audit.ChPpqAuditDataset;
import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.ResponseType;
import org.openehealth.ipf.commons.ihe.xacml20.stub.xacml20.saml.protocol.XACMLPolicyQueryType;
import org.openehealth.ipf.platform.camel.ihe.ws.*;
import org.openehealth.ipf.platform.camel.ihe.xacml20.Xacml20Endpoint;

import java.util.Map;

public class ChPpq2Endpoint extends Xacml20Endpoint {

public ChPpq2Endpoint(
String endpointUri,
String address,
AbstractWsComponent<ChPpqAuditDataset, WsTransactionConfiguration<ChPpqAuditDataset>, ? extends WsInteractionId<WsTransactionConfiguration<ChPpqAuditDataset>>> component,
Map<String, Object> parameters)
{
super(endpointUri, address, component, parameters, ChPpq2Service.class);
}

@Override
protected AbstractWebService getCustomServiceInstance(AbstractWsEndpoint<ChPpqAuditDataset, WsTransactionConfiguration<ChPpqAuditDataset>> endpoint) {
return new ChPpq2Service(endpoint.getHomeCommunityId());
}

@Override
public AbstractWsProducer<ChPpqAuditDataset, WsTransactionConfiguration<ChPpqAuditDataset>, ?, ?> getProducer(AbstractWsEndpoint<ChPpqAuditDataset, WsTransactionConfiguration<ChPpqAuditDataset>> endpoint, JaxWsClientFactory<ChPpqAuditDataset> clientFactory) {
return new SimpleWsProducer<>(this, clientFactory, XACMLPolicyQueryType.class, ResponseType.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
package org.openehealth.ipf.platform.camel.ihe.xacml20.chppq2;

import lombok.extern.slf4j.Slf4j;
import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Utils;
import org.openehealth.ipf.commons.ihe.xacml20.ChPpqMessageCreator;
import org.openehealth.ipf.commons.ihe.xacml20.chppq2.ChPpq2PortType;
import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.ResponseType;
import org.openehealth.ipf.commons.ihe.xacml20.stub.xacml20.saml.protocol.XACMLPolicyQueryType;
Expand All @@ -30,15 +30,19 @@
@Slf4j
public class ChPpq2Service extends AbstractWebService implements ChPpq2PortType {

private final ChPpqMessageCreator messageCreator;

public ChPpq2Service(String homeCommunityId) {
this.messageCreator = new ChPpqMessageCreator(homeCommunityId);
}

@Override
public ResponseType policyQuery(XACMLPolicyQueryType request) {
var result = process(request);
var exception = Exchanges.extractException(result);
if (exception != null) {
log.debug(getClass().getSimpleName() + " service failed", exception);
var response = Xacml20Utils.createXacmlQueryResponse("urn:oasis:names:tc:SAML:2.0:status:Responder");
response.getStatus().setStatusMessage(exception.getMessage());
return response;
return messageCreator.createNegativePolicyQueryResponse(exception);
}
return result.getMessage().getBody(ResponseType.class);
}
Expand Down
Loading

0 comments on commit 07f4419

Please sign in to comment.