diff --git a/client/src/test/java/dev/fitko/fitconnect/client/SubscriberClientTest.java b/client/src/test/java/dev/fitko/fitconnect/client/SubscriberClientTest.java index bb982ef402ea3187d1f45b52ea4d9426373419b3..32e34aed7afc9127fa93a6ffdcd26644d7a3cf86 100644 --- a/client/src/test/java/dev/fitko/fitconnect/client/SubscriberClientTest.java +++ b/client/src/test/java/dev/fitko/fitconnect/client/SubscriberClientTest.java @@ -134,6 +134,7 @@ class SubscriberClientTest { when(subscriberMock.decryptStringContent(any(), any())).thenReturn(mapper.writeValueAsBytes(metadata)); when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateSubmissionDataSchema(any(), any())).thenReturn(ValidationResult.ok()); // When underTest.requestSubmission(submissionId).acceptSubmission(); @@ -172,6 +173,7 @@ class SubscriberClientTest { when(subscriberMock.decryptStringContent(any(), any())).thenReturn(mapper.writeValueAsBytes(metadata)); when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateSubmissionDataSchema(any(), any())).thenReturn(ValidationResult.ok()); // When underTest.requestSubmission(submissionId).rejectSubmission(List.of(new InvalidEventLog())); @@ -263,6 +265,7 @@ class SubscriberClientTest { when(subscriberMock.getSubmission(any())).thenReturn(submission); when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateSubmissionDataSchema(any(), any())).thenReturn(ValidationResult.ok()); when(subscriberMock.decryptStringContent(encryptionKey, submission.getEncryptedData())).thenReturn(dataPayload.getBytes()); when(subscriberMock.decryptStringContent(encryptionKey, submission.getEncryptedMetadata())).thenReturn(metadataBytes); @@ -348,6 +351,7 @@ class SubscriberClientTest { when(subscriberMock.decryptStringContent(any(), any())).thenReturn(invalidMetadata); when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateSubmissionDataSchema(any(), any())).thenReturn(ValidationResult.ok()); // When final var receivedSubmission = underTest.requestSubmission(submissionId); @@ -398,6 +402,7 @@ class SubscriberClientTest { when(subscriberMock.getSubmission(any())).thenReturn(submission); when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateSubmissionDataSchema(any(), any())).thenReturn(ValidationResult.ok()); when(subscriberMock.decryptStringContent(encryptionKey, submission.getEncryptedData())).thenReturn(dataPayload.getBytes()); when(subscriberMock.decryptStringContent(encryptionKey, submission.getEncryptedMetadata())).thenReturn(metadataBytes); @@ -458,6 +463,59 @@ class SubscriberClientTest { logs.assertContains("Data might be corrupted, hash-sum does not match"); } + @Test + void testInvalidSubmissionDataSchema() throws JsonProcessingException { + + // Given + final RSAKey decryptionKey = privateKey; + final RSAKey encryptionKey = decryptionKey.toPublicJWK(); + final CryptoService cryptoService = new JWECryptoService(new HashService()); + + final var dataPayload = "some data that was transferred"; + + final var hash = new Hash(); + hash.setContent(cryptoService.hashBytes(dataPayload.getBytes())); + final SubmissionSchema submissionSchema = new SubmissionSchema(); + submissionSchema.setSchemaUri(URI.create("urn:something")); + final var data = new Data(); + data.setHash(hash); + data.setSubmissionSchema(submissionSchema); + final var contentStructure = new ContentStructure(); + contentStructure.setData(data); + final var metadata = new Metadata(); + metadata.setContentStructure(contentStructure); + + final String encryptedData = cryptoService.encryptString(encryptionKey, mapper.writeValueAsString(data)); + final String encryptedMetadata = cryptoService.encryptString(encryptionKey, mapper.writeValueAsString(metadata)); + final var metadataBytes = mapper.writeValueAsBytes(metadata); + final var dataBytes = mapper.writeValueAsBytes(data); + + final var submissionId = UUID.randomUUID(); + final var destinationId = UUID.randomUUID(); + final var caseId = UUID.randomUUID(); + + final var submission = new Submission(); + submission.setCaseId(caseId); + submission.setSubmissionId(submissionId); + submission.setDestinationId(destinationId); + submission.setEncryptedMetadata(encryptedMetadata); + submission.setEncryptedData(encryptedData); + + when(subscriberMock.getSubmission(any())).thenReturn(submission); + when(subscriberMock.decryptStringContent(decryptionKey, encryptedMetadata)).thenReturn(metadataBytes); + when(subscriberMock.decryptStringContent(decryptionKey, encryptedData)).thenReturn(dataBytes); + when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateSubmissionDataSchema(any(), any())).thenReturn(ValidationResult.error(new RuntimeException("Something went wrong!"))); + + // When + final var receivedSubmission = underTest.requestSubmission(submissionId); + + // Then + assertNull(receivedSubmission); + logs.assertContains("Submission data does not match the provided schema"); + } + @Test void testCorruptedAttachment() throws JsonProcessingException { // Given @@ -467,8 +525,11 @@ class SubscriberClientTest { final var dataHash = new Hash(); dataHash.setContent(""); + final SubmissionSchema submissionSchema = new SubmissionSchema(); + submissionSchema.setSchemaUri(URI.create("urn:something")); final var data = new Data(); data.setHash(dataHash); + data.setSubmissionSchema(submissionSchema); final var attachmentHash = new Hash(); attachmentHash.setContent(""); @@ -502,6 +563,7 @@ class SubscriberClientTest { when(subscriberMock.decryptStringContent(decryptionKey, encryptedMetadata)).thenReturn(metadataBytes); when(subscriberMock.decryptStringContent(decryptionKey, encryptedData)).thenReturn(dataBytes); when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateSubmissionDataSchema(any(), any())).thenReturn(ValidationResult.ok()); final var decryptedDataBytes = cryptoService.decryptBytes(decryptionKey, submission.getEncryptedData()); when(subscriberMock.validateHashIntegrity(dataHash.getContent(), decryptedDataBytes)).thenReturn(ValidationResult.ok()); diff --git a/client/src/test/java/dev/fitko/fitconnect/client/util/ValidDataGuardTest.java b/client/src/test/java/dev/fitko/fitconnect/client/util/ValidDataGuardTest.java index ed2f3167720a079eaee6b28377e28bab1a28170f..8f5e702d98b7896ff45503e85f19db41a6d37ed6 100644 --- a/client/src/test/java/dev/fitko/fitconnect/client/util/ValidDataGuardTest.java +++ b/client/src/test/java/dev/fitko/fitconnect/client/util/ValidDataGuardTest.java @@ -252,11 +252,9 @@ class ValidDataGuardTest { } } - @Nested class EncryptedSubmissionPayloadDataTests { - @Test void testValidEncryptedSubmissionPayload() { diff --git a/core/src/main/java/dev/fitko/fitconnect/core/validation/DefaultValidationService.java b/core/src/main/java/dev/fitko/fitconnect/core/validation/DefaultValidationService.java index ba18643c862ff7cf0d1cbb40a572de4289a70413..7656fda15ef084e5256d44f4e4b5ec4bc24187e7 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/validation/DefaultValidationService.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/validation/DefaultValidationService.java @@ -155,7 +155,7 @@ public class DefaultValidationService implements ValidationService { if (config.getCurrentEnvironment().isSkipSubmissionDataValidation()) { LOGGER.warn("Submission data validation is deactivated. This should be done only on secure test environments."); - return ValidationResult.ok(); + return ValidationResult.ok(); } String schema = schemaProvider.loadSubmissionDataSchema(schemaUri); diff --git a/core/src/test/java/dev/fitko/fitconnect/core/schema/SchemaResourceProviderTest.java b/core/src/test/java/dev/fitko/fitconnect/core/schema/SchemaResourceProviderTest.java index 764644acc6ba655c6a259d84647a9eab21e1411a..10b954b6ae4fe5fe9f1f426a302ad857c44032c6 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/schema/SchemaResourceProviderTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/schema/SchemaResourceProviderTest.java @@ -8,15 +8,20 @@ import dev.fitko.fitconnect.api.exceptions.SchemaNotFoundException; import dev.fitko.fitconnect.api.services.schema.SchemaProvider; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestTemplate; import java.net.URI; import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class SchemaResourceProviderTest { @@ -95,4 +100,59 @@ class SchemaResourceProviderTest { final URI schemaUri = URI.create("https://schema.fitko.de/fit-connect/metadata/9.9.9/metadata.schema.json"); assertThrows(SchemaNotFoundException.class, () -> underTest.loadMetadataSchema(schemaUri)); } + + @Test + void loadSubmissionDataSchemaFromRemote() { + + RestTemplate restTemplate = mock(RestTemplate.class); + when(restTemplate.getForEntity(eq(URI.create("https://test.json")), any())).thenReturn(ResponseEntity.ok("schema")); + + SchemaResourceProvider schemaResourceProvider = new SchemaResourceProvider(restTemplate, + new SchemaResources(List.of(), List.of(), List.of(), List.of())); + + String schema = schemaResourceProvider.loadSubmissionDataSchema(URI.create("https://test.json")); + + assertThat(schema, equalTo("schema")); + } + + @Test + void loadSubmissionDataSchemaFromRemoteFailed() { + + RestTemplate restTemplate = mock(RestTemplate.class); + when(restTemplate.getForEntity(eq(URI.create("https://test.json")), any())).thenThrow(new RuntimeException("Something went wrong!")); + + SchemaResourceProvider schemaResourceProvider = new SchemaResourceProvider(restTemplate, + new SchemaResources(List.of(), List.of(), List.of(), List.of())); + + Exception exception = assertThrows(SchemaNotFoundException.class, + () -> schemaResourceProvider.loadSubmissionDataSchema(URI.create("https://test.json"))); + + assertThat(exception.getMessage(), equalTo("Submission data schema https://test.json is not available.")); + } + + @Test + void loadSubmissionDataSchemaFromMemory() { + + SchemaResources schemaResources = new SchemaResources(List.of(), List.of(), List.of(), + List.of("/submission_data_schema_local.json")); + + SchemaResourceProvider schemaResourceProvider = new SchemaResourceProvider(mock(RestTemplate.class), schemaResources); + + String schema = schemaResourceProvider.loadSubmissionDataSchema(URI.create("urn:test:submission_data_schema.json")); + + assertThat(schema, containsString("\"$id\": \"urn:test:submission_data_schema.json\"")); + } + + @Test + void loadSubmissionDataSchemaFromMemoryFailed() { + + SchemaResources schemaResources = new SchemaResources(List.of(), List.of(), List.of(), List.of()); + + SchemaResourceProvider schemaResourceProvider = new SchemaResourceProvider(mock(RestTemplate.class), schemaResources); + + Exception exception = assertThrows(SchemaNotFoundException.class, + () -> schemaResourceProvider.loadSubmissionDataSchema(URI.create("urn:test:submission_data_schema.json"))); + + assertThat(exception.getMessage(), equalTo("Submission data schema urn:test:submission_data_schema.json is not available.")); + } } \ No newline at end of file diff --git a/core/src/test/java/dev/fitko/fitconnect/core/validation/DefaultValidationServiceTest.java b/core/src/test/java/dev/fitko/fitconnect/core/validation/DefaultValidationServiceTest.java index 14b68de5f07aaf6e376dde378aaa2b872eaa084d..0eb691a6532e697499528397e53c6dcc15bb1da9 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/validation/DefaultValidationServiceTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/validation/DefaultValidationServiceTest.java @@ -56,7 +56,7 @@ class DefaultValidationServiceTest { @BeforeEach void setUp() { - final var config = getApplicationConfig(true); + final var config = getApplicationConfig(true, false); hashService = new HashService(); final List<String> setSchemas = SchemaConfig.getSetSchemaFilePaths("/set-schema"); final List<String> metadataSchemas = SchemaConfig.getMetadataSchemaFileNames("/metadata-schema"); @@ -132,7 +132,7 @@ class DefaultValidationServiceTest { void testInvalidPublicKeyWithInsecureKeyAllowed() throws Exception { // Given - final ApplicationConfig config = getApplicationConfig(true); + final ApplicationConfig config = getApplicationConfig(true, false); final var underTest = new DefaultValidationService(config, hashService, schemaProvider); final RSAKey rsaKey = new RSAKeyGenerator(4096) @@ -152,7 +152,7 @@ class DefaultValidationServiceTest { void testInvalidPublicKeyWithInsecureKeyNotAllowed() throws Exception { // Given - final ApplicationConfig config = getApplicationConfig(false); + final ApplicationConfig config = getApplicationConfig(false, false); final var underTest = new DefaultValidationService(config, hashService, schemaProvider); final RSAKey rsaKey = new RSAKeyGenerator(4096) @@ -414,7 +414,7 @@ class DefaultValidationServiceTest { // Given SchemaProvider schemaProvider = mock(SchemaProvider.class); when(schemaProvider.loadSubmissionDataSchema(any())).thenReturn("{}"); - DefaultValidationService defaultValidationService = new DefaultValidationService(getApplicationConfig(true), new HashService(), schemaProvider); + DefaultValidationService defaultValidationService = new DefaultValidationService(getApplicationConfig(true, false), new HashService(), schemaProvider); final var validJson = getResource("/valid_json_data.json"); @@ -431,7 +431,7 @@ class DefaultValidationServiceTest { // Given SchemaProvider schemaProvider = mock(SchemaProvider.class); when(schemaProvider.loadSubmissionDataSchema(any())).thenReturn("{}"); - DefaultValidationService defaultValidationService = new DefaultValidationService(getApplicationConfig(true), new HashService(), schemaProvider); + DefaultValidationService defaultValidationService = new DefaultValidationService(getApplicationConfig(true, false), new HashService(), schemaProvider); final var invalidJson = getResource("/invalid_json_data.json"); @@ -449,8 +449,8 @@ class DefaultValidationServiceTest { // Given SchemaProvider schemaProvider = mock(SchemaProvider.class); - when(schemaProvider.loadSubmissionDataSchema(any())).thenReturn(getResource("/submission_data_schema.json")); - DefaultValidationService defaultValidationService = new DefaultValidationService(getApplicationConfig(true), new HashService(), schemaProvider); + when(schemaProvider.loadSubmissionDataSchema(any())).thenReturn(getResource("/submission_data_schema_remote.json")); + DefaultValidationService defaultValidationService = new DefaultValidationService(getApplicationConfig(true, false), new HashService(), schemaProvider); final var invalidJson = "{ \"someString\": \"someStringValue\", \"someArray\": [\"someArrayValue\"] }"; @@ -467,8 +467,8 @@ class DefaultValidationServiceTest { // Given SchemaProvider schemaProvider = mock(SchemaProvider.class); - when(schemaProvider.loadSubmissionDataSchema(any())).thenReturn(getResource("/submission_data_schema.json")); - DefaultValidationService defaultValidationService = new DefaultValidationService(getApplicationConfig(true), new HashService(), schemaProvider); + when(schemaProvider.loadSubmissionDataSchema(any())).thenReturn(getResource("/submission_data_schema_remote.json")); + DefaultValidationService defaultValidationService = new DefaultValidationService(getApplicationConfig(true, false), new HashService(), schemaProvider); final var invalidJson = "{ \"wrongString\": \"someStringValue\", \"someArray\": [\"someArrayValue\"] }"; @@ -481,6 +481,24 @@ class DefaultValidationServiceTest { assertThat(validationResult.getError().getMessage(), containsString("$.someString")); } + @Test + void testIgnoreInvalidSubmissionDataSchemaWhenValidationIsDeactivated() throws IOException { + + // Given + SchemaProvider schemaProvider = mock(SchemaProvider.class); + when(schemaProvider.loadSubmissionDataSchema(any())).thenReturn(getResource("/submission_data_schema_remote.json")); + DefaultValidationService defaultValidationService = new DefaultValidationService(getApplicationConfig(true, true), new HashService(), schemaProvider); + + final var invalidJson = "{ \"wrongString\": \"someStringValue\", \"someArray\": [\"someArrayValue\"] }"; + + // When + final ValidationResult validationResult = defaultValidationService.validateSubmissionDataSchema(invalidJson, URI.create("urn:something")); + + // Then + assertTrue(validationResult.isValid()); + assertFalse(validationResult.hasError()); + } + @Test void validateCallback() { @@ -545,10 +563,11 @@ class DefaultValidationServiceTest { ); } - private static ApplicationConfig getApplicationConfig(final boolean allowInsecureKey) { + private static ApplicationConfig getApplicationConfig(final boolean allowInsecureKey, final boolean skipSubmissionDataValidation) { final var envName = new EnvironmentName("testing"); final var testing = new Environment(); testing.setAllowInsecurePublicKey(allowInsecureKey); + testing.setSkipSubmissionDataValidation(skipSubmissionDataValidation); final var config = new ApplicationConfig(); config.setEnvironments(Map.of(envName, testing)); config.setActiveEnvironment(envName); diff --git a/core/src/test/resources/submission_data_schema_local.json b/core/src/test/resources/submission_data_schema_local.json new file mode 100644 index 0000000000000000000000000000000000000000..f1c0d28907ef60a277ca5cb8d6cb6b3e29688018 --- /dev/null +++ b/core/src/test/resources/submission_data_schema_local.json @@ -0,0 +1,22 @@ +{ + "type": "object", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:test:submission_data_schema.json", + "title": "test service", + "properties": { + "someString": { + "type": "string" + }, + "someArray": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + }, + "required": [ + "someString", + "someArray" + ] +} \ No newline at end of file diff --git a/core/src/test/resources/submission_data_schema.json b/core/src/test/resources/submission_data_schema_remote.json similarity index 100% rename from core/src/test/resources/submission_data_schema.json rename to core/src/test/resources/submission_data_schema_remote.json