From 6d21176aec4a02d83c1f5804405a1c23126a8913 Mon Sep 17 00:00:00 2001
From: Martin Vogel <martin.vogel@sinc.de>
Date: Thu, 1 Sep 2022 17:58:38 +0000
Subject: [PATCH] Validierung des Metadaten Schemas

---
 .../api/config/ApplicationConfig.java         |  2 +
 .../fitconnect/api/config/Subscriber.java     |  2 -
 .../fitko/fitconnect/api/services/Sender.java | 18 ++++++
 .../fitko/fitconnect/client/SenderClient.java | 26 ++++----
 .../client/factory/ClientFactory.java         | 21 +++---
 .../client/ClientIntegrationTest.java         |  4 +-
 .../fitconnect/client/SenderClientTest.java   | 33 ++++++++--
 .../client/factory/ClientFactoryTest.java     |  6 +-
 .../fitconnect/core/SubmissionSender.java     | 23 ++++++-
 .../fitconnect/core/SubmissionSenderTest.java | 64 +++++++++++++++----
 sdk.conf                                      | 32 +++++-----
 11 files changed, 167 insertions(+), 64 deletions(-)

diff --git a/api/src/main/java/de/fitko/fitconnect/api/config/ApplicationConfig.java b/api/src/main/java/de/fitko/fitconnect/api/config/ApplicationConfig.java
index 9ff027204..e5ba97d3c 100644
--- a/api/src/main/java/de/fitko/fitconnect/api/config/ApplicationConfig.java
+++ b/api/src/main/java/de/fitko/fitconnect/api/config/ApplicationConfig.java
@@ -8,6 +8,8 @@ public class ApplicationConfig {
     private String httpProxyHost = "";
     private Integer httpProxyPort = 0;
     private Integer requestTimeoutInSeconds;
+    private String metadataSchemaPath;
+    private String privateSigningKeyPath;
 
     private Sender sender;
     private Subscriber subscriber;
diff --git a/api/src/main/java/de/fitko/fitconnect/api/config/Subscriber.java b/api/src/main/java/de/fitko/fitconnect/api/config/Subscriber.java
index b3e3b9427..6d68a9718 100644
--- a/api/src/main/java/de/fitko/fitconnect/api/config/Subscriber.java
+++ b/api/src/main/java/de/fitko/fitconnect/api/config/Subscriber.java
@@ -8,7 +8,5 @@ public class Subscriber {
     private String clientId;
     private String clientSecret;
     private String privateDecryptionKeyPath;
-    private String privateSigningKeyPath;
-    private String metadataSchemaPath;
     private String securityEventTokenSchemaPath;
 }
diff --git a/api/src/main/java/de/fitko/fitconnect/api/services/Sender.java b/api/src/main/java/de/fitko/fitconnect/api/services/Sender.java
index 78c7359f2..b925b4bbe 100644
--- a/api/src/main/java/de/fitko/fitconnect/api/services/Sender.java
+++ b/api/src/main/java/de/fitko/fitconnect/api/services/Sender.java
@@ -38,6 +38,14 @@ public interface Sender {
      */
     ValidationResult validatePublicKey(final RSAKey publicKey);
 
+    /**
+     * Validates the {@link Metadata} structure against a given JSON-schema to ensure its correctness.
+     *
+     * @param metadata the {@link Metadata} object that is validated
+     * @return a {@link ValidationResult}, contains an error if the {@link Metadata} is invalid or doesn't match the schema
+     */
+    ValidationResult validateMetadata(Metadata metadata, String schema);
+
     /**
      * Encrypts the submission data payload (json or xml) with JWE (JSON-Web-Encryption).
      *
@@ -115,4 +123,14 @@ public interface Sender {
      * @return the destination
      */
     Destination getDestination(UUID destinationId);
+
+    /**
+     * Sends a rejection event if the submission violates any validation rule.
+     *
+     * @param submissionId unique identifier of submission
+     * @param destinationId unique identifier of destination
+     * @param caseId unique identifier of case
+     * @see <a href="https://docs.fitko.de/fit-connect/docs/receiving/process-and-acknowledge">Process And Acknowledge</a>
+     */
+    void rejectSubmission(UUID submissionId, UUID destinationId, UUID caseId);
 }
diff --git a/client/src/main/java/de/fitko/fitconnect/client/SenderClient.java b/client/src/main/java/de/fitko/fitconnect/client/SenderClient.java
index 9cc27a0e6..f3ce55bb9 100644
--- a/client/src/main/java/de/fitko/fitconnect/client/SenderClient.java
+++ b/client/src/main/java/de/fitko/fitconnect/client/SenderClient.java
@@ -45,8 +45,8 @@ public class SenderClient {
     private SenderClient() {
     }
 
-    public static WithDestination build(final Sender sender) {
-        return new ClientBuilder(sender);
+    public static WithDestination build(final Sender sender, final String metadataSchema) {
+        return new ClientBuilder(sender, metadataSchema);
     }
 
     public interface WithDestination {
@@ -159,10 +159,12 @@ public class SenderClient {
         private DataPayload dataPayload;
         private UUID destinationId;
         private ServiceType serviceType;
+        private final String metadataSchema;
         private List<File> attachments = new ArrayList<>();
 
-        public ClientBuilder(final Sender sender) {
+        public ClientBuilder(final Sender sender, final String metadataSchema) {
             this.sender = sender;
+            this.metadataSchema = metadataSchema;
         }
 
         @Override
@@ -239,9 +241,16 @@ public class SenderClient {
 
                 /** Set encrypted metadata with data payload and attachments  **/
                 logger.info("Adding data payload with mime-type {} to submission", this.dataPayload.mimeType);
-                final DataPayload data = getEncryptedData(this.dataPayload, destination, encryptionKey);
-                submission.setEncryptedData(data.getEncryptedData());
-                submission.setEncryptedMetadata(getEncryptedMetadata(encryptionKey, data, hashedAttachments));
+                final DataPayload dataToSend = getEncryptedData(this.dataPayload, destination, encryptionKey);
+                final Metadata metadata = createMetadata(hashedAttachments, dataToSend);
+                final ValidationResult validatedMetadata = sender.validateMetadata(metadata, metadataSchema);
+                if (validatedMetadata.hasError()) {
+                    logger.error("Metadata does not match schema", validatedMetadata.getError());
+                    sender.rejectSubmission(submissionId, destinationId, announcedSubmission.getCaseId());
+                    return Optional.empty();
+                }
+                submission.setEncryptedData(dataToSend.getEncryptedData());
+                submission.setEncryptedMetadata(sender.encryptObject(encryptionKey, metadata));
 
                 /** submit submission **/
                 sender.sendSubmission(submission);
@@ -277,11 +286,6 @@ public class SenderClient {
                     .withHashedData(hashedData);
         }
 
-        private String getEncryptedMetadata(final RSAKey encryptionKey, final DataPayload dataPayload, final List<Attachment> hashedAttachments) {
-            final Metadata metadata = createMetadata(hashedAttachments, dataPayload);
-            return sender.encryptObject(encryptionKey, metadata);
-        }
-
         private List<Attachment> toHashedAttachments(final List<AttachmentPayload> attachmentPayloads) {
             return attachmentPayloads.stream()
                     .map(this::toHashedAttachment)
diff --git a/client/src/main/java/de/fitko/fitconnect/client/factory/ClientFactory.java b/client/src/main/java/de/fitko/fitconnect/client/factory/ClientFactory.java
index cde5757ec..c157b3984 100644
--- a/client/src/main/java/de/fitko/fitconnect/client/factory/ClientFactory.java
+++ b/client/src/main/java/de/fitko/fitconnect/client/factory/ClientFactory.java
@@ -73,7 +73,11 @@ public class ClientFactory {
         logger.info(SENDER_BANNER);
         logger.info("Initializing sender client ...");
         final Sender sender = getSender(config);
-        return SenderClient.build(sender);
+
+        logger.info("Reading metadata schema from {} ", config.getMetadataSchemaPath());
+        final String metadataSchema = readPath(config.getMetadataSchemaPath());
+
+        return SenderClient.build(sender, metadataSchema);
     }
 
 
@@ -96,19 +100,18 @@ public class ClientFactory {
         logger.info(SUBSCRIBER_BANNER);
         logger.info("Initializing subscriber client ...");
         final Subscriber subscriber = getSubscriber(config);
-        final de.fitko.fitconnect.api.config.Subscriber subscriberConfig = config.getSubscriber();
+        final var subscriberConfig = config.getSubscriber();
 
         logger.info("Reading private key from {} ", subscriberConfig.getPrivateDecryptionKeyPath());
         final String privateKey = readPath(subscriberConfig.getPrivateDecryptionKeyPath());
 
-        logger.info("Reading metadata schema from {} ", subscriberConfig.getMetadataSchemaPath());
-        final String metadataSchema = readPath(subscriberConfig.getMetadataSchemaPath());
+        logger.info("Reading metadata schema from {} ", config.getMetadataSchemaPath());
+        final String metadataSchema = readPath(config.getMetadataSchemaPath());
 
         // TODO read SET-Event Token schema
         return SubscriberClient.builder(subscriber, privateKey, metadataSchema);
     }
 
-
     private static Subscriber getSubscriber(final ApplicationConfig config) {
         final CryptoService cryptoService = getCryptoService();
         final ValidationService validator = getValidatorService();
@@ -124,12 +127,12 @@ public class ClientFactory {
         final CryptoService cryptoService = getCryptoService();
         final ValidationService validator = getValidatorService();
         final RestTemplate restTemplate = getRestTemplate(config);
-
         final OAuthService authService = getSenderConfiguredAuthService(config, restTemplate);
         final EventLogService eventLogService = getEventLogService(config,restTemplate, authService);
         final SubmissionService submissionService = getSubmissionService(config,restTemplate, authService);
         final DestinationService destinationService = getDestinationApiService(config,restTemplate, authService);
-        return new SubmissionSender(destinationService, submissionService, eventLogService, cryptoService, validator);
+        final SecurityEventService setService = getSecurityEventTokenService(config);
+        return new SubmissionSender(destinationService, submissionService, eventLogService, cryptoService, validator, setService);
     }
 
     private static OAuthService getSenderConfiguredAuthService(final ApplicationConfig config, final RestTemplate restTemplate) {
@@ -176,8 +179,8 @@ public class ClientFactory {
     }
 
     private static SecurityEventTokenService getSecurityEventTokenService(final ApplicationConfig config) {
-        logger.info("Reading private signing key from {} ", config.getSubscriber().getPrivateSigningKeyPath());
-        final String signingKey = readPath(config.getSubscriber().getPrivateSigningKeyPath());
+        logger.info("Reading private signing key from {} ", config.getPrivateSigningKeyPath());
+        final String signingKey = readPath(config.getPrivateSigningKeyPath());
         return new SecurityEventTokenService(signingKey);
     }
 
diff --git a/client/src/test/java/de/fitko/fitconnect/client/ClientIntegrationTest.java b/client/src/test/java/de/fitko/fitconnect/client/ClientIntegrationTest.java
index bf6ff0cf0..049909a1e 100644
--- a/client/src/test/java/de/fitko/fitconnect/client/ClientIntegrationTest.java
+++ b/client/src/test/java/de/fitko/fitconnect/client/ClientIntegrationTest.java
@@ -112,8 +112,6 @@ public class ClientIntegrationTest {
         subscriber.setClientId(System.getenv("SUBSCRIBER_CLIENT_ID"));
         subscriber.setClientSecret(System.getenv("SUBSCRIBER_CLIENT_SECRET"));
         subscriber.setPrivateDecryptionKeyPath("src/test/resources/private_decryption_test_key.json");
-        subscriber.setPrivateSigningKeyPath("src/test/resources/private_test_signing_key.json");
-        subscriber.setMetadataSchemaPath("src/test/resources/metadata_schema.json");
 
         final var resourcePaths = new ResourcePaths();
         resourcePaths.setAuthTokenPath("/token");
@@ -130,6 +128,8 @@ public class ClientIntegrationTest {
         config.setEnvironments(environments);
         config.setUsedEnvironment(TEST);
         config.setResourcePaths(resourcePaths);
+        config.setMetadataSchemaPath("src/test/resources/metadata_schema.json");
+        config.setPrivateSigningKeyPath("src/test/resources/private_test_signing_key.json");
 
         return config;
     }
diff --git a/client/src/test/java/de/fitko/fitconnect/client/SenderClientTest.java b/client/src/test/java/de/fitko/fitconnect/client/SenderClientTest.java
index 7aa6eb610..40509a1c2 100644
--- a/client/src/test/java/de/fitko/fitconnect/client/SenderClientTest.java
+++ b/client/src/test/java/de/fitko/fitconnect/client/SenderClientTest.java
@@ -11,10 +11,7 @@ import de.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema;
 import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
 import de.fitko.fitconnect.api.domain.model.submission.SubmitSubmission;
 import de.fitko.fitconnect.api.domain.validation.ValidationResult;
-import de.fitko.fitconnect.api.exceptions.EncryptionException;
-import de.fitko.fitconnect.api.exceptions.KeyNotRetrievedException;
-import de.fitko.fitconnect.api.exceptions.RestApiException;
-import de.fitko.fitconnect.api.exceptions.SubmissionNotCreatedException;
+import de.fitko.fitconnect.api.exceptions.*;
 import de.fitko.fitconnect.api.services.Sender;
 import io.github.netmikey.logunit.api.LogCapturer;
 import org.junit.jupiter.api.BeforeEach;
@@ -23,6 +20,7 @@ import org.junit.jupiter.api.extension.RegisterExtension;
 import org.mockito.Mockito;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.URI;
 import java.util.List;
 import java.util.Optional;
@@ -44,9 +42,9 @@ public class SenderClientTest {
     private SenderClient.WithDestination underTest;
 
     @BeforeEach
-    public void setup() {
+    public void setup() throws IOException {
         senderMock = Mockito.mock(Sender.class);
-        underTest = SenderClient.build(senderMock);
+        underTest = SenderClient.build(senderMock, "");
     }
 
     @Test
@@ -67,6 +65,8 @@ public class SenderClientTest {
         when(senderMock.createSubmission(any())).thenReturn(announcedSubmission);
         when(senderMock.validatePublicKey(any())).thenReturn(ValidationResult.ok());
         when(senderMock.getEncryptionKeyForDestination(any(), any())).thenReturn(publicKey);
+        when(senderMock.validateMetadata(any(), any())).thenReturn(ValidationResult.ok());
+
 
         // When
         final var submission = underTest
@@ -100,6 +100,7 @@ public class SenderClientTest {
         when(senderMock.createSubmission(any())).thenReturn(announcedSubmission);
         when(senderMock.validatePublicKey(any())).thenReturn(ValidationResult.ok());
         when(senderMock.getEncryptionKeyForDestination(any(), any())).thenReturn(publicKey);
+        when(senderMock.validateMetadata(any(), any())).thenReturn(ValidationResult.ok());
 
         // When
         final var submission = underTest
@@ -407,6 +408,24 @@ public class SenderClientTest {
         logs.assertContains("Getting encryption key for destination "+ destinationId+" failed");
     }
 
+    @Test
+    public void testFailOnInvalidMetadata() throws Exception {
+
+        // Given
+        final var destinationId = setupTestMocks();
+        when(senderMock.validateMetadata(any(), any())).thenReturn(ValidationResult.error(new ValidationException("invalid metadata")));
+
+        // When
+        final Optional<SubmitSubmission> submission = underTest
+                .withDestination(destinationId)
+                .withServiceType("name", "test:key")
+                .withJsonData("{}")
+                .submit();
+
+        assertTrue(submission.isEmpty());
+        logs.assertContains("Metadata does not match schema");
+    }
+
     private UUID setupTestMocks() throws JOSEException {
         final var destinationId = UUID.randomUUID();
         final var destination = getDestination(destinationId);
@@ -418,6 +437,8 @@ public class SenderClientTest {
         when(senderMock.createSubmission(any())).thenReturn(announcedSubmission);
         when(senderMock.validatePublicKey(any())).thenReturn(ValidationResult.ok());
         when(senderMock.getEncryptionKeyForDestination(any(), any())).thenReturn(publicKey);
+        when(senderMock.validateMetadata(any(), any())).thenReturn(ValidationResult.ok());
+
         return destinationId;
     }
 
diff --git a/client/src/test/java/de/fitko/fitconnect/client/factory/ClientFactoryTest.java b/client/src/test/java/de/fitko/fitconnect/client/factory/ClientFactoryTest.java
index 3538cb2c0..af7d628c9 100644
--- a/client/src/test/java/de/fitko/fitconnect/client/factory/ClientFactoryTest.java
+++ b/client/src/test/java/de/fitko/fitconnect/client/factory/ClientFactoryTest.java
@@ -38,6 +38,8 @@ public class ClientFactoryTest {
         senderConfig.setSender(sender);
         senderConfig.setUsedEnvironment(DEV);
         senderConfig.setResourcePaths(resourcePaths);
+        senderConfig.setMetadataSchemaPath("src/test/resources/metadata_schema.json");
+        senderConfig.setPrivateSigningKeyPath("src/test/resources/private_test_signing_key.json");
 
         assertNotNull(ClientFactory.senderClient(senderConfig));
     }
@@ -57,8 +59,6 @@ public class ClientFactoryTest {
         subscriber.setClientSecret("123");
         subscriber.setClientSecret("abc");
         subscriber.setPrivateDecryptionKeyPath("src/test/resources/private_decryption_test_key.json");
-        subscriber.setPrivateSigningKeyPath("src/test/resources/private_test_signing_key.json");
-        subscriber.setMetadataSchemaPath("src/test/resources/metadata_schema.json");
 
         final var subscriberConfig = new ApplicationConfig();
         subscriberConfig.setHttpProxyHost("https://proxy.fitco.de");
@@ -67,6 +67,8 @@ public class ClientFactoryTest {
         subscriberConfig.setSubscriber(subscriber);
         subscriberConfig.setUsedEnvironment(DEV);
         subscriberConfig.setResourcePaths(resourcePaths);
+        subscriberConfig.setMetadataSchemaPath("src/test/resources/metadata_schema.json");
+        subscriberConfig.setPrivateSigningKeyPath("src/test/resources/private_test_signing_key.json");
 
         assertNotNull(ClientFactory.subscriberClient(subscriberConfig));
     }
diff --git a/core/src/main/java/de/fitko/fitconnect/core/SubmissionSender.java b/core/src/main/java/de/fitko/fitconnect/core/SubmissionSender.java
index 9847913d9..4342e03d3 100644
--- a/core/src/main/java/de/fitko/fitconnect/core/SubmissionSender.java
+++ b/core/src/main/java/de/fitko/fitconnect/core/SubmissionSender.java
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.nimbusds.jose.jwk.RSAKey;
 import de.fitko.fitconnect.api.domain.model.destination.Destination;
 import de.fitko.fitconnect.api.domain.model.jwk.JWK;
+import de.fitko.fitconnect.api.domain.model.metadata.Metadata;
 import de.fitko.fitconnect.api.domain.model.submission.CreateSubmission;
 import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
 import de.fitko.fitconnect.api.domain.model.submission.SubmitSubmission;
@@ -17,6 +18,7 @@ import de.fitko.fitconnect.api.services.Sender;
 import de.fitko.fitconnect.api.services.crypto.CryptoService;
 import de.fitko.fitconnect.api.services.destination.DestinationService;
 import de.fitko.fitconnect.api.services.events.EventLogService;
+import de.fitko.fitconnect.api.services.events.SecurityEventService;
 import de.fitko.fitconnect.api.services.submission.SubmissionService;
 import de.fitko.fitconnect.api.services.validation.ValidationService;
 import org.slf4j.Logger;
@@ -33,19 +35,22 @@ public class SubmissionSender implements Sender {
     private final CryptoService cryptoService;
     private final DestinationService destinationService;
     private final SubmissionService submissionService;
-    private final EventLogService evenLogService;
+    private final EventLogService eventLogService;
+    private final SecurityEventService securityEventService;
 
     public SubmissionSender(final DestinationService destinationService,
                             final SubmissionService submissionService,
                             final EventLogService eventLogService,
                             final CryptoService encryptionService,
-                            final ValidationService validationService) {
+                            final ValidationService validationService,
+                            final SecurityEventService securityEventService) {
 
         this.destinationService = destinationService;
         this.submissionService = submissionService;
-        this.evenLogService = eventLogService;
+        this.eventLogService = eventLogService;
         this.cryptoService = encryptionService;
         this.validationService = validationService;
+        this.securityEventService =  securityEventService;
     }
 
     @Override
@@ -54,6 +59,11 @@ public class SubmissionSender implements Sender {
         return validationService.validatePublicKey(publicKey);
     }
 
+    @Override
+    public ValidationResult validateMetadata(final Metadata metadata, final String schema) {
+        return validationService.validateMetadataSchema(schema, metadata);
+    }
+
     @Override
     public String encryptBytes(final RSAKey publicKey, final byte[] data) {
         logger.info("Encrypting ...");
@@ -110,4 +120,11 @@ public class SubmissionSender implements Sender {
         return destinationService.getDestination(destinationId);
     }
 
+    @Override
+    public void rejectSubmission(final UUID submissionId, final UUID destinationId, final UUID caseId) {
+        final String rejectSubmissionEvent = securityEventService.createRejectSubmissionEvent(submissionId, destinationId, caseId);
+        eventLogService.sendEvent(caseId, rejectSubmissionEvent);
+        logger.info("REJECTED submission {}", submissionId);
+    }
+
 }
diff --git a/core/src/test/java/de/fitko/fitconnect/core/SubmissionSenderTest.java b/core/src/test/java/de/fitko/fitconnect/core/SubmissionSenderTest.java
index 693155d0b..7cbdc8e37 100644
--- a/core/src/test/java/de/fitko/fitconnect/core/SubmissionSenderTest.java
+++ b/core/src/test/java/de/fitko/fitconnect/core/SubmissionSenderTest.java
@@ -7,26 +7,29 @@ import com.nimbusds.jose.jwk.RSAKey;
 import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
 import de.fitko.fitconnect.api.domain.model.destination.Destination;
 import de.fitko.fitconnect.api.domain.model.jwk.JWK;
+import de.fitko.fitconnect.api.domain.model.metadata.ContentStructure;
 import de.fitko.fitconnect.api.domain.model.metadata.Metadata;
+import de.fitko.fitconnect.api.domain.model.metadata.PublicServiceType;
+import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment;
+import de.fitko.fitconnect.api.domain.model.metadata.data.*;
 import de.fitko.fitconnect.api.domain.model.submission.*;
 import de.fitko.fitconnect.api.domain.validation.ValidationResult;
-import de.fitko.fitconnect.api.exceptions.EncryptionException;
-import de.fitko.fitconnect.api.exceptions.KeyNotRetrievedException;
-import de.fitko.fitconnect.api.exceptions.MetadataNotCreatedException;
-import de.fitko.fitconnect.api.exceptions.RestApiException;
+import de.fitko.fitconnect.api.exceptions.*;
 import de.fitko.fitconnect.api.services.Sender;
 import de.fitko.fitconnect.api.services.crypto.CryptoService;
 import de.fitko.fitconnect.api.services.destination.DestinationService;
 import de.fitko.fitconnect.api.services.events.EventLogService;
 import de.fitko.fitconnect.api.services.submission.SubmissionService;
 import de.fitko.fitconnect.api.services.validation.ValidationService;
+import de.fitko.fitconnect.core.events.SecurityEventTokenService;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
 
 import java.io.IOException;
+import java.net.URI;
 import java.text.ParseException;
 import java.util.Collections;
+import java.util.List;
 import java.util.UUID;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -43,24 +46,26 @@ public class SubmissionSenderTest {
     private EventLogService eventLogServiceMock;
     private CryptoService cryptoServiceMock;
     private ValidationService validationServiceMock;
+    private SecurityEventTokenService setEventServiceMock;
 
     private Sender underTest;
 
     @BeforeEach
     public void setUp() {
-
-        destinationServiceMock = Mockito.mock(DestinationService.class);
-        submissionServiceMock = Mockito.mock(SubmissionService.class);
-        eventLogServiceMock = Mockito.mock(EventLogService.class);
-        cryptoServiceMock = Mockito.mock(CryptoService.class);
-        validationServiceMock = Mockito.mock(ValidationService.class);
+        this.destinationServiceMock = mock(DestinationService.class);
+        this.submissionServiceMock = mock(SubmissionService.class);
+        this.eventLogServiceMock = mock(EventLogService.class);
+        this.cryptoServiceMock = mock(CryptoService.class);
+        this.validationServiceMock = mock(ValidationService.class);
+        this.setEventServiceMock = mock(SecurityEventTokenService.class);
 
         underTest = new SubmissionSender(
                 destinationServiceMock,
                 submissionServiceMock,
                 eventLogServiceMock,
                 cryptoServiceMock,
-                validationServiceMock
+                validationServiceMock,
+                setEventServiceMock
         );
     }
 
@@ -169,7 +174,7 @@ public class SubmissionSenderTest {
         final JWK jwk = new ObjectMapper().readValue(publicKeyJson, JWK.class);
         final RSAKey expectedRSAKey = RSAKey.parse(publicKeyJson);
 
-        when(destinationServiceMock.getEncryptionKey(any(),any())).thenReturn(jwk);
+        when(destinationServiceMock.getEncryptionKey(any(), any())).thenReturn(jwk);
 
         // When
         final RSAKey key = underTest.getEncryptionKeyForDestination(submissionId, jwk.getKid());
@@ -182,7 +187,7 @@ public class SubmissionSenderTest {
     public void getEncryptionKeyForDestinationFailed() {
 
         // Given
-        when(destinationServiceMock.getEncryptionKey(any(),any())).thenThrow(RestApiException.class);
+        when(destinationServiceMock.getEncryptionKey(any(), any())).thenThrow(RestApiException.class);
 
         // Then
         assertThrows(
@@ -276,6 +281,37 @@ public class SubmissionSenderTest {
         assertThat(destination, equalTo(expectedDestination));
     }
 
+    @Test
+    public void testValidMetadata() {
+
+        // Given
+        final var metadata = new Metadata();
+
+        when(validationServiceMock.validateMetadataSchema(any(), any())).thenReturn(ValidationResult.ok());
+
+        // When
+        final ValidationResult validatedMetadata = underTest.validateMetadata(metadata, "metadataSchema");
+
+        // Then
+        assertTrue(validatedMetadata.isValid());
+    }
+
+    @Test
+    public void testInvalidMetadata() {
+
+        // Given
+        final var invalidMetadata = new Metadata();
+        final ValidationResult expectedResult = ValidationResult.error(new ValidationException("invalid metadata"));
+        when(validationServiceMock.validateMetadataSchema(any(), any())).thenReturn(expectedResult);
+
+        // When
+        final ValidationResult validatedMetadata = underTest.validateMetadata(invalidMetadata, "metadataSchema");
+
+        // Then
+        assertTrue(validatedMetadata.hasError());
+        assertThat(validatedMetadata.getError().getMessage(), equalTo("invalid metadata"));
+    }
+
     private String getResourceAsString(final String filename) throws IOException {
         return new String(SubmissionSenderTest.class.getResourceAsStream(filename).readAllBytes());
     }
diff --git a/sdk.conf b/sdk.conf
index 02fe1889c..a09969639 100644
--- a/sdk.conf
+++ b/sdk.conf
@@ -1,7 +1,21 @@
 # SKD application properties and global configurations
 sdk {
 
-    # Credentials to authenticate via OAuth
+ # Proxy config for http api calls
+  httpProxyHost: ""
+  httpProxyPort: 0
+  requestTimeoutInSeconds: 30
+
+  # Path that references the metadata schema
+  metadataSchemaPath: "path/to/metadata_schema.json"
+
+  # Path that references the signing key file
+  privateSigningKeyPath: "path/to/singning_key.json"
+
+  # switch between the active environments DEV, PROD or TEST
+  usedEnvironment: "DEV"
+
+  # Credentials to authenticate via OAuth
   sender {
     clientId: "SenderClientID"
     clientSecret: "SenderSecret"
@@ -14,22 +28,9 @@ sdk {
     # Path that references the private key file
     privateDecryptionKeyPath: "path/to/decrpytion_key.json"
 
-    # Path that references the signing key file
-    privateSigningKeyPath: "path/to/singning_key.json"
-
-     # Path that references the metadata schema
-    metadataSchemaPath: "path/to/metadata_schema.json"
     securityEventTokenSchemaPath: ""
   }
 
-  # Proxy config for http api calls
-  httpProxyHost: ""
-  httpProxyPort: 0
-  requestTimeoutInSeconds: 30
-
-  # switch between the active environments DEV, PROD or TEST
-  usedEnvironment: "DEV"
-
   # Configured environments for all api-urls
   environments {
     dev {
@@ -46,7 +47,8 @@ sdk {
     }
   }
 
-   resourcePaths {
+  # REST endpoint paths
+  resourcePaths {
     authTokenPath: "/token"
 
     destinationPath: "/v1/destinations/{destinationId}"
-- 
GitLab