From 8aba1b4f18f5fbfebdf239b4b4945b628e439905 Mon Sep 17 00:00:00 2001 From: Christof Rabensteiner Date: Tue, 9 Jul 2019 14:11:47 +0200 Subject: Refactor Needles Interfaces and Rename "process" to "backend" Reason: - Interfaces with a single method can be replaced with interfaces from java.util.function. - Less interfaces = less code = less maintenance! - Spring can inject beans by name so we name dependencies correctly to prevent ambiguity. Others: - Rename process to backend since backend gives a better description of its components. --- .../egiz/moazs/backend/DeliveryRequestBackend.java | 112 ++++++++++++++++++++ .../gv/egiz/moazs/backend/MsgResponseBackend.java | 75 +++++++++++++ .../egiz/moazs/backend/SaveResponseToFileSink.java | 89 ++++++++++++++++ .../gv/egiz/moazs/backend/SignatureVerifier.java | 116 +++++++++++++++++++++ 4 files changed, 392 insertions(+) create mode 100644 src/main/java/at/gv/egiz/moazs/backend/DeliveryRequestBackend.java create mode 100644 src/main/java/at/gv/egiz/moazs/backend/MsgResponseBackend.java create mode 100644 src/main/java/at/gv/egiz/moazs/backend/SaveResponseToFileSink.java create mode 100644 src/main/java/at/gv/egiz/moazs/backend/SignatureVerifier.java (limited to 'src/main/java/at/gv/egiz/moazs/backend') diff --git a/src/main/java/at/gv/egiz/moazs/backend/DeliveryRequestBackend.java b/src/main/java/at/gv/egiz/moazs/backend/DeliveryRequestBackend.java new file mode 100644 index 0000000..06eba80 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/backend/DeliveryRequestBackend.java @@ -0,0 +1,112 @@ +package at.gv.egiz.moazs.backend; + + +import at.gv.egiz.moazs.MoaZSException; +import at.gv.egiz.moazs.client.ClientFactory; +import at.gv.egiz.moazs.client.TnvzHelper; +import at.gv.egiz.moazs.repository.DeliveryRepository; +import at.gv.egiz.moazs.scheme.Mzs2MsgConverter; +import at.gv.egiz.moazs.scheme.RequestStatusResponse; +import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; +import at.gv.zustellung.msg.xsd.App2ZusePort; +import at.gv.zustellung.tnvz.xsd.TNVZServicePort; +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import java.util.function.Consumer; + +import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; +import static at.gv.egiz.moazs.scheme.RequestStatusResponse.generateErrorFromException; +import static java.lang.String.format; + +@Component +@Profile("!cluster") +public class DeliveryRequestBackend implements Consumer { + + private static final Logger log = Logger.getLogger(DeliveryRequestBackend.class); + private static final String MZS_PIPELINE_ERROR_MSG = + "An error occured while processing the DeliveryRequest with AppDeliveryID=%s. "; + + private final DeliveryRepository repository; + private final TnvzHelper tnvzHelper; + private final Mzs2MsgConverter converter; + private final ClientFactory clientFactory; + private final Consumer signatureVerifier; + + @Autowired + public DeliveryRequestBackend(DeliveryRepository repository, + TnvzHelper tnvzHelper, + Mzs2MsgConverter converter, + ClientFactory clientFactory, + Consumer signatureVerifier) { + this.repository = repository; + this.tnvzHelper = tnvzHelper; + this.converter = converter; + this.clientFactory = clientFactory; + this.signatureVerifier = signatureVerifier; + } + + /** + * Performs all {@code DeliveryRequest}'s Back-End Tasks. + * + * Fetches {@code DeliveryRequest} referred by appDeliveryID from + * {@code DeliveryRepository}, makes sure that all necessary + * tasks (query tnvz, query msg, accept status) are executed and + * stores the response back to {@code DeliveryRepository}. + * @param appDeliveryID + */ + @Override + public void accept(String appDeliveryID) { + + var exceptionBuilder = moaZSExceptionBuilder(); + + try { + var mzsRequest = repository.retrieveDeliveryRequest(appDeliveryID).orElseThrow(); + exceptionBuilder.withDeliverySystem(mzsRequest); + + at.gv.zustellung.msg.xsd.DeliveryRequestType msgRequest = buildMsgRequest(mzsRequest, exceptionBuilder); + + var msgClientParams = mzsRequest.getConfig().getMSGClient(); + App2ZusePort client = clientFactory.create(msgClientParams, App2ZusePort.class); + var status = client.delivery(msgRequest); + + var response = new RequestStatusResponse(status); + exceptionBuilder.withAllParametersInAnswer(response.getAnswer()); + + verifySignedStatus(response.getResponseID(), appDeliveryID, exceptionBuilder); + repository.store(response); + + } catch (MoaZSException exception) { + log.error(format(MZS_PIPELINE_ERROR_MSG, appDeliveryID), exception); + var errorResponse = generateErrorFromException(exception); + repository.store(errorResponse); + } + } + + private void verifySignedStatus(String responseID, String appDeliveryID, MoaZSException.Builder exceptionBuilder) throws MoaZSException { + try { + var signedStatus = repository.retrieveBinaryResponse(responseID).get(); + signatureVerifier.accept(signedStatus); + } catch (MoaZSException ex) { + throw exceptionBuilder.withMessage(format(MsgResponseBackend.MOASP_SIGNATURE_INVALID_ERROR_MSG, appDeliveryID)) + .withErrorCode(MoaZSException.ERROR_MOASP_SIGNATURE_INVALID) + .withCause(ex) + .build(); + } + } + + private at.gv.zustellung.msg.xsd.DeliveryRequestType buildMsgRequest(DeliveryRequestType mzsRequest, + MoaZSException.Builder exceptionBuilder) throws MoaZSException { + if (mzsRequest.getConfig().isPerformQueryPersonRequest()) { + var tnvzClientParams = mzsRequest.getConfig().getTNVZClient(); + TNVZServicePort tvnzPort = clientFactory.create(tnvzClientParams, TNVZServicePort.class); + var identification = tnvzHelper.performQueryPersonRequest(mzsRequest, tvnzPort, exceptionBuilder); + return converter.convert(mzsRequest, identification); + } else { + return converter.convert(mzsRequest); + } + } + +} diff --git a/src/main/java/at/gv/egiz/moazs/backend/MsgResponseBackend.java b/src/main/java/at/gv/egiz/moazs/backend/MsgResponseBackend.java new file mode 100644 index 0000000..414c2dc --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/backend/MsgResponseBackend.java @@ -0,0 +1,75 @@ +package at.gv.egiz.moazs.backend; + +import at.gv.egiz.moazs.MoaZSException; +import at.gv.egiz.moazs.repository.DeliveryRepository; +import at.gv.egiz.moazs.scheme.MsgResponse; +import at.gv.egiz.moazs.service.MsgService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; + +import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.supplyAsync; + +@Component +public class MsgResponseBackend implements Consumer { + + private static final Logger log = LoggerFactory.getLogger(MsgService.class); + + public static final String MOASP_SIGNATURE_INVALID_ERROR_MSG = "Signature of Msg Response " + + "with AppDeliveryID=%s is not valid."; + + private final DeliveryRepository repository; + private final Consumer signatureVerifier; + private final Function> sink; + + @Autowired + public MsgResponseBackend(DeliveryRepository repository, + Consumer signatureVerifier, + Function> sink) { + this.repository = repository; + this.signatureVerifier = signatureVerifier; + this.sink = sink; + } + + /** + * Performs all {@code MsgResponse}'s Back-End Tasks, such as verifying + * its signature and archiving the response. + * + * @param responseID refers to MsgResponse Object. + */ + @Override + public void accept(String responseID) { + supplyAsync(() -> verify(responseID)).thenAcceptAsync(sink::apply); + } + + public MsgResponse verify(String responseID) { + + var response = repository.retrieveResponse(responseID).get(); + var builder = moaZSExceptionBuilder().withAllParametersInAnswer(response.getAnswer()); + + var binaryResponse = repository.retrieveBinaryResponse(responseID).get(); + + try { + signatureVerifier.accept(binaryResponse); + return response; + } catch (MoaZSException ex) { + log.error(ex.getMessage(), ex); + var wrappingEx = builder + .withMessage(format(MOASP_SIGNATURE_INVALID_ERROR_MSG, response.getAppDeliveryID())) + .withErrorCode(MoaZSException.ERROR_MOASP_SIGNATURE_INVALID) + .withCause(ex) + .build(); + + return response.generateError(wrappingEx); + } + + } + +} diff --git a/src/main/java/at/gv/egiz/moazs/backend/SaveResponseToFileSink.java b/src/main/java/at/gv/egiz/moazs/backend/SaveResponseToFileSink.java new file mode 100644 index 0000000..02771a9 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/backend/SaveResponseToFileSink.java @@ -0,0 +1,89 @@ +package at.gv.egiz.moazs.backend; + +import at.gv.egiz.moazs.repository.DeliveryRepository; +import at.gv.egiz.moazs.scheme.Marshaller; +import at.gv.egiz.moazs.scheme.MsgResponse; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import static at.gv.egiz.moazs.MoaZSException.moaZSException; +import static java.lang.String.format; +import static java.util.concurrent.CompletableFuture.allOf; +import static java.util.concurrent.CompletableFuture.supplyAsync; + +@Component +public class SaveResponseToFileSink implements Function> { + + private static final Logger log = LoggerFactory.getLogger(SaveResponseToFileSink.class); + private static final String SAVING_FAILED_MSG = "Could not save response with AppDeliveryId=%s."; + + private final SimpleDateFormat isoFormatter; + private final Marshaller msgMarshaller; + private final DeliveryRepository repository; + private final String root; + + @Autowired + public SaveResponseToFileSink(Marshaller msgMarshaller, DeliveryRepository repository, String root) { + this.msgMarshaller = msgMarshaller; + this.repository = repository; + this.root = root; + this.isoFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + } + + @Override + public CompletableFuture apply(MsgResponse response) { + + var responseID = response.getResponseID(); + + var responsePath = generatePath(responseID, "xml"); + var storeResponseToFileSystemFuture = supplyAsync(() -> msgMarshaller.marshallXml(response.getResponse())) + .thenApply(responseString -> responseString.getBytes(StandardCharsets.UTF_8)) + .thenAccept(responseByteArray -> storeToFile(responsePath, responseByteArray)) + .exceptionally(ex -> logException(ex, responseID)); + + var binaryResponsePath = generatePath(responseID, "binary.xml"); + var storeBinaryResponseToFileSystemFuture = supplyAsync(() -> repository.retrieveBinaryResponse(responseID).get()) + .thenAccept(binaryResponseByteArray -> storeToFile(binaryResponsePath, binaryResponseByteArray)) + .exceptionally(ex -> logException(ex, responseID)); + + return allOf(storeResponseToFileSystemFuture, storeBinaryResponseToFileSystemFuture); + + } + + private String generatePath(String id, String suffix) { + var folder = sanitizeFileString(id); + var nowInIso8601 = isoFormatter.format(new Date()); + return format("%s/%s/%s.%s", root, folder, nowInIso8601, suffix); + } + + private String sanitizeFileString(String fileString) { + return fileString.replaceAll("[^a-zA-Z0-9\\._\\-]", ""); + } + + private Void logException(Throwable ex, String appDeliveryID) { + if(log.isErrorEnabled()) { + log.error(format(SAVING_FAILED_MSG, appDeliveryID), ex); + } + return null; + } + + private void storeToFile(String path, byte[] content) { + try { + FileUtils.writeByteArrayToFile(new File(path), content); + } catch (IOException e) { + throw moaZSException(e.getMessage(), e); + } + } + +} diff --git a/src/main/java/at/gv/egiz/moazs/backend/SignatureVerifier.java b/src/main/java/at/gv/egiz/moazs/backend/SignatureVerifier.java new file mode 100644 index 0000000..e9c5387 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/backend/SignatureVerifier.java @@ -0,0 +1,116 @@ +package at.gv.egiz.moazs.backend; + +import at.gv.egiz.eid.authhandler.modules.sigverify.moasig.api.ISignatureVerificationService; +import at.gv.egiz.eid.authhandler.modules.sigverify.moasig.api.data.IXMLSignatureVerificationResponse; +import at.gv.egiz.eid.authhandler.modules.sigverify.moasig.exceptions.MOASigServiceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.function.Consumer; + +import static at.gv.egiz.moazs.MoaZSException.moaZSException; +import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; +import static java.lang.String.format; + +public class SignatureVerifier implements Consumer { + + private static final Logger log = LoggerFactory.getLogger(SignatureVerifier.class); + private static final int OK_CODE = 0; + + private final ISignatureVerificationService service; + private final String trustProfile; + private final boolean isManifestCheckActive; + + public SignatureVerifier(ISignatureVerificationService service, + String trustProfile, boolean isManifestCheckActive) { + this.service = service; + this.trustProfile = trustProfile; + this.isManifestCheckActive = isManifestCheckActive; + } + + /** + * Verifies the signature of a signed XML document. Throws a at.gv.egiz.moazs.MoaZSException exception + * if the validation fails. + * @param signedXMLdocument + * @throws at.gv.egiz.moazs.MoaZSException + */ + @Override + public void accept(byte[] signedXMLdocument) { + + try { + var response = service.verifyXMLSignature(signedXMLdocument, trustProfile); + + if (log.isDebugEnabled()) { + print(response); + } + + if (response == null) { + throw moaZSException("MOA SPSS could not find the signature. "); + } + + var builder = new StringBuilder(); + + if (response.getSignatureCheckCode() != OK_CODE) { + builder.append(format("Signature is not valid; SignatureCheckCode was %d. ", + response.getSignatureCheckCode())); + } + + if (response.getCertificateCheckCode() != OK_CODE) { + builder.append(format("Certificate chain is not valid; CertificateCheckCode was %d. ", + response.getCertificateCheckCode())); + } + + if (response.getSignatureManifestCheckCode() != OK_CODE) { + var signatureManifestErrorMsg = format("Signature Manifest is not valid; " + + "SignatureManifestCheckCode was %d. ", response.getSignatureManifestCheckCode()); + if (isManifestCheckActive) { + builder.append(signatureManifestErrorMsg); + } else { + log.warn(signatureManifestErrorMsg); + } + } + + if (response.isXmlDSIGManigest() && response.getXmlDSIGManifestCheckCode() != OK_CODE) { + var xmlDSIGManifestErrorMsg = format("XmlDSIGManifest Manifest is not valid; " + + "XmlDSIGManifest was %d. ", response.getXmlDSIGManifestCheckCode()); + if (isManifestCheckActive) { + builder.append(xmlDSIGManifestErrorMsg); + } else { + log.warn(xmlDSIGManifestErrorMsg); + } + } + + var msg = builder.toString(); + + if(msg.length() > 0) { + throw moaZSException(msg); + } + + } catch (MOASigServiceException e) { + throw moaZSExceptionBuilder("Could not accept the XML signature.") + .withCause(e) + .build(); + } + + } + + private void print(IXMLSignatureVerificationResponse response) { + log.debug("Response:"); + + if (response == null) { + log.debug("null"); + return; + } + + log.debug(" XmlDsigSubjectName: {}", response.getXmlDsigSubjectName()); + log.debug(" SignatureManifestCheckCode: {}", response.getSignatureManifestCheckCode()); + log.debug(" XmlDSIGManifestCheckCode: {}", response.getXmlDSIGManifestCheckCode()); + log.debug(" CertificateCheckCode: {}", response.getCertificateCheckCode()); + log.debug(" SignatureCheckCode: {}", response.getSignatureCheckCode()); + log.debug(" SigningDateTime: {}", response.getSigningDateTime()); + log.debug(" isXmlDSIGManigest: {}", response.isXmlDSIGManigest()); + log.debug(" isPublicAuthority: {}", response.isPublicAuthority()); + log.debug(" isQualifiedCertificate: {}", response.isQualifiedCertificate()); + log.debug(" getPublicAuthorityCode: {}", response.getPublicAuthorityCode()); + } +} -- cgit v1.2.3