From 65163646205b6e05139485fe957bceabe531f447 Mon Sep 17 00:00:00 2001 From: Christof Rabensteiner Date: Tue, 9 Jul 2019 12:56:54 +0200 Subject: Minor Fixes And Refactoring - Fix typo in SafeResponseToFileSink. - MoaZSException: Simplify constructor by replacing lots of arguments with the builder instance. - Fix minor codesmells (unused imports, superfluous braces). --- src/main/java/at/gv/egiz/moazs/MoaZSException.java | 25 ++-- .../at/gv/egiz/moazs/client/ClientFactory.java | 113 +++++++++++++++++ .../at/gv/egiz/moazs/client/SSLContextCreator.java | 133 +++++++++++++++++++++ .../at/gv/egiz/moazs/config/ServicesConfig.java | 1 - .../egiz/moazs/process/SafeResponseToFileSink.java | 84 ------------- .../egiz/moazs/process/SaveResponseToFileSink.java | 87 ++++++++++++++ .../moazs/process/SingleNodeResponseHandler.java | 2 +- .../java/at/gv/egiz/moazs/scheme/MsgResponse.java | 10 +- .../gv/egiz/moazs/scheme/NotificationResponse.java | 6 +- .../java/at/gv/egiz/moazs/util/ClientFactory.java | 113 ----------------- .../at/gv/egiz/moazs/util/SSLContextCreator.java | 133 --------------------- .../gv/egiz/moazs/SafeResponseToFileSinkTest.java | 113 ----------------- .../gv/egiz/moazs/SaveResponseToFileSinkTest.java | 113 +++++++++++++++++ 13 files changed, 464 insertions(+), 469 deletions(-) create mode 100644 src/main/java/at/gv/egiz/moazs/client/ClientFactory.java create mode 100644 src/main/java/at/gv/egiz/moazs/client/SSLContextCreator.java delete mode 100644 src/main/java/at/gv/egiz/moazs/process/SafeResponseToFileSink.java create mode 100644 src/main/java/at/gv/egiz/moazs/process/SaveResponseToFileSink.java delete mode 100644 src/main/java/at/gv/egiz/moazs/util/ClientFactory.java delete mode 100644 src/main/java/at/gv/egiz/moazs/util/SSLContextCreator.java delete mode 100644 src/test/java/at/gv/egiz/moazs/SafeResponseToFileSinkTest.java create mode 100644 src/test/java/at/gv/egiz/moazs/SaveResponseToFileSinkTest.java diff --git a/src/main/java/at/gv/egiz/moazs/MoaZSException.java b/src/main/java/at/gv/egiz/moazs/MoaZSException.java index 1e86c22..dbb2894 100644 --- a/src/main/java/at/gv/egiz/moazs/MoaZSException.java +++ b/src/main/java/at/gv/egiz/moazs/MoaZSException.java @@ -5,9 +5,6 @@ import at.gv.zustellung.msg.xsd.PreAdviceNoteSentType; import at.gv.zustellung.tnvz.xsd.PersonResultType; import org.springframework.lang.Nullable; -import java.util.concurrent.ExecutorService; - - public class MoaZSException extends RuntimeException { public static final String ERROR_MZS_MIMETYPE_MISSMATCH = "8001"; @@ -27,19 +24,16 @@ public class MoaZSException extends RuntimeException { @Nullable private final String appDeliveryID; - private MoaZSException(String message, @Nullable Throwable cause, @Nullable String errorCode, @Nullable PreAdviceNoteSentType preAdviceNoteSent, - @Nullable String deliverySystem, @Nullable String gz, @Nullable String zsDeliveryID, - @Nullable String appDeliveryID) { - super(message, cause); - this.errorCode = errorCode; - this.preAdviceNoteSent = preAdviceNoteSent; - this.deliverySystem = deliverySystem; - this.gz = gz; - this.zsDeliveryID = zsDeliveryID; - this.appDeliveryID = appDeliveryID; + private MoaZSException(Builder builder) { + super(builder.message, builder.cause); + this.errorCode = builder.errorCode; + this.preAdviceNoteSent = builder.preAdviceNoteSent; + this.deliverySystem = builder.deliverySystem; + this.gz = builder.gz; + this.zsDeliveryID = builder.zsDeliveryID; + this.appDeliveryID = builder.appDeliveryID; } - public static MoaZSException moaZSException(String message, Throwable cause) { return moaZSExceptionBuilder(message).withCause(cause).build(); } @@ -166,8 +160,7 @@ public class MoaZSException extends RuntimeException { } public MoaZSException build() { - return new MoaZSException(message, cause, errorCode, preAdviceNoteSent, deliverySystem, gz, - zsDeliveryID, appDeliveryID); + return new MoaZSException(this); } } diff --git a/src/main/java/at/gv/egiz/moazs/client/ClientFactory.java b/src/main/java/at/gv/egiz/moazs/client/ClientFactory.java new file mode 100644 index 0000000..d0a445b --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/client/ClientFactory.java @@ -0,0 +1,113 @@ +package at.gv.egiz.moazs.client; + +import at.gv.egiz.moazs.util.FileUtils; +import at.gv.egiz.moazs.util.StoreSOAPBodyBinaryInRepositoryInterceptor; +import at.gv.zustellung.app2mzs.xsd.ClientType; +import at.gv.zustellung.app2mzs.xsd.KeyStoreType; +import at.gv.zustellung.app2mzs.xsd.SSLType; +import org.apache.cxf.configuration.jsse.TLSClientParameters; +import org.apache.cxf.endpoint.Client; +import org.apache.cxf.frontend.ClientProxy; +import org.apache.cxf.jaxws.JaxWsClientFactoryBean; +import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; +import org.apache.cxf.transport.http.HTTPConduit; +import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; +import javax.xml.ws.BindingProvider; +import javax.xml.ws.soap.SOAPBinding; + +import static at.gv.zustellung.app2mzs.xsd.KeyStoreType.keyStoreTypeBuilder; + +@Component +public class ClientFactory { + + private static final Logger log = LoggerFactory.getLogger(ClientFactory.class); + + private final StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor; + private final SSLContextCreator sslContextCreator; + private final FileUtils fileUtils; + + @Autowired + public ClientFactory(StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor, + SSLContextCreator creator, + FileUtils fileUtils) { + this.storeResponseInterceptor = storeResponseInterceptor; + this.sslContextCreator = creator; + this.fileUtils = fileUtils; + } + + /** + * Creates a client that communicates with a soap service. + * + * @param params for the client, such as service url and ssl parameters. + * @return the client + */ + public T create(ClientType params, Class clazz) { + + var factory = new JaxWsClientFactoryBean(); + factory.setServiceClass(clazz); + factory.setAddress(params.getURL()); + factory.getInInterceptors().add(storeResponseInterceptor); + + var proxy = new JaxWsProxyFactoryBean(factory).create(); + Client client = ClientProxy.getClient(proxy); + HTTPConduit http = (HTTPConduit) client.getConduit(); + + var bindingProvider = (BindingProvider) proxy; + SOAPBinding binding = (SOAPBinding) bindingProvider.getBinding(); + binding.setMTOMEnabled(true); + + var httpClientPolicy = new HTTPClientPolicy(); + httpClientPolicy.setConnectionTimeout(params.getConnectionTimeout().longValueExact()); + httpClientPolicy.setReceiveTimeout(params.getReceiveTimeout().longValueExact()); + http.setClient(httpClientPolicy); + + if (params.getURL().startsWith("https")) { + TLSClientParameters tlsParams = setupTLSParams(params.getSSL()); + http.setTlsClientParameters(tlsParams); + log.info("SSLContext initialized. "); + } + + return ((T)proxy); + } + + private TLSClientParameters setupTLSParams(SSLType ssl) { + + var tlsParams = new TLSClientParameters(); + var keystore = resolveKeyStorePath(ssl.getKeyStore()); + + SSLContext sslContext; + if (ssl.isTrustAll()) { + sslContext = sslContextCreator.createUnsafeSSLContext(keystore); + } else { + var truststore = resolveKeyStorePath(ssl.getTrustStore()); + sslContext = sslContextCreator.createSSLContext(keystore, truststore); + } + tlsParams.setSSLSocketFactory(sslContext.getSocketFactory()); + + if (ssl.isLaxHostNameVerification()) { + tlsParams.setDisableCNCheck(true); + } + + return tlsParams; + } + + private KeyStoreType resolveKeyStorePath(@Nullable KeyStoreType store) { + + if (store == null) return null; + + var resolvedURI = "file:" + fileUtils.determinePath(store.getFileName()); + log.trace("Resolved key store path from {} to {}.", store.getFileName(), resolvedURI); + + return keyStoreTypeBuilder(store) + .withFileName(resolvedURI) + .build(); + } + +} diff --git a/src/main/java/at/gv/egiz/moazs/client/SSLContextCreator.java b/src/main/java/at/gv/egiz/moazs/client/SSLContextCreator.java new file mode 100644 index 0000000..8fb5d80 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/client/SSLContextCreator.java @@ -0,0 +1,133 @@ +package at.gv.egiz.moazs.client; + +import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; +import at.gv.zustellung.app2mzs.xsd.KeyStoreType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import javax.net.ssl.X509TrustManager; + +import javax.net.ssl.*; +import java.io.IOException; +import java.security.*; + +import static at.gv.egiz.moazs.MoaZSException.moaZSException; +import static java.lang.String.format; + +@Component + /** + * Adapted from at.asitplus.eidas.specific.modules.authmodule_eIDASv2.szr.SZRClient + */ +public class SSLContextCreator { + + private static final Logger log = LoggerFactory.getLogger(SSLContextCreator.class); + + private static final String SSL_WARN_MAN_IN_THE_MIDDLE_MSG = + "HTTP Client trusts ANY server certificate and is therefore vulnerable to Man-In-The-Middle attacks. " + + "Use this configuration for testing purposes only and NOT IN PRODUCTION. "; + + /** + * Creates an SSL Context. + * + * @param keystore (if null, use no key store) + * @param truststore (if null, use default trust store) + * @throws at.gv.egiz.moazs.MoaZSException + */ + public SSLContext createSSLContext(@Nullable KeyStoreType keystore, @Nullable KeyStoreType truststore) { + return createSSLContext(keystore, false, truststore); + } + + /** + * Creates an SSL Context that trusts all certificates. Don't use in production. + * + * @param keystore (if null, use no key store) + * @throws at.gv.egiz.moazs.MoaZSException + */ + public SSLContext createUnsafeSSLContext(@Nullable KeyStoreType keystore) { + log.warn(SSL_WARN_MAN_IN_THE_MIDDLE_MSG); + return createSSLContext(keystore, true, null); + } + + private SSLContext createSSLContext(@Nullable KeyStoreType keystore, boolean trustAll, @Nullable KeyStoreType truststore) { + try { + SSLContext context = SSLContext.getInstance("TLS"); + KeyManager[] keyManager = initKeyManager(keystore); + TrustManager[] trustManager = trustAll + ? new TrustManager[]{new TrustAllManager()} + : initTrustManager(truststore); + context.init(keyManager, trustManager, new SecureRandom()); + return context; + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw moaZSException("SSLContext initialization FAILED.", e); + } + } + + private KeyManager[] initKeyManager(KeyStoreType keystore) { + if (keystore == null) { + log.trace("No keystore path provided. NOT using SSL client authentication. "); + return null; + } else { + log.trace("Find keystore path: {}. Injecting SSL client certificate... ", keystore.getFileName()); + try { + KeyStore keyStore = KeyStoreUtils.loadKeyStore( + keystore.getFileType(), keystore.getFileName(), keystore.getPassword()); + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(keyStore, keystore.getPassword().toCharArray()); + log.trace("SSL client certificate injected."); + return kmf.getKeyManagers(); + } catch (IOException | GeneralSecurityException e) { + throw moaZSException(format("Can NOT load SSL client certificate from path: %s.", + keystore.getFileName()), e); + } + } + } + + private TrustManager[] initTrustManager(KeyStoreType truststore) { + if (truststore == null) { + log.trace("Using default truststore. "); + return null; + } else { + log.trace("Find truststore path: {}. Injecting SSL truststore... ", truststore.getFileName()); + try { + KeyStore trustStore = KeyStoreUtils.loadKeyStore( + truststore.getFileType(), truststore.getFileName(), truststore.getPassword()); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + tmf.init(trustStore); + log.trace("SSL TrustStore injected to client. "); + return tmf.getTrustManagers(); + } catch (GeneralSecurityException | IOException e) { + throw moaZSException(format("Can NOT open SSL TrustStore from path: %s.", + truststore.getFileName()), e); + } + } + } + + /** + * Class implementing a trust manager that trusts all certificates. + * + * @author Arne Tauber + */ + public static class TrustAllManager implements X509TrustManager { + + private static Logger log = LoggerFactory.getLogger(TrustAllManager.class); + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void checkClientTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { + log.debug("Automatically accepting client certificate as trusted."); + } + + public void checkServerTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { + log.debug("Automatically accepting server certificate as trusted."); + } + } + + +} diff --git a/src/main/java/at/gv/egiz/moazs/config/ServicesConfig.java b/src/main/java/at/gv/egiz/moazs/config/ServicesConfig.java index 8e354ab..42019b1 100644 --- a/src/main/java/at/gv/egiz/moazs/config/ServicesConfig.java +++ b/src/main/java/at/gv/egiz/moazs/config/ServicesConfig.java @@ -3,7 +3,6 @@ package at.gv.egiz.moazs.config; import at.gv.egiz.moazs.service.MsgService; import at.gv.egiz.moazs.service.MzsService; import at.gv.egiz.moazs.util.EndpointFactory; -import at.gv.egiz.moazs.util.StoreSOAPBodyBinaryInRepositoryInterceptor; import at.gv.zustellung.app2mzs.xsd.App2Mzs; import at.gv.zustellung.msg.xsd.Zuse2AppPortService; import org.apache.cxf.interceptor.Interceptor; diff --git a/src/main/java/at/gv/egiz/moazs/process/SafeResponseToFileSink.java b/src/main/java/at/gv/egiz/moazs/process/SafeResponseToFileSink.java deleted file mode 100644 index 12a9fe3..0000000 --- a/src/main/java/at/gv/egiz/moazs/process/SafeResponseToFileSink.java +++ /dev/null @@ -1,84 +0,0 @@ -package at.gv.egiz.moazs.process; - -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 static java.lang.String.format; -import static java.util.concurrent.CompletableFuture.allOf; -import static java.util.concurrent.CompletableFuture.supplyAsync; - -@Component -public class SafeResponseToFileSink implements MsgResponseSink { - - private static final Logger log = LoggerFactory.getLogger(SafeResponseToFileSink.class); - private static final String SAFING_FAILED_MSG = "Could not save response with AppDeliveryId=%s."; - private static final SimpleDateFormat ISO_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - - private final Marshaller msgMarshaller; - private final DeliveryRepository respository; - private final String root; - - - @Autowired - public SafeResponseToFileSink(Marshaller msgMarshaller, DeliveryRepository binaryRepository, String root) { - this.msgMarshaller = msgMarshaller; - this.respository = binaryRepository; - this.root = root; - } - - @Override - public CompletableFuture handle(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(() -> respository.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 iso8601_now = ISO_FORMATTER.format(new Date()); - return format("%s/%s/%s.%s", root, folder, iso8601_now, suffix); - } - - private String sanitizeFileString(String fileString) { - return fileString.replaceAll("[^a-zA-Z0-9\\._\\-]", ""); - } - - private Void logException(Throwable ex, String appDeliveryID) { - log.error(format(SAFING_FAILED_MSG, appDeliveryID), ex); - return null; - } - - private void storeToFile(String path, byte[] content) { - try { - FileUtils.writeByteArrayToFile(new File(path), content); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/src/main/java/at/gv/egiz/moazs/process/SaveResponseToFileSink.java b/src/main/java/at/gv/egiz/moazs/process/SaveResponseToFileSink.java new file mode 100644 index 0000000..09d8570 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/process/SaveResponseToFileSink.java @@ -0,0 +1,87 @@ +package at.gv.egiz.moazs.process; + +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 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 MsgResponseSink { + + 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 handle(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/process/SingleNodeResponseHandler.java b/src/main/java/at/gv/egiz/moazs/process/SingleNodeResponseHandler.java index b115e27..65f5eed 100644 --- a/src/main/java/at/gv/egiz/moazs/process/SingleNodeResponseHandler.java +++ b/src/main/java/at/gv/egiz/moazs/process/SingleNodeResponseHandler.java @@ -21,6 +21,6 @@ public class SingleNodeResponseHandler implements MsgResponseHandler { @Override public void handle(String responseID) { supplyAsync(() -> verifier.verify(responseID)) - .thenAcceptAsync((response) -> sink.handle(response)); + .thenAcceptAsync(response -> sink.handle(response)); } } diff --git a/src/main/java/at/gv/egiz/moazs/scheme/MsgResponse.java b/src/main/java/at/gv/egiz/moazs/scheme/MsgResponse.java index 699819f..80e2059 100644 --- a/src/main/java/at/gv/egiz/moazs/scheme/MsgResponse.java +++ b/src/main/java/at/gv/egiz/moazs/scheme/MsgResponse.java @@ -19,10 +19,10 @@ public abstract class MsgResponse { return id; } - abstract public T getResponse(); - abstract public String getAppDeliveryID(); - abstract public String getZSDeliveryID(); - abstract public DeliveryAnswerType getAnswer(); - abstract public MsgResponse generateError(MoaZSException exception); + public abstract T getResponse(); + public abstract String getAppDeliveryID(); + public abstract String getZSDeliveryID(); + public abstract DeliveryAnswerType getAnswer(); + public abstract MsgResponse generateError(MoaZSException exception); } diff --git a/src/main/java/at/gv/egiz/moazs/scheme/NotificationResponse.java b/src/main/java/at/gv/egiz/moazs/scheme/NotificationResponse.java index 7670ab7..de1fd38 100644 --- a/src/main/java/at/gv/egiz/moazs/scheme/NotificationResponse.java +++ b/src/main/java/at/gv/egiz/moazs/scheme/NotificationResponse.java @@ -9,7 +9,7 @@ import static at.gv.zustellung.msg.xsd.DeliveryNotificationType.deliveryNotifica public class NotificationResponse extends MsgResponse { private final DeliveryNotificationType notification; - private final static String ID_SUFFIX = ".NO"; + private static final String ID_SUFFIX = ".NO"; public NotificationResponse(DeliveryNotificationType notification) { super.id = createResponseId(notification.getAppDeliveryID(), ID_SUFFIX); @@ -44,14 +44,14 @@ public class NotificationResponse extends MsgResponse public MsgResponse generateError(MoaZSException exception) { //TODO: test this! - var notification = deliveryNotificationTypeBuilder() + var notificationType = deliveryNotificationTypeBuilder() .withAppDeliveryID(exception.getAppDeliveryID()) .withDeliverySystem(exception.getDeliverySystem()) .withGZ(exception.getGz()) .withZSDeliveryID(exception.getZsDeliveryID()) .build(); - return new NotificationResponse(notification); + return new NotificationResponse(notificationType); } } diff --git a/src/main/java/at/gv/egiz/moazs/util/ClientFactory.java b/src/main/java/at/gv/egiz/moazs/util/ClientFactory.java deleted file mode 100644 index d0a445b..0000000 --- a/src/main/java/at/gv/egiz/moazs/util/ClientFactory.java +++ /dev/null @@ -1,113 +0,0 @@ -package at.gv.egiz.moazs.client; - -import at.gv.egiz.moazs.util.FileUtils; -import at.gv.egiz.moazs.util.StoreSOAPBodyBinaryInRepositoryInterceptor; -import at.gv.zustellung.app2mzs.xsd.ClientType; -import at.gv.zustellung.app2mzs.xsd.KeyStoreType; -import at.gv.zustellung.app2mzs.xsd.SSLType; -import org.apache.cxf.configuration.jsse.TLSClientParameters; -import org.apache.cxf.endpoint.Client; -import org.apache.cxf.frontend.ClientProxy; -import org.apache.cxf.jaxws.JaxWsClientFactoryBean; -import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; -import org.apache.cxf.transport.http.HTTPConduit; -import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.annotation.Nullable; -import javax.net.ssl.SSLContext; -import javax.xml.ws.BindingProvider; -import javax.xml.ws.soap.SOAPBinding; - -import static at.gv.zustellung.app2mzs.xsd.KeyStoreType.keyStoreTypeBuilder; - -@Component -public class ClientFactory { - - private static final Logger log = LoggerFactory.getLogger(ClientFactory.class); - - private final StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor; - private final SSLContextCreator sslContextCreator; - private final FileUtils fileUtils; - - @Autowired - public ClientFactory(StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor, - SSLContextCreator creator, - FileUtils fileUtils) { - this.storeResponseInterceptor = storeResponseInterceptor; - this.sslContextCreator = creator; - this.fileUtils = fileUtils; - } - - /** - * Creates a client that communicates with a soap service. - * - * @param params for the client, such as service url and ssl parameters. - * @return the client - */ - public T create(ClientType params, Class clazz) { - - var factory = new JaxWsClientFactoryBean(); - factory.setServiceClass(clazz); - factory.setAddress(params.getURL()); - factory.getInInterceptors().add(storeResponseInterceptor); - - var proxy = new JaxWsProxyFactoryBean(factory).create(); - Client client = ClientProxy.getClient(proxy); - HTTPConduit http = (HTTPConduit) client.getConduit(); - - var bindingProvider = (BindingProvider) proxy; - SOAPBinding binding = (SOAPBinding) bindingProvider.getBinding(); - binding.setMTOMEnabled(true); - - var httpClientPolicy = new HTTPClientPolicy(); - httpClientPolicy.setConnectionTimeout(params.getConnectionTimeout().longValueExact()); - httpClientPolicy.setReceiveTimeout(params.getReceiveTimeout().longValueExact()); - http.setClient(httpClientPolicy); - - if (params.getURL().startsWith("https")) { - TLSClientParameters tlsParams = setupTLSParams(params.getSSL()); - http.setTlsClientParameters(tlsParams); - log.info("SSLContext initialized. "); - } - - return ((T)proxy); - } - - private TLSClientParameters setupTLSParams(SSLType ssl) { - - var tlsParams = new TLSClientParameters(); - var keystore = resolveKeyStorePath(ssl.getKeyStore()); - - SSLContext sslContext; - if (ssl.isTrustAll()) { - sslContext = sslContextCreator.createUnsafeSSLContext(keystore); - } else { - var truststore = resolveKeyStorePath(ssl.getTrustStore()); - sslContext = sslContextCreator.createSSLContext(keystore, truststore); - } - tlsParams.setSSLSocketFactory(sslContext.getSocketFactory()); - - if (ssl.isLaxHostNameVerification()) { - tlsParams.setDisableCNCheck(true); - } - - return tlsParams; - } - - private KeyStoreType resolveKeyStorePath(@Nullable KeyStoreType store) { - - if (store == null) return null; - - var resolvedURI = "file:" + fileUtils.determinePath(store.getFileName()); - log.trace("Resolved key store path from {} to {}.", store.getFileName(), resolvedURI); - - return keyStoreTypeBuilder(store) - .withFileName(resolvedURI) - .build(); - } - -} diff --git a/src/main/java/at/gv/egiz/moazs/util/SSLContextCreator.java b/src/main/java/at/gv/egiz/moazs/util/SSLContextCreator.java deleted file mode 100644 index 8fb5d80..0000000 --- a/src/main/java/at/gv/egiz/moazs/util/SSLContextCreator.java +++ /dev/null @@ -1,133 +0,0 @@ -package at.gv.egiz.moazs.client; - -import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; -import at.gv.zustellung.app2mzs.xsd.KeyStoreType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.lang.Nullable; -import org.springframework.stereotype.Component; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import javax.net.ssl.X509TrustManager; - -import javax.net.ssl.*; -import java.io.IOException; -import java.security.*; - -import static at.gv.egiz.moazs.MoaZSException.moaZSException; -import static java.lang.String.format; - -@Component - /** - * Adapted from at.asitplus.eidas.specific.modules.authmodule_eIDASv2.szr.SZRClient - */ -public class SSLContextCreator { - - private static final Logger log = LoggerFactory.getLogger(SSLContextCreator.class); - - private static final String SSL_WARN_MAN_IN_THE_MIDDLE_MSG = - "HTTP Client trusts ANY server certificate and is therefore vulnerable to Man-In-The-Middle attacks. " + - "Use this configuration for testing purposes only and NOT IN PRODUCTION. "; - - /** - * Creates an SSL Context. - * - * @param keystore (if null, use no key store) - * @param truststore (if null, use default trust store) - * @throws at.gv.egiz.moazs.MoaZSException - */ - public SSLContext createSSLContext(@Nullable KeyStoreType keystore, @Nullable KeyStoreType truststore) { - return createSSLContext(keystore, false, truststore); - } - - /** - * Creates an SSL Context that trusts all certificates. Don't use in production. - * - * @param keystore (if null, use no key store) - * @throws at.gv.egiz.moazs.MoaZSException - */ - public SSLContext createUnsafeSSLContext(@Nullable KeyStoreType keystore) { - log.warn(SSL_WARN_MAN_IN_THE_MIDDLE_MSG); - return createSSLContext(keystore, true, null); - } - - private SSLContext createSSLContext(@Nullable KeyStoreType keystore, boolean trustAll, @Nullable KeyStoreType truststore) { - try { - SSLContext context = SSLContext.getInstance("TLS"); - KeyManager[] keyManager = initKeyManager(keystore); - TrustManager[] trustManager = trustAll - ? new TrustManager[]{new TrustAllManager()} - : initTrustManager(truststore); - context.init(keyManager, trustManager, new SecureRandom()); - return context; - } catch (NoSuchAlgorithmException | KeyManagementException e) { - throw moaZSException("SSLContext initialization FAILED.", e); - } - } - - private KeyManager[] initKeyManager(KeyStoreType keystore) { - if (keystore == null) { - log.trace("No keystore path provided. NOT using SSL client authentication. "); - return null; - } else { - log.trace("Find keystore path: {}. Injecting SSL client certificate... ", keystore.getFileName()); - try { - KeyStore keyStore = KeyStoreUtils.loadKeyStore( - keystore.getFileType(), keystore.getFileName(), keystore.getPassword()); - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); - kmf.init(keyStore, keystore.getPassword().toCharArray()); - log.trace("SSL client certificate injected."); - return kmf.getKeyManagers(); - } catch (IOException | GeneralSecurityException e) { - throw moaZSException(format("Can NOT load SSL client certificate from path: %s.", - keystore.getFileName()), e); - } - } - } - - private TrustManager[] initTrustManager(KeyStoreType truststore) { - if (truststore == null) { - log.trace("Using default truststore. "); - return null; - } else { - log.trace("Find truststore path: {}. Injecting SSL truststore... ", truststore.getFileName()); - try { - KeyStore trustStore = KeyStoreUtils.loadKeyStore( - truststore.getFileType(), truststore.getFileName(), truststore.getPassword()); - TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); - tmf.init(trustStore); - log.trace("SSL TrustStore injected to client. "); - return tmf.getTrustManagers(); - } catch (GeneralSecurityException | IOException e) { - throw moaZSException(format("Can NOT open SSL TrustStore from path: %s.", - truststore.getFileName()), e); - } - } - } - - /** - * Class implementing a trust manager that trusts all certificates. - * - * @author Arne Tauber - */ - public static class TrustAllManager implements X509TrustManager { - - private static Logger log = LoggerFactory.getLogger(TrustAllManager.class); - - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - - public void checkClientTrusted(X509Certificate[] arg0, String arg1) - throws CertificateException { - log.debug("Automatically accepting client certificate as trusted."); - } - - public void checkServerTrusted(X509Certificate[] arg0, String arg1) - throws CertificateException { - log.debug("Automatically accepting server certificate as trusted."); - } - } - - -} diff --git a/src/test/java/at/gv/egiz/moazs/SafeResponseToFileSinkTest.java b/src/test/java/at/gv/egiz/moazs/SafeResponseToFileSinkTest.java deleted file mode 100644 index c85c601..0000000 --- a/src/test/java/at/gv/egiz/moazs/SafeResponseToFileSinkTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package at.gv.egiz.moazs; - -import at.gv.egiz.moazs.process.SafeResponseToFileSink; -import at.gv.egiz.moazs.repository.DeliveryRepository; -import at.gv.egiz.moazs.scheme.Marshaller; -import at.gv.egiz.moazs.scheme.RequestStatusResponse; -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Collection; - -import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.Success.successBuilder; -import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.deliveryRequestStatusTypeBuilder; -import static java.util.Optional.of; -import static org.apache.commons.io.FileUtils.readFileToString; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class SafeResponseToFileSinkTest { - - private static final Logger log = LoggerFactory.getLogger(SafeResponseToFileSinkTest.class); - - private SafeResponseToFileSink sink; - - private final String root = "./target/tmp/SafeResponseToFileSinkTestOut"; - - @Mock - private DeliveryRepository repository; - - @Mock - private Marshaller marshaller; - - - @Before - public void setup() { - sink = new SafeResponseToFileSink(marshaller, repository, root); - deleteRoot(); - - } - - private void deleteRoot() { - try { - FileUtils.deleteDirectory(new File(root)); - } catch (IOException e) { - log.warn("Could not delete {}", root); - } - } - - @Test - public void safeRequestToFiles() { - - var fileContent = "some content"; - var status = setupMocks(fileContent); - - sink.handle(status) - .thenRun(() -> assertFilesCreatedAndContentMatches(fileContent)); - } - - private void assertFilesCreatedAndContentMatches(String fileContent) { - var rootFolder = new File(root); - Collection files = FileUtils.listFiles(rootFolder, null, true); - - assertThat(rootFolder.exists()).isTrue(); - assertThat(rootFolder.isDirectory()).isTrue(); - assertThat(files).isNotEmpty(); - - files.stream() - .map(file -> readFile(file)) - .forEach(content -> assertThat(content).isEqualTo(fileContent)); - } - - private String readFile(File file) { - try { - return readFileToString(file, StandardCharsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private RequestStatusResponse setupMocks(String fileContent) { - - var appDeliveryID = "app-delivery-id"; - var responseID = RequestStatusResponse.getResponseID(appDeliveryID); - - when(repository.retrieveBinaryResponse(responseID)) - .thenReturn(of(fileContent.getBytes(StandardCharsets.UTF_8))); - when(marshaller.marshallXml(any())).thenReturn(fileContent); - - var success = successBuilder() - .withAppDeliveryID(appDeliveryID) - .build(); - - var status = deliveryRequestStatusTypeBuilder() - .withSuccess(success) - .build(); - - return new RequestStatusResponse(status); - - - } - -} diff --git a/src/test/java/at/gv/egiz/moazs/SaveResponseToFileSinkTest.java b/src/test/java/at/gv/egiz/moazs/SaveResponseToFileSinkTest.java new file mode 100644 index 0000000..ca501c8 --- /dev/null +++ b/src/test/java/at/gv/egiz/moazs/SaveResponseToFileSinkTest.java @@ -0,0 +1,113 @@ +package at.gv.egiz.moazs; + +import at.gv.egiz.moazs.process.SaveResponseToFileSink; +import at.gv.egiz.moazs.repository.DeliveryRepository; +import at.gv.egiz.moazs.scheme.Marshaller; +import at.gv.egiz.moazs.scheme.RequestStatusResponse; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Collection; + +import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.Success.successBuilder; +import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.deliveryRequestStatusTypeBuilder; +import static java.util.Optional.of; +import static org.apache.commons.io.FileUtils.readFileToString; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class SaveResponseToFileSinkTest { + + private static final Logger log = LoggerFactory.getLogger(SaveResponseToFileSinkTest.class); + + private SaveResponseToFileSink sink; + + private final String root = "./target/tmp/SaveResponseToFileSinkTestOut"; + + @Mock + private DeliveryRepository repository; + + @Mock + private Marshaller marshaller; + + + @Before + public void setup() { + sink = new SaveResponseToFileSink(marshaller, repository, root); + deleteRoot(); + + } + + private void deleteRoot() { + try { + FileUtils.deleteDirectory(new File(root)); + } catch (IOException e) { + log.warn("Could not delete {}", root); + } + } + + @Test + public void saveRequestToFiles() { + + var fileContent = "some content"; + var status = setupMocks(fileContent); + + sink.handle(status) + .thenRun(() -> assertFilesCreatedAndContentMatches(fileContent)); + } + + private void assertFilesCreatedAndContentMatches(String fileContent) { + var rootFolder = new File(root); + Collection files = FileUtils.listFiles(rootFolder, null, true); + + assertThat(rootFolder.exists()).isTrue(); + assertThat(rootFolder.isDirectory()).isTrue(); + assertThat(files).isNotEmpty(); + + files.stream() + .map(file -> readFile(file)) + .forEach(content -> assertThat(content).isEqualTo(fileContent)); + } + + private String readFile(File file) { + try { + return readFileToString(file, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private RequestStatusResponse setupMocks(String fileContent) { + + var appDeliveryID = "app-delivery-id"; + var responseID = RequestStatusResponse.getResponseID(appDeliveryID); + + when(repository.retrieveBinaryResponse(responseID)) + .thenReturn(of(fileContent.getBytes(StandardCharsets.UTF_8))); + when(marshaller.marshallXml(any())).thenReturn(fileContent); + + var success = successBuilder() + .withAppDeliveryID(appDeliveryID) + .build(); + + var status = deliveryRequestStatusTypeBuilder() + .withSuccess(success) + .build(); + + return new RequestStatusResponse(status); + + + } + +} -- cgit v1.2.3