From e1f365955aa22cdf8e44429af2b744388ce0c05b Mon Sep 17 00:00:00 2001 From: Christof Rabensteiner Date: Tue, 28 May 2019 10:48:20 +0200 Subject: Integrate Sign.Verification and Improve Error Handling of Pipeline - Ensure proper communication of errors between pipeline and mzs service by converting MoaZSExceptions into DeliveryRequestStatus messages. - Revise MoaZSException: Add optional fields; those fields are a) helpful to construct meaningful error messages and b) optional because, depending on where an exception appears, either existent or non-existent and thus optional. Add inner-class Builder. - Integrate Signature Verification into pipeline and add Stub for SignatureVerification. - Move TNVZResponse's Mimetype check into dedicated class (Reason: separate abstration layers). - Update api changes in testcases. --- pom.xml | 3 +- src/main/java/at/gv/egiz/moazs/MoaZSException.java | 123 +++++++++++++++++-- src/main/java/at/gv/egiz/moazs/msg/MsgClient.java | 6 +- .../at/gv/egiz/moazs/msg/MsgClientFactory.java | 9 +- .../at/gv/egiz/moazs/msg/SignatureVerifier.java | 13 ++ ...StoreSOAPBodyBinaryInRepositoryInterceptor.java | 7 +- src/main/java/at/gv/egiz/moazs/mzs/MzsService.java | 10 +- .../gv/egiz/moazs/pipeline/DeliveryPipeline.java | 6 +- .../moazs/pipeline/SameThreadDeliveryPipeline.java | 131 +++++++++++++-------- .../java/at/gv/egiz/moazs/scheme/NameSpace.java | 1 + .../java/at/gv/egiz/moazs/scheme/SOAPUtils.java | 4 +- .../java/at/gv/egiz/moazs/tnvz/TnvzClient.java | 12 +- .../at/gv/egiz/moazs/tnvz/TnvzResultVerifier.java | 63 ++++++++++ src/test/java/at/gv/egiz/moazs/MsgClientTest.java | 8 +- .../egiz/moazs/SameThreadDeliveryPipelineTest.java | 95 +++++++++++---- 15 files changed, 388 insertions(+), 103 deletions(-) create mode 100644 src/main/java/at/gv/egiz/moazs/msg/SignatureVerifier.java create mode 100644 src/main/java/at/gv/egiz/moazs/tnvz/TnvzResultVerifier.java diff --git a/pom.xml b/pom.xml index 95dd298..9359744 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ 27.1-jre 1.5 0.8.3 + 2.0.7-snapshot http://reference.e-government.gv.at/namespace/zustellung/mzs/app2mzs# http://reference.e-government.gv.at/namespace/zustellung/mzs/persondata# http://reference.e-government.gv.at/namespace/zustellung/mzs/app2mzs.wsdl @@ -101,7 +102,7 @@ at.gv.util egovutils - 2.0.7-snapshot + ${egovutils.version} diff --git a/src/main/java/at/gv/egiz/moazs/MoaZSException.java b/src/main/java/at/gv/egiz/moazs/MoaZSException.java index 11d9b4e..db04241 100644 --- a/src/main/java/at/gv/egiz/moazs/MoaZSException.java +++ b/src/main/java/at/gv/egiz/moazs/MoaZSException.java @@ -1,20 +1,129 @@ package at.gv.egiz.moazs; +import at.gv.zustellung.msg.xsd.DeliveryRequestStatusType; +import at.gv.zustellung.msg.xsd.DeliveryRequestType; +import at.gv.zustellung.tnvz.xsd.PersonResultType; +import org.springframework.lang.Nullable; + public class MoaZSException extends RuntimeException { - public MoaZSException(String s, Throwable throwable) { - super(s, throwable); - } - public MoaZSException(String s) { - super(s); + public static final String ERROR_MZS_MIMETYPE_MISSMATCH = "8001"; + public static final String ERROR_MOASP_SIGNATURE_INVALID = "7001"; + + @Nullable + private final String errorCode; + @Nullable + private final PersonResultType tnvzResult; + @Nullable + private final DeliveryRequestStatusType msgResult; + @Nullable + private final DeliveryRequestType msgRequest; + @Nullable + private final at.gv.zustellung.app2mzs.xsd.DeliveryRequestType mzsRequest; + + private MoaZSException(String message, Throwable cause, String errorCode, PersonResultType tnvzResult, + DeliveryRequestStatusType msgResult, DeliveryRequestType msgRequest, + at.gv.zustellung.app2mzs.xsd.DeliveryRequestType mzsRequest) { + super(message, cause); + this.errorCode = errorCode; + this.tnvzResult = tnvzResult; + this.msgResult = msgResult; + this.msgRequest = msgRequest; + this.mzsRequest = mzsRequest; } public static MoaZSException moaZSException(String formatString, Object... objects) { - return new MoaZSException(String.format(formatString, objects)); + return moaZSExceptionBuilder(formatString, objects).build(); } public static MoaZSException moaZSException(String message) { - return new MoaZSException(String.format(message)); + return moaZSExceptionBuilder(message).build(); + } + + public static Builder moaZSExceptionBuilder(String formatString, Object... objects) { + return new Builder().withMessage(String.format(formatString, objects)); + } + + public static Builder moaZSExceptionBuilder(String message) { + return new Builder().withMessage(message); + } + + @Nullable + public String getErrorCode() { + return errorCode; + } + + @Nullable + public PersonResultType getTnvzResult() { + return tnvzResult; + } + + @Nullable + public DeliveryRequestStatusType getMsgResult() { + return msgResult; + } + + @Nullable + public at.gv.zustellung.app2mzs.xsd.DeliveryRequestType getMzsRequest() { + return mzsRequest; + } + + @Nullable + public DeliveryRequestType getMsgRequest() { + return msgRequest; + } + + public static class Builder extends Throwable { + + private String message; + private Throwable cause; + private String errorCode; + private PersonResultType tnvzResult; + private DeliveryRequestStatusType msgResult; + private DeliveryRequestType msgRequest; + private at.gv.zustellung.app2mzs.xsd.DeliveryRequestType mzsRequest; + + private Builder() { + } + + public Builder withMessage(String message) { + this.message = message; + return this; + } + + public Builder withCause(Throwable cause) { + this.cause = cause; + return this; + } + + public Builder withErrorCode(String errorCode) { + this.errorCode = errorCode; + return this; + } + + public Builder withTnvzResult(PersonResultType tnvzResult) { + this.tnvzResult = tnvzResult; + return this; + } + + public Builder withMsgResult(DeliveryRequestStatusType msgResult) { + this.msgResult = msgResult; + return this; + } + + public Builder withMsgRequest(DeliveryRequestType msgRequest) { + this.msgRequest = msgRequest; + return this; + } + + public Builder withMzsRequest(at.gv.zustellung.app2mzs.xsd.DeliveryRequestType mzsRequest) { + this.mzsRequest = mzsRequest; + return this; + } + + public MoaZSException build() { + return new MoaZSException(message, cause, errorCode, tnvzResult, msgResult, msgRequest, mzsRequest); + } } } diff --git a/src/main/java/at/gv/egiz/moazs/msg/MsgClient.java b/src/main/java/at/gv/egiz/moazs/msg/MsgClient.java index fd36a92..82f172d 100644 --- a/src/main/java/at/gv/egiz/moazs/msg/MsgClient.java +++ b/src/main/java/at/gv/egiz/moazs/msg/MsgClient.java @@ -8,18 +8,14 @@ import org.apache.cxf.jaxws.JaxWsClientFactoryBean; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; import org.apache.cxf.message.Message; import org.apache.cxf.phase.PhaseInterceptor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class MsgClient { - private static final Logger log = LoggerFactory.getLogger(MsgClient.class); - private final DeliveryRequestType msgRequest; private final ConfigType config; private final PhaseInterceptor interceptor; - MsgClient(DeliveryRequestType msgRequest, ConfigType config, PhaseInterceptor interceptor) { + public MsgClient(DeliveryRequestType msgRequest, ConfigType config, PhaseInterceptor interceptor) { this.msgRequest = msgRequest; this.config = config; this.interceptor = interceptor; diff --git a/src/main/java/at/gv/egiz/moazs/msg/MsgClientFactory.java b/src/main/java/at/gv/egiz/moazs/msg/MsgClientFactory.java index 82468bc..228ebec 100644 --- a/src/main/java/at/gv/egiz/moazs/msg/MsgClientFactory.java +++ b/src/main/java/at/gv/egiz/moazs/msg/MsgClientFactory.java @@ -8,14 +8,7 @@ import org.springframework.stereotype.Component; @Component public class MsgClientFactory { - private final StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor; - - @Autowired - public MsgClientFactory(StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor) { - this.storeResponseInterceptor = storeResponseInterceptor; - } - - public MsgClient create(DeliveryRequestType msgRequest, ConfigType config) { + public MsgClient create(DeliveryRequestType msgRequest, ConfigType config, StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor) { return new MsgClient(msgRequest, config, storeResponseInterceptor); } diff --git a/src/main/java/at/gv/egiz/moazs/msg/SignatureVerifier.java b/src/main/java/at/gv/egiz/moazs/msg/SignatureVerifier.java new file mode 100644 index 0000000..12b1ccb --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/msg/SignatureVerifier.java @@ -0,0 +1,13 @@ +package at.gv.egiz.moazs.msg; + +import org.springframework.stereotype.Component; + +@Component +public class SignatureVerifier { + + public boolean verify(byte[] signedXMLdocument) { + return true; + + } + +} diff --git a/src/main/java/at/gv/egiz/moazs/msg/StoreSOAPBodyBinaryInRepositoryInterceptor.java b/src/main/java/at/gv/egiz/moazs/msg/StoreSOAPBodyBinaryInRepositoryInterceptor.java index 4e023ac..c78c44b 100644 --- a/src/main/java/at/gv/egiz/moazs/msg/StoreSOAPBodyBinaryInRepositoryInterceptor.java +++ b/src/main/java/at/gv/egiz/moazs/msg/StoreSOAPBodyBinaryInRepositoryInterceptor.java @@ -1,6 +1,5 @@ package at.gv.egiz.moazs.msg; -import at.gv.egiz.moazs.MoaZSException; import at.gv.egiz.moazs.repository.DeliveryRepository; import at.gv.egiz.moazs.scheme.SOAPUtils; import at.gv.egiz.moazs.util.CXFMessageUtils; @@ -17,6 +16,8 @@ import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; +import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; + @Component public class StoreSOAPBodyBinaryInRepositoryInterceptor extends AbstractPhaseInterceptor { @@ -48,7 +49,9 @@ public class StoreSOAPBodyBinaryInRepositoryInterceptor extends AbstractPhaseInt log.info("Store binary DeliveryRequestStatus with AppDeliveryId={}", appDeliveryId); } } catch (ParserConfigurationException | SAXException | IOException | NullPointerException e) { - throw new MoaZSException("Could not extract signed data from message.", e); + throw moaZSExceptionBuilder("Could not extract signed data from message.") + .withCause(e) + .build(); } } diff --git a/src/main/java/at/gv/egiz/moazs/mzs/MzsService.java b/src/main/java/at/gv/egiz/moazs/mzs/MzsService.java index bb927ba..928e3d7 100644 --- a/src/main/java/at/gv/egiz/moazs/mzs/MzsService.java +++ b/src/main/java/at/gv/egiz/moazs/mzs/MzsService.java @@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static at.gv.egiz.moazs.MoaZSException.moaZSException; +import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; import static at.gv.zustellung.app2mzs.xsd.PartialSuccessType.partialSuccessTypeBuilder; import static java.lang.String.format; import static java.util.concurrent.CompletableFuture.supplyAsync; @@ -70,10 +71,11 @@ public class MzsService implements App2MzsPortType { future.thenAccept(appClient::sendNotification); return generatePartialSuccessResponse(appDeliveryID); - } catch (Exception e ) { - var message = format("An error occurred while processing DeliveryRequest with AppDeliveryID=%s.", appDeliveryID); - throw new MoaZSException(message, e); - + } catch (Exception e) { + throw moaZSExceptionBuilder("An error occurred while processing DeliveryRequest " + + "with AppDeliveryID=%s.", appDeliveryID) + .withCause(e) + .build(); } } diff --git a/src/main/java/at/gv/egiz/moazs/pipeline/DeliveryPipeline.java b/src/main/java/at/gv/egiz/moazs/pipeline/DeliveryPipeline.java index f3ac0e6..f32dfe2 100644 --- a/src/main/java/at/gv/egiz/moazs/pipeline/DeliveryPipeline.java +++ b/src/main/java/at/gv/egiz/moazs/pipeline/DeliveryPipeline.java @@ -4,12 +4,12 @@ package at.gv.egiz.moazs.pipeline; public interface DeliveryPipeline { /** - * Performs a {@code DeliveryRequest}'s Back-End Tasks + * Performs all {@code DeliveryRequest}'s Back-End Tasks. * * Fetches {@code DeliveryRequest} referred by appDeliveryId from * {@code DeliveryRepository}, makes sure that all necessary - * tasks are executed and stores the response back to - * {@code DeliveryRepository}. + * tasks (query tnvz, query msg, verify status) are executed and + * stores the response back to {@code DeliveryRepository}. * @param appDeliveryId */ void processRequest(String appDeliveryId); diff --git a/src/main/java/at/gv/egiz/moazs/pipeline/SameThreadDeliveryPipeline.java b/src/main/java/at/gv/egiz/moazs/pipeline/SameThreadDeliveryPipeline.java index a0475fd..130e147 100644 --- a/src/main/java/at/gv/egiz/moazs/pipeline/SameThreadDeliveryPipeline.java +++ b/src/main/java/at/gv/egiz/moazs/pipeline/SameThreadDeliveryPipeline.java @@ -1,24 +1,28 @@ package at.gv.egiz.moazs.pipeline; +import at.gv.egiz.moazs.MoaZSException; import at.gv.egiz.moazs.msg.MsgClientFactory; +import at.gv.egiz.moazs.msg.SignatureVerifier; +import at.gv.egiz.moazs.msg.StoreSOAPBodyBinaryInRepositoryInterceptor; import at.gv.egiz.moazs.repository.DeliveryRepository; import at.gv.egiz.moazs.scheme.Mzs2MsgConverter; +import at.gv.egiz.moazs.scheme.NameSpace; import at.gv.egiz.moazs.tnvz.TnvzClient; +import at.gv.egiz.moazs.tnvz.TnvzResultVerifier; import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; -import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType.Payload; -import at.gv.zustellung.msg.xsd.persondata.IdentificationType; +import at.gv.zustellung.msg.xsd.DeliveryAnswerType; +import at.gv.zustellung.msg.xsd.DeliveryRequestStatusType; import at.gv.zustellung.tnvz.xsd.PersonResultType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; -import java.util.Collection; -import java.util.List; - -import static at.gv.egiz.moazs.MoaZSException.moaZSException; -import static java.lang.String.join; -import static java.util.stream.Collectors.toSet; +import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; +import static at.gv.egiz.moazs.util.NullCoalesce.coalesce; +import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.Error.errorBuilder; +import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.deliveryRequestStatusTypeBuilder; +import static at.gv.zustellung.msg.xsd.ErrorInfoType.errorInfoTypeBuilder; @Component @Profile("!cluster") @@ -26,74 +30,109 @@ public class SameThreadDeliveryPipeline implements DeliveryPipeline { private final DeliveryRepository repository; private final TnvzClient tnvzClient; + private final TnvzResultVerifier tnvzVerifier; private final Mzs2MsgConverter converter; private final MsgClientFactory msgClientFactory; + private final SignatureVerifier verifier; + private final StoreSOAPBodyBinaryInRepositoryInterceptor interceptor; @Autowired public SameThreadDeliveryPipeline(DeliveryRepository repository, TnvzClient tnvzClient, + TnvzResultVerifier tnvzVerifier, Mzs2MsgConverter converter, - MsgClientFactory msgClientFactory) { + StoreSOAPBodyBinaryInRepositoryInterceptor interceptor, + MsgClientFactory msgClientFactory, + SignatureVerifier verifier + ) { this.repository = repository; this.tnvzClient = tnvzClient; + this.tnvzVerifier = tnvzVerifier; this.converter = converter; this.msgClientFactory = msgClientFactory; + this.verifier = verifier; + this.interceptor = interceptor; } @Override public void processRequest(String appDeliveryId) { - var mzsRequest = repository.getDeliveryRequest(appDeliveryId).orElseThrow(); - var msgRequest = (mzsRequest.getConfig().isPerformQueryPersonRequest()) - ? converter.convert(mzsRequest, queryPerson(mzsRequest)) - : converter.convert(mzsRequest); + DeliveryRequestType mzsRequest; + PersonResultType tnvzResult = null; + at.gv.zustellung.msg.xsd.DeliveryRequestType msgRequest; + DeliveryRequestStatusType status; + + try { + mzsRequest = repository.getDeliveryRequest(appDeliveryId).orElseThrow(); + + if (mzsRequest.getConfig().isPerformQueryPersonRequest()) { + tnvzResult = performTnvzQuery(mzsRequest); + msgRequest = converter.convert(mzsRequest, tnvzResult.getSuccess().getIdentification()); + } else { + msgRequest = converter.convert(mzsRequest); + } + + status = msgClientFactory.create(msgRequest, mzsRequest.getConfig(), interceptor).send(); + + var signedStatus = repository.getSignedDeliveryRequestStatus(appDeliveryId).get(); + if (verifier.verify(signedStatus)) { + repository.add(status); + } else { + throw moaZSExceptionBuilder("Signature of DeliveryRequestStatus with AppDeliveryId={} " + + "is invalid.", appDeliveryId) + .withErrorCode(MoaZSException.ERROR_MOASP_SIGNATURE_INVALID) + .withMzsRequest(mzsRequest) + .withTnvzResult(tnvzResult) + .withMsgRequest(msgRequest) + .withMsgResult(status) + .build(); + } + } catch (MoaZSException exception) { + var errorStatus = generateErrorStatus(exception, appDeliveryId); + repository.add(errorStatus); + } + } - var msgClient = msgClientFactory.create(msgRequest, mzsRequest.getConfig()); - var status = msgClient.send(); - repository.add(status); + private PersonResultType performTnvzQuery(DeliveryRequestType request) { + var result = tnvzClient.query(request.getSender(), request.getReceiver()); + tnvzVerifier.verify(request, result); + return result; } - private IdentificationType queryPerson(DeliveryRequestType request) { - var result = tnvzClient.queryPerson(request.getSender(), request.getReceiver()); + private DeliveryRequestStatusType generateErrorStatus(MoaZSException exception, String appDeliveryId) { - if (result.getError() != null) { - var error = result.getError(); - var info = error.getErrorInfo(); - var noteSent = (error.getPreAdviceNoteSent() != null) ? "sent" : "not sent"; - var template = "Receiver is not addressable. Code: %s ; Text: %s; Preadvice note was %s."; - throw moaZSException(template, info.getCode(), info.getText(), noteSent); - } + var infoBuilder = errorInfoTypeBuilder() + .withText(exception.getMessage()) + .withCode(exception.getErrorCode()); - var mismatchedTypes = findMimeTypeMismatches(result, request); + var errorBuilder = errorBuilder() + .withErrorInfo(infoBuilder.build()) + .withAppDeliveryID(appDeliveryId); - if (!mismatchedTypes.isEmpty()) { - var template = "Request contains attachment of type(s) %s, but receiver only accepts attachments of type(s) %s."; - var acceptedTypesString = join(",", getAcceptedTypes(result)); - var mismatchedTypesString = join(",", mismatchedTypes); - throw moaZSException(template, mismatchedTypesString, acceptedTypesString); + if (exception.getMzsRequest() != null) { + errorBuilder.withDeliverySystem(exception.getMzsRequest().getConfig().getServer().getZUSEUrlID()); } - return result.getSuccess().getIdentification(); - } - - private Collection findMimeTypeMismatches(PersonResultType result, DeliveryRequestType request) { - var acceptedTypes = getAcceptedTypes(result); - - if (acceptedTypes.contains("*/*")) { - return List.of(); + if (exception.getTnvzResult() != null && exception.getTnvzResult().getError() != null) { + errorBuilder.withPreAdviceNoteSent(exception.getTnvzResult().getError().getPreAdviceNoteSent()); } - var typesInRequest = request.getPayload().stream() - .map(Payload::getMIMEType) - .collect(toSet()); + if (exception.getMsgResult() != null) { + var answer = getAnswerFromResult(exception.getMsgResult()); + errorBuilder.withGZ(answer.getGZ()); + errorBuilder.withZSDeliveryID(answer.getZSDeliveryID()); + } - typesInRequest.removeAll(acceptedTypes); + return deliveryRequestStatusTypeBuilder() + .withError(errorBuilder.build()) + .withVersion(NameSpace.MSG_VERSION) + .build(); - return typesInRequest; } - private List getAcceptedTypes(PersonResultType result) { - return result.getSuccess().getMimeTypeList().getMimeType(); + private DeliveryAnswerType getAnswerFromResult(DeliveryRequestStatusType msgResult) { + return coalesce(msgResult.getSuccess(), msgResult.getPartialSuccess(), msgResult.getError()).get(); } + } diff --git a/src/main/java/at/gv/egiz/moazs/scheme/NameSpace.java b/src/main/java/at/gv/egiz/moazs/scheme/NameSpace.java index 63276cb..fc479eb 100644 --- a/src/main/java/at/gv/egiz/moazs/scheme/NameSpace.java +++ b/src/main/java/at/gv/egiz/moazs/scheme/NameSpace.java @@ -5,6 +5,7 @@ public class NameSpace { private NameSpace() {} public static final String MSG = new at.gv.zustellung.msg.xsd.ObjectFactory().createDeliveryRequest(null).getName().getNamespaceURI(); + public static final String MSG_VERSION = "2.2.0"; public static final String MSGP = new at.gv.zustellung.msg.xsd.persondata.ObjectFactory().createPerson(null).getName().getNamespaceURI(); diff --git a/src/main/java/at/gv/egiz/moazs/scheme/SOAPUtils.java b/src/main/java/at/gv/egiz/moazs/scheme/SOAPUtils.java index 2d4df4b..b1c1909 100644 --- a/src/main/java/at/gv/egiz/moazs/scheme/SOAPUtils.java +++ b/src/main/java/at/gv/egiz/moazs/scheme/SOAPUtils.java @@ -13,6 +13,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; + @Component public class SOAPUtils { @@ -30,7 +32,7 @@ public class SOAPUtils { .getBytes(StandardCharsets.UTF_8); } catch (IOException | TransformerException e) { - throw new MoaZSException("Error while parsing message. ", e); + throw moaZSExceptionBuilder("Error while parsing message. ").withCause(e).build(); } } diff --git a/src/main/java/at/gv/egiz/moazs/tnvz/TnvzClient.java b/src/main/java/at/gv/egiz/moazs/tnvz/TnvzClient.java index 4a69aea..d4049fc 100644 --- a/src/main/java/at/gv/egiz/moazs/tnvz/TnvzClient.java +++ b/src/main/java/at/gv/egiz/moazs/tnvz/TnvzClient.java @@ -1,5 +1,7 @@ package at.gv.egiz.moazs.tnvz; +import at.gv.egiz.moazs.MoaZSException; +import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType.Sender; import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType.Receiver; import at.gv.zustellung.tnvz.xsd.PersonResultSuccessType; @@ -7,10 +9,18 @@ import at.gv.zustellung.tnvz.xsd.PersonResultType; import at.gv.zustellung.tnvz.xsd.PersonResultType.PersonResultTypeBuilder; import org.springframework.stereotype.Component; +import java.util.Collection; +import java.util.List; + +import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; +import static java.lang.String.join; +import static java.util.stream.Collectors.toSet; + @Component public class TnvzClient { - public PersonResultType queryPerson(Sender sender, Receiver receiver) { + public PersonResultType query(Sender sender, Receiver receiver) { + //TODO: Replace this stub with actual request. PersonResultSuccessType success = new PersonResultSuccessType.PersonResultSuccessTypeBuilder().build(); return new PersonResultTypeBuilder().withSuccess(success).build(); } diff --git a/src/main/java/at/gv/egiz/moazs/tnvz/TnvzResultVerifier.java b/src/main/java/at/gv/egiz/moazs/tnvz/TnvzResultVerifier.java new file mode 100644 index 0000000..9992246 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/tnvz/TnvzResultVerifier.java @@ -0,0 +1,63 @@ +package at.gv.egiz.moazs.tnvz; + +import at.gv.egiz.moazs.MoaZSException; +import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; +import at.gv.zustellung.tnvz.xsd.PersonResultType; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.List; + +import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; +import static java.lang.String.join; +import static java.util.stream.Collectors.toSet; + +@Component +public class TnvzResultVerifier { + + public void verify(DeliveryRequestType request, PersonResultType result) { + + if (result.getError() != null) { + var info = result.getError().getErrorInfo(); + throw moaZSExceptionBuilder("Receiver is not addressable. Reason: %s", info.getText()) + .withErrorCode(info.getCode()) + .withMzsRequest(request) + .withTnvzResult(result) + .build(); + } + + var mismatchedTypes = findMimeTypeMismatches(result, request); + + if (!mismatchedTypes.isEmpty()) { + var template = "Request contains attachment of type(s) %s, but receiver only accepts attachments" + + " of type(s) %s."; + var acceptedTypesString = join(",", getAcceptedTypes(result)); + var mismatchedTypesString = join(",", mismatchedTypes); + throw moaZSExceptionBuilder(template, mismatchedTypesString, acceptedTypesString) + .withErrorCode(MoaZSException.ERROR_MZS_MIMETYPE_MISSMATCH) + .withMzsRequest(request) + .withTnvzResult(result) + .build(); + } + } + + private Collection findMimeTypeMismatches(PersonResultType result, DeliveryRequestType request) { + var acceptedTypes = getAcceptedTypes(result); + + if (acceptedTypes.contains("*/*")) { + return List.of(); + } + + var typesInRequest = request.getPayload().stream() + .map(DeliveryRequestType.Payload::getMIMEType) + .collect(toSet()); + + typesInRequest.removeAll(acceptedTypes); + + return typesInRequest; + } + + private List getAcceptedTypes(PersonResultType result) { + return result.getSuccess().getMimeTypeList().getMimeType(); + } +} diff --git a/src/test/java/at/gv/egiz/moazs/MsgClientTest.java b/src/test/java/at/gv/egiz/moazs/MsgClientTest.java index 1822ff5..8cebf06 100644 --- a/src/test/java/at/gv/egiz/moazs/MsgClientTest.java +++ b/src/test/java/at/gv/egiz/moazs/MsgClientTest.java @@ -2,6 +2,7 @@ package at.gv.egiz.moazs; import at.gv.egiz.moazs.msg.MsgClient; import at.gv.egiz.moazs.msg.MsgClientFactory; +import at.gv.egiz.moazs.msg.StoreSOAPBodyBinaryInRepositoryInterceptor; import at.gv.egiz.moazs.scheme.Marshaller; import at.gv.zustellung.app2mzs.xsd.ConfigType; import at.gv.zustellung.msg.xsd.DeliveryRequestType; @@ -39,6 +40,9 @@ public class MsgClientTest { @Autowired private MsgClientFactory factory; + @Autowired + private StoreSOAPBodyBinaryInRepositoryInterceptor interceptor; + private static final ObjectFactory OF = new ObjectFactory(); @@ -49,7 +53,7 @@ public class MsgClientTest { var request = loadFromFile("validDeliveryRequest.xml"); var config = generateConfig(httpServiceUri); - var client = factory.create(request, config); + var client = factory.create(request, config, interceptor); try{ var status = client.send(); @@ -64,7 +68,7 @@ public class MsgClientTest { var request = loadFromFile("validDeliveryRequest.xml"); var config = generateConfig(sslServiceUri); - var client = factory.create(request, config); + var client = factory.create(request, config, interceptor); var status = client.send(); diff --git a/src/test/java/at/gv/egiz/moazs/SameThreadDeliveryPipelineTest.java b/src/test/java/at/gv/egiz/moazs/SameThreadDeliveryPipelineTest.java index 5e4b9b0..b0c03a2 100644 --- a/src/test/java/at/gv/egiz/moazs/SameThreadDeliveryPipelineTest.java +++ b/src/test/java/at/gv/egiz/moazs/SameThreadDeliveryPipelineTest.java @@ -2,11 +2,15 @@ package at.gv.egiz.moazs; import at.gv.egiz.moazs.msg.MsgClient; import at.gv.egiz.moazs.msg.MsgClientFactory; +import at.gv.egiz.moazs.msg.SignatureVerifier; +import at.gv.egiz.moazs.msg.StoreSOAPBodyBinaryInRepositoryInterceptor; import at.gv.egiz.moazs.pipeline.DeliveryPipeline; import at.gv.egiz.moazs.pipeline.SameThreadDeliveryPipeline; import at.gv.egiz.moazs.repository.DeliveryRepository; +import at.gv.egiz.moazs.repository.InMemoryDeliveryRepository; import at.gv.egiz.moazs.scheme.Mzs2MsgConverter; import at.gv.egiz.moazs.tnvz.TnvzClient; +import at.gv.egiz.moazs.tnvz.TnvzResultVerifier; import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; import at.gv.zustellung.msg.xsd.DeliveryRequestStatusType; import at.gv.zustellung.msg.xsd.MetaData; @@ -24,6 +28,7 @@ import static at.gv.zustellung.app2mzs.xsd.ConfigType.configTypeBuilder; import static at.gv.zustellung.app2mzs.xsd.DeliveryRequestType.Payload; import static at.gv.zustellung.app2mzs.xsd.DeliveryRequestType.Payload.payloadBuilder; import static at.gv.zustellung.app2mzs.xsd.DeliveryRequestType.deliveryRequestTypeBuilder; +import static at.gv.zustellung.app2mzs.xsd.ServerType.serverTypeBuilder; import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.Success.successBuilder; import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.deliveryRequestStatusTypeBuilder; import static at.gv.zustellung.msg.xsd.ErrorInfoType.errorInfoTypeBuilder; @@ -32,16 +37,15 @@ import static at.gv.zustellung.tnvz.xsd.MimeTypeList.mimeTypeListBuilder; import static at.gv.zustellung.tnvz.xsd.PersonResultSuccessType.personResultSuccessTypeBuilder; import static at.gv.zustellung.tnvz.xsd.PersonResultType.Error.errorBuilder; import static at.gv.zustellung.tnvz.xsd.PersonResultType.personResultTypeBuilder; -import static java.util.Optional.of; import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class SameThreadDeliveryPipelineTest { - @Mock - private DeliveryRepository repository; + private DeliveryRepository repository = new InMemoryDeliveryRepository(); @Mock private TnvzClient tnvzClient; @@ -55,34 +59,46 @@ public class SameThreadDeliveryPipelineTest { @Mock private Mzs2MsgConverter converter; + @Mock + private StoreSOAPBodyBinaryInRepositoryInterceptor interceptor; + + @Mock + private SignatureVerifier verifier; + private DeliveryPipeline pipeline; @Before public void setup() { - pipeline = new SameThreadDeliveryPipeline(repository, tnvzClient, converter, msgClientFactory); + pipeline = new SameThreadDeliveryPipeline(repository, tnvzClient, new TnvzResultVerifier(), + converter,interceptor, msgClientFactory, verifier); } @Test public void executePipelineWithoutTnvzRequest() { var appDeliveryId = "no-tnvz-request"; - var status = setupMocks(appDeliveryId, false, List.of(), List.of("*/*")); + var expectedStatus = setupMocks(appDeliveryId, false, List.of(), List.of("*/*")); pipeline.processRequest(appDeliveryId); verifyZeroInteractions(tnvzClient); - verify(repository).add(status); + var actualStatus = repository.getDeliveryRequestStatus(appDeliveryId).get(); + + assertThat(actualStatus).isEqualTo(expectedStatus); } - @Test(expected = MoaZSException.class) + @Test public void rejectDeliveryWhenReceiverIsNotAddressable() { var appDeliveryId = "not-addressable"; setupMocks(appDeliveryId, true, List.of(), List.of("*/*")); - when(tnvzClient.queryPerson(any(), any())).thenReturn(setupTnvzError()); + when(tnvzClient.query(any(), any())).thenReturn(setupTnvzError("400", "Person not found.")); pipeline.processRequest(appDeliveryId); + var actualCode = repository.getDeliveryRequestStatus(appDeliveryId).get() + .getError().getErrorInfo().getCode(); - verify(tnvzClient).queryPerson(any(), any()); + verify(tnvzClient).query(any(), any()); + assertThat(actualCode).isEqualTo("400"); } @Test @@ -90,15 +106,16 @@ public class SameThreadDeliveryPipelineTest { var appDeliveryId = "wild-card-mimetype"; List acceptedTypes = List.of("*/*"); List attachedTypes = List.of("pdf", "xml", "html", "random/attachedtype"); - var status = setupMocks(appDeliveryId, true, attachedTypes, acceptedTypes); + var expectedStatus = setupMocks(appDeliveryId, true, attachedTypes, acceptedTypes); pipeline.processRequest(appDeliveryId); + var actualStatus = repository.getDeliveryRequestStatus(appDeliveryId).get(); - verify(tnvzClient).queryPerson(any(), any()); - verify(repository).add(status); + verify(tnvzClient).query(any(), any()); + assertThat(actualStatus).isEqualTo(expectedStatus); } - @Test(expected = MoaZSException.class) + @Test public void rejectMismatchedMimeTypes() { var appDeliveryId = "mismatched-mimetype"; List acceptedTypes = List.of("xml"); @@ -106,8 +123,12 @@ public class SameThreadDeliveryPipelineTest { setupMocks(appDeliveryId, true, attachedTypes, acceptedTypes); pipeline.processRequest(appDeliveryId); + var actualCode = repository.getDeliveryRequestStatus(appDeliveryId).get() + .getError().getErrorInfo().getCode(); + + verify(tnvzClient).query(any(), any()); + assertThat(actualCode).isEqualTo(MoaZSException.ERROR_MZS_MIMETYPE_MISSMATCH); - verify(tnvzClient).queryPerson(any(), any()); } @Test @@ -115,36 +136,59 @@ public class SameThreadDeliveryPipelineTest { var appDeliveryId = "specific-mimetype"; List acceptedTypes = List.of("pdf", "xml", "html"); List attachedTypes = List.of("pdf", "xml"); - var status = setupMocks(appDeliveryId, true, attachedTypes, acceptedTypes); + var expectedStatus = setupMocks(appDeliveryId, true, attachedTypes, acceptedTypes); + + pipeline.processRequest(appDeliveryId); + var actualStatus = repository.getDeliveryRequestStatus(appDeliveryId).get(); + + verify(tnvzClient).query(any(), any()); + assertThat(actualStatus).isEqualTo(expectedStatus); + } + + @Test + public void rejectInvalidSignature() { + var appDeliveryId = "invalid-signature"; + setupMocks(appDeliveryId, true, List.of(), List.of("*/*"), false); pipeline.processRequest(appDeliveryId); + var actualCode = repository.getDeliveryRequestStatus(appDeliveryId).get() + .getError().getErrorInfo().getCode(); + + assertThat(actualCode).isEqualTo(MoaZSException.ERROR_MOASP_SIGNATURE_INVALID); - verify(tnvzClient).queryPerson(any(), any()); - verify(repository).add(status); } private DeliveryRequestStatusType setupMocks(String appDeliveryId, boolean tnvzRequest, List attachedTypes, List acceptedTypes) { + return setupMocks(appDeliveryId, tnvzRequest, attachedTypes, acceptedTypes, true); + } + + private DeliveryRequestStatusType setupMocks(String appDeliveryId, boolean tnvzRequest, + List attachedTypes, List acceptedTypes, boolean isSignedStatusValid) { var mzsRequest = setupMzsRequest(appDeliveryId, tnvzRequest, attachedTypes); var msgRequest = setupMsgRequest(appDeliveryId); var status = setupStatus(appDeliveryId); - when(repository.getDeliveryRequest(appDeliveryId)).thenReturn(of(mzsRequest)); - when(tnvzClient.queryPerson(any(), any())).thenReturn(setupTnvzSuccess(acceptedTypes)); + var signedStatus = new byte[0]; + repository.add(mzsRequest); + repository.addSignedDeliveryRequestStatus(signedStatus, appDeliveryId); + + when(tnvzClient.query(any(), any())).thenReturn(setupTnvzSuccess(acceptedTypes)); when(converter.convert(eq(mzsRequest) )).thenReturn(msgRequest); when(converter.convert(eq(mzsRequest), any())).thenReturn(msgRequest); - when(msgClientFactory.create(msgRequest, mzsRequest.getConfig())).thenReturn(msgClient); + when(msgClientFactory.create(msgRequest, mzsRequest.getConfig(), interceptor)).thenReturn(msgClient); when(msgClient.send()).thenReturn(status); + when(verifier.verify(signedStatus)).thenReturn(isSignedStatusValid); return status; } - private PersonResultType setupTnvzError() { + private PersonResultType setupTnvzError(String code, String text) { var info = errorInfoTypeBuilder() - .withCode("400") - .withText("Person Not Found") + .withCode(code) + .withText(text) .build(); var error = errorBuilder() @@ -181,8 +225,13 @@ public class SameThreadDeliveryPipelineTest { private DeliveryRequestType setupMzsRequest(String appDeliveryId, boolean tnvzRequest, List mimeTypes) { + var server = serverTypeBuilder() + .withZUSEUrlID("http://zuse") + .build(); + var config = configTypeBuilder() .withPerformQueryPersonRequest(tnvzRequest) + .withServer(server) .build(); return deliveryRequestTypeBuilder() -- cgit v1.2.3