diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ca448852635fd771bdc06addf410fde9c05568a8..9fd92857d38eaafc1fd86f5d2b0470651972a3e8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -26,19 +26,19 @@ reuse: DOCKER_REGISTRY_READ: $DOCKER_PULL_REGISTRY build: - image: maven:latest + image: eclipse-temurin:11.0.18_10-jdk stage: build script: - ./mvnw $MAVEN_CLI_OPTS clean install -DskipTests --no-transfer-progress -T2 test: - image: maven:latest + image: eclipse-temurin:11.0.18_10-jdk stage: test script: - - ./mvnw $MAVEN_CLI_OPTS test --no-transfer-progress -T2 + - ./mvnw $MAVEN_CLI_OPTS verify --no-transfer-progress -T2 package: - image: maven:latest + image: eclipse-temurin:11.0.18_10-jdk stage: package before_script: &gpg-setup - apt-get update @@ -50,14 +50,15 @@ package: - ./mvnw $MAVEN_CLI_OPTS package -DskipTests artifacts: paths: - - client/target/*.jar - - core/target/*.jar - api/target/*.jar + - core/target/*.jar + - client/target/*.jar + - integration-tests/target/*.jar expire_in: 1 day when: manual deploy: - image: maven:latest + image: eclipse-temurin:11.0.18_10-jdk stage: deploy before_script: *gpg-setup script: diff --git a/.reuse/dep5 b/.reuse/dep5 index 6249c1e0b8eb8e23f2b7b44e29d2c5441460a921..802c5fab808ab34cf08b4cb87c9e34540d5ea22c 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -1,6 +1,6 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Files: api/src/* client/src/* core/src/* +Files: api/src/* client/src/* core/src/* integration-tests/src/* api/README.md client/README.md core/README.md README.md CHANGELOG.md SECURITY.md Copyright: 2022 FIT-Connect contributors @@ -10,7 +10,7 @@ Files: .mvn/wrapper/* mvnw mvnw.cmd Copyright: 2013-2022 The Apache Software Foundation License: Apache-2.0 -Files: pom.xml api/pom.xml core/pom.xml client/pom.xml open-api/pom.xml +Files: pom.xml api/pom.xml core/pom.xml client/pom.xml integration-tests/pom.xml .gitlab-ci.yml .gitignore .m2/release.xml renovate.json checkstyle.xml checkstyle-suppressions.xml config.yml Copyright: 2022 FIT-Connect contributors diff --git a/README.md b/README.md index 178122ce7eb8ee703017675c30237e8eda4adac3..494476882a9fbd458a96d64bf8d9e7331657c118 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ > $`\textcolor{#890000}{\text{THIS SDK IS IN DEVELOPMENT AND NOT READY FOR PRODUCTION USE YET!!!}}`$ +<br/> + + + ## About the FIT-Connect Java SDK The Java SDK for FIT-Connect enables to build clients for senders and subscribers without directly interacting with any REST-Endpoints. @@ -29,7 +33,7 @@ This section lists major frameworks/libraries used in the SDK. * Junit 5 FIT-Connect dependencies: -* [JWKValidator 1.4.0](https://git.fitko.de/fit-connect/jwk-validator) +* [JWKValidator 1.5.0](https://git.fitko.de/fit-connect/jwk-validator) Further 3rd party dependencies: @@ -102,11 +106,18 @@ _The following steps show how to get the SDK running_ - add reference to private signature key (JWK) to *SUBSCRIBER* section -6. Provide config via environment variable ``FIT_CONNECT_CONFIG`` or load the config with +6. Load a configuration via: ````java - final var config = ApplicationConfigLoader.loadConfig("absolute/path/to/config.yml"); - final var senderClient = ClientFactory.senderClient(config); + final ApplicationConfig config = ApplicationConfigLoader.loadConfigFromPath(Path.of("path/to/config.yml")); ```` +7. Afterwards the config is used to initialize clients with the `ClientFactory` that offer clients for sender, subscriber and routing: + ````java + final SenderClient senderClient = ClientFactory.getSenderClient(config); + // + final SubscriberClient subscriberClient = ClientFactory.getSubscriberClient(config); + // + final RoutingClient routingClient = ClientFactory.getRoutingClient(config); + ```` <p align="right">(<a href="#top">back to top</a>)</p> @@ -122,7 +133,7 @@ A typical workflow using the `RoutingClient` and `SenderClient` would be: Areas can be searched with one or more search criteria: ```java -final RoutingClient routingClient = ClientFactory.routingClient(config); +final RoutingClient routingClient = ClientFactory.getRoutingClient(config); final var citySearchCriterion = "Leip*"; final var zipCodeSearchCriterion = "04229"; @@ -152,7 +163,7 @@ __Note:__ Both, the `leikaKey` service-identifier and the region keys `ars/ags`c #### Find destination by service identifier and *areaId* ```java -final RoutingClient routingClient = ClientFactory.routingClient(config); +final RoutingClient routingClient = ClientFactory.getRoutingClient(config); final DestinationSearch search = DestinationSearch.Builder() .withLeikaKey("99123456760610") @@ -174,7 +185,7 @@ for (final Route route : routes){ Besides the areaId another search criterion for the area/region can be used as well: ```java -final RoutingClient routingClient = ClientFactory.routingClient(config); +final RoutingClient routingClient = ClientFactory.getRoutingClient(config); final DestinationSearch search = DestinationSearch.Builder() .withLeikaKey("99123456760610") @@ -208,12 +219,12 @@ If all data, metadata and attachments are encrypted outside the SDK the sender c ```java final var destinationId = UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"); -final Optional<String> publicJwk = ClientFactory.senderClient(config).getPublicKey(destinationId); +final String publicJwkAsJsonString = ClientFactory.getSenderClient(config).getPublicKey(destinationId); ``` #### 2. Send encrypted data -To send an encrypted submission the builder `EncryptedSubmissionBuilder` is needed to construct an `EncryptedSubmissionPayload` object. +To send an encrypted submission the builder `EncryptedSubmissionBuilder` is needed to construct an `SendableEncryptedSubmission` object. The payload can be submitted as shown below via the `ClientFactory`. If optional attachments are added, the UUID needs to be provided for each attachment to be able to announce them before the submission is actually sent. @@ -221,17 +232,17 @@ This is necessary because the SDK does not have access to the already encrypted ```java // The constructed client can be reused to send multiple submissions -final SenderClient senderClient = ClientFactory.senderClient(config); - -final EncryptedSubmissionPayload frontendEncryptedPayload = EncryptedSubmissionBuilder.Builder() - .withEncryptedAttachment(UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"), "$encrpyt€ed @tt@chment") // optional - .withEncryptedData("{$encrpyt€ed json}") - .withEncryptedMetadata("$encrpyt€ed metadata") - .withDestination(UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14")) - .withServiceType("Führerscheinummeldung", "urn:de:fim:leika:leistung:99400048079000") +final SenderClient senderClient = ClientFactory.getSenderClient(config); + +final SendableEncryptedSubmission encryptedSubmission = SendableEncryptedSubmission.Builder() + .setDestination(UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14")) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Führerscheinummeldung") + .setEncryptedMetadata("$encrpyt€ed metadata") + .setEncryptedData("{$encrpyt€ed json}") + .addEncryptedAttachment(new EncryptedAttachment(UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"), "$encrpyt€ed @tt@chment")) // optional .build(); -final SentSubmission sentSubmission = senderClient.submit(frontendEncryptedPayload); +final SentSubmission sentSubmission = senderClient.send(encryptedSubmission); ``` | **Important** | @@ -245,21 +256,23 @@ If all data, metadata and attachments are encrypted in the sender using the SDK, Be aware that this example is not end-2-end encrypted, see [FIT-Connect documentation](https://docs.fitko.de/fit-connect/docs/getting-started/encryption) for details. -To send a submission the builder `SubmissionBuilder` is needed to construct a `SubmissionPayload` object. +To send a submission the builder `SubmissionBuilder` is needed to construct a `SendableSubmission` object. The payload can be submitted as shown below via the `ClientFactory`. ```java // The constructed client can be reused to send multiple submissions -final SenderClient senderClient = ClientFactory.senderClient(config); - -final SubmissionPayload submissionPayload = SubmissionBuilder.Builder() - .withAttachment(Path.of("path/to/attachment.txt").toFile()) // optional - .withJsonData("{ \"foo\" : \"bar\"}") - .withDestination(UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14")) - .withServiceType("Führerscheinummeldung", "urn:de:fim:leika:leistung:99400048079000") +final SenderClient senderClient = ClientFactory.getSenderClient(config); + +final SendableSubmission sendableSubmission = SendableSubmission.Builder() + .setDestination(UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14")) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Führerscheinummeldung") + .setJsonData("{ \"foo\" : \"bar\"}") + // optional properties + .addAttachment(Attachment.fromPath(Path.of("path/to/attachment.txt"), "text/plain")) + .setReplyChannel(ReplyChannel.fromEmail("test@mail.org")) .build(); -final SentSubmission sentSubmission = senderClient.submit(submissionPayload); +final SentSubmission sentSubmission = senderClient.send(sendableSubmission); ``` @@ -269,7 +282,7 @@ To read the event-log, destinationId and caseId are needed. final var caseId = UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"); final var destinationId = UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"); -final List<EventLogEntry> eventLog = ClientFactory.senderClient(config).getEventLog(caseId, destinationId); +final List<EventLogEntry> eventLog = ClientFactory.getSenderClient(config).getEventLog(caseId, destinationId); for(EventLogEntry logEntry: eventLog) { LOGGER.info("Event: {}", logEntry.getEvent()); @@ -299,9 +312,9 @@ Instead of the entire log, the latest status for a submission can be retrieved a ```java final SentSubmission sentSubmission = ... // persisted sent submission by sender client -final EventStatus eventStatus = ClientFactory.senderClient(config).getStatusForSubmission(sentSubmission); +final EventStatus submissionStatus = ClientFactory.getSenderClient(config).getStatusForSubmission(sentSubmission); -LOGGER.info("Current status for submission {} => {}", sentSubmission.getSubmissionId(), eventStatus.getStatus()); +LOGGER.info("Current status for submission {} => {}", sentSubmission.getSubmissionId(), submissionStatus.getStatus()); ``` The example output shows the current state of the submission after being created, following the transitions in the diagram below: @@ -322,7 +335,7 @@ More details on how this method works can be found here: The Java SDK provides a convenient method for validating callbacks, its usage could look like this: ```java -final SenderClient senderClient = ClientFactory.senderClient(config); +final SenderClient senderClient = ClientFactory.getSenderClient(config); final ValidationResult validationResult = senderClient.validateCallback("hmac", 0L, "body", "secret"); @@ -338,45 +351,62 @@ Submissions can be fetched by id or as a list of submissions for a specific case #### List with pagination Limit and offset parameters allow to page through the result. ```java +final var subscriberClient = ClientFactory.getSubscriberClient(config); + final int offset = 0; final int limit = 100; final var destinationId = UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"); -final Set<SubmissionForPickup> firstOneHundredSubmissions = ClientFactory.subscriberClient(config).getAvailableSubmissions(destinationId), limit, offset); + +final Set<SubmissionForPickup> firstOneHundredSubmissions = subscriberClient.getAvailableSubmissionsforDestination(destinationId), limit, offset); ``` #### List without pagination Listing available submissions without pagination pulls the first 500 entries. ```java +final var subscriberClient = ClientFactory.getSubscriberClient(config); + final var destinationId= UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"); -final Set<SubmissionForPickup> submissions = ClientFactory.subscriberClient(config).getAvailableSubmissions(destinationId); + +final Set<SubmissionForPickup> submissions = subscriberClient.getAvailableSubmissionsForDestination(destinationId); ``` -#### Receive single submission +#### Receive single submission ```java -final submissionId = UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"); -final ReceivedSubmission receivedSubmission = ClientFactory.subscriberClient(config).requestSubmission(submissionId); +// by id +final var submissionId = UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"); +final ReceivedSubmission receivedSubmission = ClientFactory.getSubscriberClient(config).requestSubmission(submissionId); +``` +```java +// by object +final SubmissionForPickup submissionForPickup = // code for sending submission; +final ReceivedSubmission receivedSubmission = ClientFactory.getSubscriberClient(config).requestSubmission(submissionForPickup); ``` + Now, the received submission allows access to the decrypted data, attachments, metadata and ids ```java // access data -final String data = receivedData.getData(); -final URI dataSchemaUri = receivedData.getDataSchemaUri(); -final MimeType mimeType = receivedData.getDataMimeType(); +final String data = receivedSubmission.getDataAsString(); +final URI dataSchemaUri = receivedSubmission.getDataSchemaUri(); +final String mimeType = receivedSubmission.getDataMimeType(); // access metadata -final Metadata = receivedData.getSubmissionMetdata(); +final Metadata metadata = receivedSubmission.getSubmissionMetdata(); +// access reply channel +final ReplyChannel replyChannel = metadata.getReplyChannel(); // access attachments -for(final ReceivedAttachment attachment : receivedSubmission.getAttachments()){ - final byte[] attachmentRawData = attachment.getData(); +for(final Attachment attachment : receivedSubmission.getAttachments()){ final String originalFilename = attachment.getFilename(); - final UUID attachmentId = attachment.getAttachmentId(); + final String attachmentMimeType = attachment.getMimeType(); + // different formats to retrieve the attachment content + final byte[] attachmentDataAsBytes = attachment.getDataAsBytes(); + final String attachmentDataAsString = attachment.getDataAString(StandardCharsets.UTF_8); } // access further ids of submission -final UUID caseId = receivedData.getCaseId(); -final UUID submissionId = receivedData.getSubmissionId(); -final UUID destinationId = receivedData.getDestinationId(); +final UUID caseId = receivedSubmission.getCaseId(); +final UUID submissionId = receivedSubmission.getSubmissionId(); +final UUID destinationId = receivedSubmission.getDestinationId(); ``` ### Sending events to the event-log @@ -385,13 +415,20 @@ In order to accept or reject a submission, the subscriber client can send events For more details please see the documentation on [events](https://docs.fitko.de/fit-connect/docs/getting-started/event-log/overview) and the creation of [set-events](https://docs.fitko.de/fit-connect/docs/getting-started/event-log/set-creation). #### Accepting a submission -If the functional review by the subscriber was positive, the submission can be accepted with an `accept-submission` event. +If the functional review by the subscriber was positive, the submission can be accepted with an `accept-submission` event. +For this event a list of optional problems can be sent, the submission is still accepted but with remarks. ```java final var submissionId = UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"); -ClientFactory.subscriberClient(config) - .requestSubmission(submissionId) +ClientFactory.getSubscriberClient(config) + .requestSubmission(submissionId); .acceptSubmission(); + +// OR accept with an optional list of problems + +ClientFactory.getSubscriberClient(config) + .requestSubmission(submissionId); + .acceptSubmission(List.of(new MyCustomProblem())); ``` After the accept event was sent the submission transitions into the state `deleted` and is removed. #### Rejecting a submission @@ -401,11 +438,9 @@ See the Fit-Connect documentation for more details on [available (technical) pro ```java final var submissionId = UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"); -final var rejectionProblems = List.of(new DataSchemaViolation()); - -ClientFactory.subscriberClient(config) - .requestSubmission(submissionId) - .rejectSubmission(rejectionProblems); +ClientFactory.getSubscriberClient(config) + .requestSubmission(submissionId); + .rejectSubmission(List.of(new DataSchemaViolation())); ``` After the rejection event was sent the submission transitions into the state `deleted` and is removed. @@ -415,7 +450,7 @@ To read the event-log, destinationId and caseId are needed. final var caseId = UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"); final var destinationId = UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"); -final List<EventLogEntry> eventLog = ClientFactory.subscriberClient(config).getEventLog(caseId, destinationId); +final List<EventLogEntry> eventLog = ClientFactory.getSubscriberClient(config).getEventLog(caseId, destinationId); for(EventLogEntry logEntry: eventLog) { LOGGER.info("Event: {}", logEntry.getEvent()); @@ -446,7 +481,7 @@ For more details please see the [event-log documentation](https://docs.fitko.de/ The validation of callbacks works similar to the sender side (see [Sender Callback Validation](#validating-callbacks)), but instead of the `SenderClient`, we use the `SubscriberClient`: ```java -final SubscriberClient subscriberClient = ClientFactory.subscriberClient(config); +final SubscriberClient subscriberClient = ClientFactory.getSubscriberClient(config); final ValidationResult validationResult = subscriberClient.validateCallback("hmac", 0L, "body", "secret"); @@ -457,8 +492,8 @@ if(validationResult.hasError()){ ## Integration Tests -Integration tests in `dev.fitko.fitconnect.client.ClientIntegrationTest` run only on the CI-Server and are ignored by default locally. -To run them on a local machine the following environment variables have to be set in the run-configuration of the IDE: +Integration tests do not run per default with `mvn test`, but they can be executed with the maven profile `IntegrationTests` via `mvn -PIntegrationTests test`. +They expect the following environment variables to be set in the rn configuration of the IDE or on the local terminal: * SENDER_CLIENT_ID * SENDER_CLIENT_SECRET @@ -477,9 +512,8 @@ var submissionBaseUrl = "https://submission-api-testing.fit-connect.fitko.dev"; ``` ## Roadmap - - [ ] Add auto-reject on technical errors -- [ ] Maven central release of 1.0.0 +- [ ] Maven central release of 1.0.0-beta See the [open issues](https://git.fitko.de/fit-connect/planning/-/boards/44?search=SDK) for a full list of proposed features (and known issues). @@ -502,6 +536,6 @@ Hierfür kann das SDK in die anzubindenden Software integriert werden. Erfolgt die Integration des SDK in unveränderter Form, liegt keine Bearbeitung im Sinne der EUPL bzw. des deutschen Urheberrechts vor. Die Art und Weise der Verlinkung des SDK führt insbesondere nicht zur Schaffung eines abgeleiteten Werkes. Die unveränderte Übernahme des SDK in eine anzubindende Software führt damit nicht dazu, dass die anzubindende Software unter den Bedingungen der EUPL zu lizenzieren ist. -Für die Weitergabe des SDK selbst - in unveränderter oder bearbeiteter Form, als Quellcode oder ausführbares Programm - gelten die Lizenzbedingungen der EUPL in unverände*rter Weise. +Für die Weitergabe des SDK selbst - in unveränderter oder bearbeiteter Form, als Quellcode oder ausführbares Programm - gelten die Lizenzbedingungen der EUPL in unveränderter Weise.* <p align="right">(<a href="#top">back to top</a>)</p> diff --git a/api/src/main/java/dev/fitko/fitconnect/api/config/ApplicationConfig.java b/api/src/main/java/dev/fitko/fitconnect/api/config/ApplicationConfig.java index 5c57e4aa7fa47ba9f2d40b0697ab5c34537e91d9..1b4755b4378e0ecf7c3b71b41dc72b16331338ee 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/config/ApplicationConfig.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/config/ApplicationConfig.java @@ -17,13 +17,21 @@ import java.util.stream.Collectors; @AllArgsConstructor public class ApplicationConfig { + @Builder.Default + public static final String AUTH_TAG_SPLIT_TOKEN = "\\."; + @Builder.Default private String httpProxyHost = ""; + @Builder.Default private Integer httpProxyPort = 0; + @Builder.Default private Integer requestTimeoutInSeconds = 30; + @Builder.Default + private boolean enableAutoReject = true; + @Builder.Default private URI setSchemaWriteVersion = SchemaConfig.SET_V_1_0_1.getSchemaUri(); @@ -120,5 +128,9 @@ public class ApplicationConfig { .map(EnvironmentName::getName) .collect(Collectors.joining(" | ")); } + + public boolean isProxySet() { + return (httpProxyPort != null && httpProxyPort > 0) && (httpProxyHost != null || !httpProxyHost.isEmpty()); + } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/config/SubscriberConfig.java b/api/src/main/java/dev/fitko/fitconnect/api/config/SubscriberConfig.java index 19ee09c7ed00199187c4e3024ebe1defaf5d04f1..949ca70d084420305132a57a3f8183af76d41f1f 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/config/SubscriberConfig.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/config/SubscriberConfig.java @@ -5,6 +5,8 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; + @Data @Builder @AllArgsConstructor @@ -12,6 +14,6 @@ import lombok.NoArgsConstructor; public class SubscriberConfig { private String clientId; private String clientSecret; - private String privateDecryptionKeyPath; + private List<String> privateDecryptionKeyPaths; private String privateSigningKeyPath; } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/Destination.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/Destination.java index f19c30d6106c82d8e3cd4f3917a424a63dfd0041..0b4578d591532ec8f71f8a01021e913a18a99ddc 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/Destination.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/Destination.java @@ -2,6 +2,7 @@ package dev.fitko.fitconnect.api.domain.model.destination; import com.fasterxml.jackson.annotation.JsonProperty; +import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel; import dev.fitko.fitconnect.api.domain.model.submission.Callback; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/PublicDestination.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/PublicDestination.java index a5e6d1883eb34d8088335cb1de4fa48e22a1f7ef..aa85dd6c7008b604cb78af42cc604194e6de154b 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/PublicDestination.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/PublicDestination.java @@ -1,6 +1,7 @@ package dev.fitko.fitconnect.api.domain.model.destination; import com.fasterxml.jackson.annotation.JsonProperty; +import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/ReplyChannel.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/ReplyChannel.java deleted file mode 100644 index fd3a8a0565ec0a368883c09b8569f464681b1a04..0000000000000000000000000000000000000000 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/ReplyChannel.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.fitko.fitconnect.api.domain.model.destination; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -public class ReplyChannel { - - @JsonProperty("deMail") - private Object deMail; - - @JsonProperty("elster") - private Object elster; - - @JsonProperty("eMail") - private ReplyChannelEMail eMail; - - @JsonProperty("fink") - private Object fink; -} - diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/ReplyChannelEMail.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/ReplyChannelEMail.java deleted file mode 100644 index 7f9fec808762c485d58af6375dca265e66ba41d1..0000000000000000000000000000000000000000 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/destination/ReplyChannelEMail.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.fitko.fitconnect.api.domain.model.destination; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -class ReplyChannelEMail { - - @JsonProperty("usePgp") - private Boolean usePgp; -} - diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/Event.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/Event.java index 6ef771e23f2f45c25a87acf7b026d0c19329b729..a687818587d0fc93869963d3adc1e7f6ace1e53e 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/Event.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/Event.java @@ -100,12 +100,12 @@ public enum Event { @Override public SubmissionState getState() { - return SubmissionState.SUBMITTED; + return SubmissionState.NOTIFIED; } @Override public Set<Event> allowedNextEvents() { - return Set.of(SUBMIT); + return Set.of(ACCEPT, REJECT, FORWARD); } }, diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/EventClaimFields.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/EventClaimFields.java index 8f23d61fe7a5aa3b8dfeaab39f01ef7179e4e690..5218c2cb26f2cea6ab1b1654d8ecdf9213ece729 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/EventClaimFields.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/EventClaimFields.java @@ -9,5 +9,7 @@ public final class EventClaimFields { public static final String CLAIM_SCHEMA = "$schema"; public static final String CLAIM_TXN = "txn"; public static final String CLAIM_SUB = "sub"; + public static final String AUTHENTICATION_TAGS = "authenticationTags"; + public static final String PROBLEMS = "problems"; public static final String HEADER_TYPE = "secevent+jwt"; } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/EventPayload.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/EventPayload.java index bc696b2b69b44236c43e9805dedc3b0269c16e50..e57ccb0bf8251a8331125479e3d5580540dc78c0 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/EventPayload.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/EventPayload.java @@ -2,43 +2,72 @@ package dev.fitko.fitconnect.api.domain.model.event; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; import dev.fitko.fitconnect.api.domain.model.submission.Submission; +import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.UUID; @Data +@Builder +@AllArgsConstructor public class EventPayload { private UUID submissionId; private UUID destinationId; private UUID caseId; + + // used for auth-tag validation private String encryptedMetadata; private String encryptedData; private Map<UUID, String> encryptedAttachments; + private List<Problem> problems; - public EventPayload(final Submission submission, final Map<UUID, String> encryptedAttachments, final List<Problem> problems) { - submissionId = submission.getSubmissionId(); - destinationId = submission.getDestinationId(); - caseId = submission.getCaseId(); - encryptedData = submission.getEncryptedData(); - encryptedMetadata = submission.getEncryptedMetadata(); - this.encryptedAttachments = encryptedAttachments; - this.problems = problems; + public static EventPayload forRejectEvent(final SubmissionForPickup submission, final List<Problem> problems){ + return EventPayload.builder() + .submissionId(submission.getSubmissionId()) + .destinationId(submission.getDestinationId()) + .caseId(submission.getCaseId()) + .problems(problems) + .build(); } - public EventPayload(final Submission submission, final Map<UUID, String> encryptedAttachments) { - this(submission, encryptedAttachments, Collections.emptyList()); + public static EventPayload forRejectEvent(final Submission submission, final List<Problem> problems){ + return EventPayload.builder() + .submissionId(submission.getSubmissionId()) + .destinationId(submission.getDestinationId()) + .caseId(submission.getCaseId()) + .problems(problems) + .build(); } - public EventPayload(final Submission submission, final List<Problem> problems) { - this(submission, Collections.emptyMap(), problems); + public static EventPayload forAcceptEvent(final Submission submission, final Problem... problems){ + return EventPayload.builder() + .submissionId(submission.getSubmissionId()) + .destinationId(submission.getDestinationId()) + .caseId(submission.getCaseId()) + .encryptedData(submission.getEncryptedData()) + .encryptedMetadata(submission.getEncryptedMetadata()) + .encryptedAttachments(Collections.emptyMap()) + .problems(Arrays.asList(problems)) + .build(); } - public EventPayload(final Submission submission) { - this(submission, Collections.emptyMap(), Collections.emptyList()); + public static EventPayload forAcceptEventWithAttachments(final Submission submission, final Map<UUID, String> encryptedAttachments, final Problem... problems){ + return EventPayload.builder() + .submissionId(submission.getSubmissionId()) + .destinationId(submission.getDestinationId()) + .caseId(submission.getCaseId()) + .encryptedData(submission.getEncryptedData()) + .encryptedMetadata(submission.getEncryptedMetadata()) + .encryptedAttachments(encryptedAttachments) + .problems(Arrays.asList(problems)) + .build(); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/SubmissionState.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/SubmissionState.java index 2fda72b1c839b28557d070379b61bd2151a2ecb3..b46dc3a612ba3b06ec2f641d30bb376c0613c7a9 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/SubmissionState.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/SubmissionState.java @@ -7,7 +7,8 @@ public enum SubmissionState { ACCEPTED("accepted"), REJECTED("rejected"), DELETED("deleted"), - FORWARDED("forwarded"); + FORWARDED("forwarded"), + NOTIFIED("notified"); private final String name; diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/EventStatus.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/SubmissionStatus.java similarity index 91% rename from api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/EventStatus.java rename to api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/SubmissionStatus.java index e4a816f5f1db2b5ca460d33bae420a7fc639ec5f..3c5c3f539f9eaf6dd744dc02946af6cc82ded34d 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/EventStatus.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/SubmissionStatus.java @@ -10,7 +10,7 @@ import java.util.List; @Getter @AllArgsConstructor @NoArgsConstructor -public class EventStatus { +public class SubmissionStatus { SubmissionState status; List<Problem> problems; } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/authtags/AuthenticationTags.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/authtags/AuthenticationTags.java index fd09244fb4cdb267673a111279c8586ccfc22dee..9f7594ead7cf5139888f3dc5d8fcd26d7f9da16c 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/authtags/AuthenticationTags.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/authtags/AuthenticationTags.java @@ -7,7 +7,9 @@ import java.util.UUID; @Data public class AuthenticationTags { + private String metadata; private String data; private Map<UUID, String> attachments; + } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/Problem.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/Problem.java index 2b6313a6ed29f5ab8bc763ecf53f8d2d52d38299..6504666420bd3f791e1b5b86bdb13040e8081213 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/Problem.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/Problem.java @@ -1,6 +1,7 @@ package dev.fitko.fitconnect.api.domain.model.event.problems; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Data; @@ -12,11 +13,19 @@ import lombok.Data; @Data @AllArgsConstructor public class Problem { + @JsonIgnore public static final String SCHEMA_URL = "https://schema.fitko.de/fit-connect/events/problems/"; + @JsonProperty("type") private final String type; + + @JsonProperty("title") private final String title; + + @JsonProperty("detail") private final String detail; + + @JsonProperty("instance") private final String instance; } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/AttachmentEncryptionIssue.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/AttachmentEncryptionIssue.java index 5408f7eca13945b83a7d325bd537a54c7c130f3f..409e25035c478785111915653bcb03cc8e13f887 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/AttachmentEncryptionIssue.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/AttachmentEncryptionIssue.java @@ -2,16 +2,18 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.attachment; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; +import java.util.UUID; + import static java.lang.String.format; -public class AttachmentEncryptionIssue extends Problem { +public final class AttachmentEncryptionIssue extends Problem { - private static final String type = SCHEMA_URL + "encryption-issue"; - private static final String title = "Entschlüsselungs-Fehler"; - private static final String detail = "Die Anlage %s konnte nicht entschlüsselt werden."; - private static final String instance = "attachment:%s"; + private static final String TYPE = SCHEMA_URL + "encryption-issue"; + private static final String TITLE = "Decryption failure"; + private static final String DETAIL = "Decrypting attachment %s failed."; + private static final String INSTANCE = "attachment:%s"; - public AttachmentEncryptionIssue(final String attachmentId) { - super(type, title, format(detail, attachmentId), format(instance, attachmentId)); + public AttachmentEncryptionIssue(final UUID attachmentId) { + super(TYPE, TITLE, format(DETAIL, attachmentId), format(INSTANCE, attachmentId)); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/AttachmentEncryptionKeyIssue.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/AttachmentEncryptionKeyIssue.java index 1c8ec1d441ad87ceb860929a06a934ffaa6c30fe..00baff8bc62efd823e9846924a2de00a6cbf6ab7 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/AttachmentEncryptionKeyIssue.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/AttachmentEncryptionKeyIssue.java @@ -4,14 +4,18 @@ import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; import static java.lang.String.format; -public class AttachmentEncryptionKeyIssue extends Problem { +public final class AttachmentEncryptionKeyIssue extends Problem { - private static final String type = SCHEMA_URL + "encryption-issue"; - private static final String title = "Entschlüsselungs-Fehler"; - private static final String detail = "Der Schlüssel %s ist nicht der zu diesem Zweck vorgesehene Schlüssel."; - private static final String instance = "attachment:%s"; + private static final String TYPE = SCHEMA_URL + "encryption-issue"; + private static final String TITLE = "Decryption failure"; + private static final String DETAIL = "The key %s is not the key intended for this purpose."; + private static final String INSTANCE = "attachment:%s"; public AttachmentEncryptionKeyIssue(final String keyId) { - super(type, title, format(detail, keyId), format(instance, keyId)); + super(TYPE, TITLE, format(DETAIL, keyId), format(INSTANCE, keyId)); + } + + public static Problem getWithAttachmentId(final String keyId) { + return new AttachmentEncryptionKeyIssue(keyId); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/AttachmentHashMismatch.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/AttachmentHashMismatch.java index 744b4155ad374ef2252c0fdacd0c87481eda995f..eafa5058913fa74a1fcb2e2ccaa4e5ac8cc29524 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/AttachmentHashMismatch.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/AttachmentHashMismatch.java @@ -2,16 +2,18 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.attachment; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; +import java.util.UUID; + import static java.lang.String.format; -public class AttachmentHashMismatch extends Problem { +public final class AttachmentHashMismatch extends Problem { - private static final String type = SCHEMA_URL + "hash-mismatch"; - private static final String title = "Prüfsumme stimmt nicht"; - private static final String detail = "Der Hash der Anlage %s stimmt nicht."; - private static final String instance = "attachment:%s"; + private static final String TYPE = SCHEMA_URL + "hash-mismatch"; + private static final String TITLE = "Checksum does not match"; + private static final String DETAIL = "Attachment %s hash value is wrong."; + private static final String INSTANCE = "attachment:%s"; - public AttachmentHashMismatch(final String attachmentId) { - super(type, title, format(detail, attachmentId), format(instance, attachmentId)); + public AttachmentHashMismatch(final UUID attachmentId) { + super(TYPE, TITLE, format(DETAIL, attachmentId), format(INSTANCE, attachmentId)); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/IncorrectAttachmentAuthenticationTag.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/IncorrectAttachmentAuthenticationTag.java index f0eefac5fd5fabd163e37f809d8cd398fda69993..68932328be2068faf6a3dd7de58bcc46988ebdad 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/IncorrectAttachmentAuthenticationTag.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/IncorrectAttachmentAuthenticationTag.java @@ -2,16 +2,18 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.attachment; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; +import java.util.UUID; + import static java.lang.String.format; -public class IncorrectAttachmentAuthenticationTag extends Problem { +public final class IncorrectAttachmentAuthenticationTag extends Problem { - private static final String type = SCHEMA_URL + "incorrect-authentication-tag"; - private static final String title = "Authentication-Tag ungültig"; - private static final String detail = "Das Authentication-Tag der Anlage %s ist ungültig."; - private static final String instance = "attachment:%s"; + private static final String TYPE = SCHEMA_URL + "incorrect-authentication-tag"; + private static final String TITLE = "Authentication tag is invalid"; + private static final String DETAIL = "The authentication tag for the attachment %s is invalid."; + private static final String INSTANCE = "attachment:%s"; - public IncorrectAttachmentAuthenticationTag(final String attachmentId) { - super(type, title, format(detail, attachmentId), format(instance, attachmentId)); + public IncorrectAttachmentAuthenticationTag(final UUID attachmentId) { + super(TYPE, TITLE, format(DETAIL, attachmentId), format(INSTANCE, attachmentId)); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/InvalidAttachmentContent.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/InvalidAttachmentContent.java index 8c81d1886e32f92f81b099988bcdc69082df7020..3436ae96ed0529b1eb82068dbf1ba909e18d848c 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/InvalidAttachmentContent.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/InvalidAttachmentContent.java @@ -2,16 +2,19 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.attachment; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; +import java.util.UUID; + import static java.lang.String.format; -public class InvalidAttachmentContent extends Problem { +public final class InvalidAttachmentContent extends Problem { - private static final String type = SCHEMA_URL + "invalid-content"; - private static final String title = "Unzulässiger Inhalt"; - private static final String detail = "Der Inhalt der Anlage %s ist nicht zulässig."; - private static final String instance = "attachment:%s"; + private static final String TYPE = SCHEMA_URL + "invalid-content"; + private static final String TITLE = "Invalid content"; + private static final String DETAIL = "The content of the attachment %s is not allowed."; + private static final String INSTANCE = "attachment:%s"; - public InvalidAttachmentContent(final String attachmentId) { - super(type, title, format(detail, attachmentId), format(instance, attachmentId)); + public InvalidAttachmentContent(final UUID attachmentId) { + super(TYPE, TITLE, format(DETAIL, attachmentId), format(INSTANCE, attachmentId)); } + } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/MissingAttachment.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/MissingAttachment.java index e1ed4ab551e5a588157468ff7cea1e54cc333dd4..7afdec4a55a2ce3d792ca41ac773fba8a1ec2451 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/MissingAttachment.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/attachment/MissingAttachment.java @@ -2,15 +2,17 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.attachment; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; +import java.util.UUID; + import static java.lang.String.format; -public class MissingAttachment extends Problem { - private static final String type = SCHEMA_URL + "missing-attachment"; - private static final String title = "Anlage fehlt"; - private static final String detail = "Die Anlage %s konnte nicht geladen werden."; - private static final String instance = "attachment:%s"; +public final class MissingAttachment extends Problem { + private static final String TYPE = SCHEMA_URL + "missing-attachment"; + private static final String TITLE = "List of attachments is invalid"; + private static final String DETAIL = "Unable to load attachment %s."; + private static final String INSTANCE = "attachment:%s"; - public MissingAttachment(final String attachmentId){ - super(type, title, format(detail, attachmentId), format(instance, attachmentId)); + public MissingAttachment(final UUID attachmentId){ + super(TYPE, TITLE, format(DETAIL, attachmentId), format(INSTANCE, attachmentId)); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataEncryptionIssue.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataEncryptionIssue.java index bea6ba5935ff385f147971b75b02578cbf56f8b8..39f5d6d088b5561e8a6c8be3aa5f41da441952c2 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataEncryptionIssue.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataEncryptionIssue.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.data; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class DataEncryptionIssue extends Problem { +public final class DataEncryptionIssue extends Problem { - private static final String type = SCHEMA_URL + "encryption-issue"; - private static final String title = "Entschlüsselungs-Fehler"; - private static final String detail = "Der Fachdatensatz konnte nicht entschlüsselt werden."; - private static final String instance = "data"; + private static final String TYPE = SCHEMA_URL + "encryption-issue"; + private static final String TITLE = "Decryption failure"; + private static final String DETAIL = "Decrypting submission data failed."; + private static final String INSTANCE = "data"; public DataEncryptionIssue() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataEncryptionKeyIssue.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataEncryptionKeyIssue.java index fd9aad66d0730344e1ef4d6d42b3fe7cac481139..797083f1e01e2f6675c9e4ac69994730d68ef830 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataEncryptionKeyIssue.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataEncryptionKeyIssue.java @@ -4,14 +4,14 @@ import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; import static java.lang.String.format; -public class DataEncryptionKeyIssue extends Problem { +public final class DataEncryptionKeyIssue extends Problem { - private static final String type = SCHEMA_URL + "encryption-issue"; - private static final String title = "Entschlüsselungs-Fehler"; - private static final String detail = "Der Schlüssel %s ist nicht der zu diesem Zweck vorgesehene Schlüssel."; - private static final String instance = "data"; + private static final String TYPE = SCHEMA_URL + "encryption-issue"; + private static final String TITLE = "Decryption failure"; + private static final String DETAIL = "The key %s is not the key intended for this purpose."; + private static final String INSTANCE = "data"; public DataEncryptionKeyIssue(final String keyId) { - super(type, title, format(detail, keyId), instance); + super(TYPE, TITLE, format(DETAIL, keyId), INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataHashMismatch.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataHashMismatch.java index 34959d7867a2eda8d612334051eeb86658153fbe..657035eefd012ce8fa4484e49a456bb51bdf319b 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataHashMismatch.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataHashMismatch.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.data; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class DataHashMismatch extends Problem { +public final class DataHashMismatch extends Problem { - private static final String type = SCHEMA_URL + "hash-mismatch"; - private static final String title = "Prüfsumme stimmt nicht"; - private static final String detail = "Die Prüfsumme des Fachdatensatzes stimmt nicht."; - private static final String instance = "data"; + private static final String TYPE = SCHEMA_URL + "hash-mismatch"; + private static final String TITLE = "Checksum does not match"; + private static final String DETAIL = "Submission data checksum wrong."; + private static final String INSTANCE = "data"; public DataHashMismatch() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataJsonSyntaxViolation.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataJsonSyntaxViolation.java index 3d3fde4724b0a23f4bec00450e1e2185cca66992..f2f15dd6682116124c0b0838e12563c2c6e9c23a 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataJsonSyntaxViolation.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataJsonSyntaxViolation.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.data; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class DataJsonSyntaxViolation extends Problem { +public final class DataJsonSyntaxViolation extends Problem { - private static final String type = SCHEMA_URL + "syntax-violation"; - private static final String title = "Syntax-Fehler"; - private static final String detail = "Der Fachdatensatz ist kein valides JSON."; - private static final String instance = "data"; + private static final String TYPE = SCHEMA_URL + "syntax-violation"; + private static final String TITLE = "Syntax violation"; + private static final String DETAIL = "Submission data is no valid JSON."; + private static final String INSTANCE = "data"; public DataJsonSyntaxViolation() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataSchemaViolation.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataSchemaViolation.java index 94d6c7613aebffcde6ec72d2064f5b9fdfa0a6b3..31632425e625951f74747cfb0294ffcee4a2b827 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataSchemaViolation.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataSchemaViolation.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.data; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class DataSchemaViolation extends Problem { +public final class DataSchemaViolation extends Problem { - private static final String type = SCHEMA_URL + "schema-violation"; - private static final String title = "Schema-Fehler"; - private static final String detail = "Der Fachdatensatz ist nicht schema-valide."; - private static final String instance = "data"; + private static final String TYPE = SCHEMA_URL + "schema-violation"; + private static final String TITLE = "Schema violation"; + private static final String DETAIL = "Submission data does not comply to schema."; + private static final String INSTANCE = "data"; public DataSchemaViolation() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataXmlSyntaxViolation.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataXmlSyntaxViolation.java index 4098f7dd8b1b76b4749e6af8b76a0d02b560444b..fc12f172786babeab49a91c6f13d73eb85f0b1e5 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataXmlSyntaxViolation.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/DataXmlSyntaxViolation.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.data; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class DataXmlSyntaxViolation extends Problem { +public final class DataXmlSyntaxViolation extends Problem { - private static final String type = SCHEMA_URL + "syntax-violation"; - private static final String title = "Syntax-Fehler"; - private static final String detail = "Der Fachdatensatz ist kein valides XML."; - private static final String instance = "data"; + private static final String TYPE = SCHEMA_URL + "syntax-violation"; + private static final String TITLE = "Syntax violation"; + private static final String DETAIL = "Submission data is no valid XML."; + private static final String INSTANCE = "data"; public DataXmlSyntaxViolation() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/IncorrectDataAuthenticationTag.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/IncorrectDataAuthenticationTag.java index b63523fbcce783d4be34294b0792a833489e754c..463626e522050791211b9d3c52a6778d07939f92 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/IncorrectDataAuthenticationTag.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/data/IncorrectDataAuthenticationTag.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.data; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class IncorrectDataAuthenticationTag extends Problem { +public final class IncorrectDataAuthenticationTag extends Problem { - private static final String type = SCHEMA_URL + "incorrect-authentication-tag"; - private static final String title = "Authentication-Tag ungültig"; - private static final String detail = "Das Authentication-Tag des Fachdatensatzes ist ungültig."; - private static final String instance = "data"; + private static final String TYPE = SCHEMA_URL + "incorrect-authentication-tag"; + private static final String TITLE = "Authentication tag is invalid"; + private static final String DETAIL = "The authentication tag for the submission data is invalid."; + private static final String INSTANCE = "data"; public IncorrectDataAuthenticationTag() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/AttachmentsMismatch.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/AttachmentsMismatch.java index 09aea66ae80f45342c0c212609242db372ef46e1..94f1adfd0f75715a50dc785e86e78553546cd9b8 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/AttachmentsMismatch.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/AttachmentsMismatch.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.metadata; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class AttachmentsMismatch extends Problem { +public final class AttachmentsMismatch extends Problem { - private static final String type = SCHEMA_URL + "attachments-mismatch"; - private static final String title = "Fehlerhafte Anlagen-Liste"; - private static final String detail = "Die Liste der Anlagen in Submission und Metadatensatz stimmt nicht überein."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "attachments-mismatch"; + private static final String TITLE = "List of attachments is invalid"; + private static final String DETAIL = "The list of attachments in the submission and metadata record do not match."; + private static final String INSTANCE = "metadata"; public AttachmentsMismatch() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/IncorrectMetadataAuthenticationTag.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/IncorrectMetadataAuthenticationTag.java index 83227eb987bf5bae4eee561f2fd27e3d0ff52f84..c73d65ea6b656346c2dd9834742d75df647f73e8 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/IncorrectMetadataAuthenticationTag.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/IncorrectMetadataAuthenticationTag.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.metadata; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class IncorrectMetadataAuthenticationTag extends Problem { +public final class IncorrectMetadataAuthenticationTag extends Problem { - private static final String type = SCHEMA_URL + "incorrect-authentication-tag"; - private static final String title = "Authentication-Tag ungültig"; - private static final String detail = "Das Authentication-Tag des Metadatensatzes ist ungültig."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "incorrect-authentication-tag"; + private static final String TITLE = "Authentication tag is invalid"; + private static final String DETAIL = "The authentication tag for the metadata is invalid."; + private static final String INSTANCE = "metadata"; public IncorrectMetadataAuthenticationTag() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataEncryptionIssue.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataEncryptionIssue.java index a99b770938551ace51026b7e9bbf34589cb9a6c4..31a0a1dcbe9f67e6942906ea742c09f6726a3b86 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataEncryptionIssue.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataEncryptionIssue.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.metadata; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class MetadataEncryptionIssue extends Problem { +public final class MetadataEncryptionIssue extends Problem { - private static final String type = SCHEMA_URL + "encryption-issue"; - private static final String title = "Entschlüsselungs-Fehler"; - private static final String detail = "Die Entschlüsselung des Metadatensatzes ist fehlgeschlagen."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "encryption-issue"; + private static final String TITLE = "Decryption failure"; + private static final String DETAIL = "Decrypting metadata failed."; + private static final String INSTANCE = "metadata"; public MetadataEncryptionIssue() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataEncryptionKeyIssue.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataEncryptionKeyIssue.java index 9dcf99d70bd46b8a02e0490d985dc4348ad12820..c7d0791de281a64fd1198bbf5b0b18ce1aec599e 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataEncryptionKeyIssue.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataEncryptionKeyIssue.java @@ -4,14 +4,14 @@ import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; import static java.lang.String.format; -public class MetadataEncryptionKeyIssue extends Problem { +public final class MetadataEncryptionKeyIssue extends Problem { - private static final String type = SCHEMA_URL + "encryption-issue"; - private static final String title = "Entschlüsselungs-Fehler"; - private static final String detail = "Der Schlüssel %s ist nicht der zu diesem Zweck vorgesehene Schlüssel."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "encryption-issue"; + private static final String TITLE = "Encryption failure"; + private static final String DETAIL = "The key %s is not the key intended for this purpose."; + private static final String INSTANCE = "metadata"; public MetadataEncryptionKeyIssue(final String keyId) { - super(type, title, format(detail, keyId), instance); + super(TYPE, TITLE, format(DETAIL, keyId), INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataJsonSyntaxViolation.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataJsonSyntaxViolation.java index 728470dd28c09b1d694e6f2b18c6ce6849c3c5f9..566839a387cf05e59ea849be042a41fff762fd3e 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataJsonSyntaxViolation.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataJsonSyntaxViolation.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.metadata; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class MetadataJsonSyntaxViolation extends Problem { +public final class MetadataJsonSyntaxViolation extends Problem { - private static final String type = SCHEMA_URL + "syntax-violation"; - private static final String title = "Syntax-Fehler"; - private static final String detail = "Der Metadatensatz ist kein valides JSON."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "syntax-violation"; + private static final String TITLE = "Syntax violation"; + private static final String DETAIL = "Metadata record is no valid JSON."; + private static final String INSTANCE = "metadata"; public MetadataJsonSyntaxViolation() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataSchemaViolation.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataSchemaViolation.java index 4a174f8be375aa60f82671463d2dae47373c0e42..636b572909e3cee712a178edbae042e4b594a626 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataSchemaViolation.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MetadataSchemaViolation.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.metadata; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class MetadataSchemaViolation extends Problem { +public final class MetadataSchemaViolation extends Problem { - private static final String type = SCHEMA_URL + "schema-violation"; - private static final String title = "Schema-Fehler"; - private static final String detail = "Der Metadatensatz ist nicht schema-valide."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "schema-violation"; + private static final String TITLE = "Schema violation"; + private static final String DETAIL = "Submission data does not comply to schema."; + private static final String INSTANCE = "metadata"; public MetadataSchemaViolation() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MissingData.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MissingData.java index ba390303a6ecff2003c5ee83a14f01a5a65b39fb..35fda0bcc283b894d441b2444d6fa242b50d2d10 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MissingData.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MissingData.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.metadata; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class MissingData extends Problem { +public final class MissingData extends Problem { - private static final String type = SCHEMA_URL + "missing-data"; - private static final String title = "Fachdatensatz fehlt"; - private static final String detail = "Der Fachdatensatz fehlt."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "missing-data"; + private static final String TITLE = "Submission data missing"; + private static final String DETAIL = "Submission data is missing"; + private static final String INSTANCE = "metadata"; public MissingData() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MissingSchemaReference.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MissingSchemaReference.java index 17b5ec37589e6ce9853f89e9eef24db43a2afa2f..960a0d232f30d96ed9c0040f1d8b96cd007ffa40 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MissingSchemaReference.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/MissingSchemaReference.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.metadata; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class MissingSchemaReference extends Problem { +public final class MissingSchemaReference extends Problem { - private static final String type = SCHEMA_URL + "missing-schema"; - private static final String title = "Schema-Referenz fehlt"; - private static final String detail = "Die Schema-Referenz fehlt im Metadatensatz."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "missing-schema"; + private static final String TITLE = "Schema reference missing"; + private static final String DETAIL = "Metadata does not contain a schema reference."; + private static final String INSTANCE = "metadata"; public MissingSchemaReference() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/ServiceMismatch.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/ServiceMismatch.java index 9deae15efd19823f405d7342aefcbbd14960c6ac..046d6356b45c68480a76fc7389cad321db87fddd 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/ServiceMismatch.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/ServiceMismatch.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.metadata; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class ServiceMismatch extends Problem { +public final class ServiceMismatch extends Problem { - private static final String type = SCHEMA_URL + "service-mismatch"; - private static final String title = "Verwaltungsleistung stimmt nicht überein"; - private static final String detail = "Die Verwaltungsleistung in Submission und Metadatensatz stimmen nicht überein."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "service-mismatch"; + private static final String TITLE = "Service type does not match"; + private static final String DETAIL = "Service type of metadata does not match submission."; + private static final String INSTANCE = "metadata"; public ServiceMismatch() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedDataSchema.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedDataSchema.java index ccd19f355db46fad054d3540f3232b182122201f..b4951e47fae96b8ab7b7ef921a65c8f7c37ca60f 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedDataSchema.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedDataSchema.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.metadata; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class UnsupportedDataSchema extends Problem { +public final class UnsupportedDataSchema extends Problem { - private static final String type = SCHEMA_URL + "unsupported-schema"; - private static final String title = "Fachdatenschema nicht unterstützt"; - private static final String detail = "Das angegebene Fachdatenschema wird nicht unterstützt."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "unsupported-schema"; + private static final String TITLE = "Data schema not supported"; + private static final String DETAIL = "Submission data schema is not supported."; + private static final String INSTANCE = "metadata"; public UnsupportedDataSchema() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedMetadataSchema.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedMetadataSchema.java index 29bfc120d398c1f9cd5ac6dbe9ceed3b6d026577..752dd33c476523bdfda20ecf2b939b35983e1250 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedMetadataSchema.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedMetadataSchema.java @@ -4,14 +4,14 @@ import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; import static java.lang.String.format; -public class UnsupportedMetadataSchema extends Problem { +public final class UnsupportedMetadataSchema extends Problem { - private static final String type = SCHEMA_URL + "unsupported-schema"; - private static final String title = "Metadatenschema nicht unterstützt"; - private static final String detail = "Die angegebene Metadatenschema-URI ('%s') ist keines der unterstützten Metadatenschemas."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "unsupported-schema"; + private static final String TITLE = "Metadata schema not supported"; + private static final String DETAIL = "The specified metadata schema URI ('%s') is not referring to a supported metadata schema."; + private static final String INSTANCE = "metadata"; public UnsupportedMetadataSchema(final String metadataSchemaUri) { - super(type, title, format(detail, metadataSchemaUri), instance); + super(TYPE, TITLE, format(DETAIL, metadataSchemaUri), INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedReplyChannel.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedReplyChannel.java index d13246a96340a2658f3ee0487797277b71239632..7eeb1a5d2f4c641f9d79d402e1955653426fabe9 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedReplyChannel.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedReplyChannel.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.metadata; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class UnsupportedReplyChannel extends Problem { +public final class UnsupportedReplyChannel extends Problem { - private static final String type = SCHEMA_URL + "unsupported-reply-channel"; - private static final String title = "Rückkanal nicht unterstützt"; - private static final String detail = "Der gewählte Rückkanal wird nicht unterstützt."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "unsupported-reply-channel"; + private static final String TITLE = "Reply channel is not supported"; + private static final String DETAIL = "The chosen reply channel is not supported."; + private static final String INSTANCE = "metadata"; public UnsupportedReplyChannel() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedService.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedService.java index e7744a14ebf5e3e2a47d14926423303ea0fdcbc9..c6994df90fc010ffe5236cc5b3afa194746e2b51 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedService.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/metadata/UnsupportedService.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.metadata; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class UnsupportedService extends Problem { +public final class UnsupportedService extends Problem { - private static final String type = SCHEMA_URL + "unsupported-service"; - private static final String title = "Verwaltungsleistung nicht unterstützt"; - private static final String detail = "Die angegebene Verwaltungsleistung wird nicht unterstützt."; - private static final String instance = "metadata"; + private static final String TYPE = SCHEMA_URL + "unsupported-service"; + private static final String TITLE = "Service nor supported"; + private static final String DETAIL = "The specified service is not supported."; + private static final String INSTANCE = "metadata"; public UnsupportedService() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/other/TechnicalError.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/other/TechnicalError.java index 344d855d11d659982e87a1ab8a6faaf83e838e05..b947172136a789ec8a181c8400ac3f38c6ed6154 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/other/TechnicalError.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/other/TechnicalError.java @@ -2,14 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.other; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class TechnicalError extends Problem { +public final class TechnicalError extends Problem { - private static final String type = SCHEMA_URL + "technical-error"; - private static final String title = "Technischer Fehler"; - private static final String detail = "Bei der Verarbeitung im empfangenden System trat ein technischer Fehler auf."; - private static final String instance = "other"; + private static final String TYPE = SCHEMA_URL + "technical-error"; + private static final String TITLE = "Technical error"; + private static final String DETAIL = "A technical error occurred during processing in the receiving system."; + private static final String INSTANCE = "other"; public TechnicalError() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/AttachmentsMismatch.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/AttachmentsMismatch.java index 9fc8a745ef9639fe70daf552b4732c3d9c566b58..2c5cccc6c6d7a16e91083419a93c593117297619 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/AttachmentsMismatch.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/AttachmentsMismatch.java @@ -2,13 +2,13 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.submission; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class AttachmentsMismatch extends Problem { - private static final String type = SCHEMA_URL + "attachments-mismatch"; - private static final String title = "Fehlerhafte Anlagen-Liste"; - private static final String detail = "Die Liste der Anlagen in Submission und Event-Log stimmt nicht überein."; - private static final String instance = "submission"; +public final class AttachmentsMismatch extends Problem { + private static final String TYPE = SCHEMA_URL + "attachments-mismatch"; + private static final String TITLE = "List of attachments is invalid"; + private static final String DETAIL = "List of attachments in submission does not match list on event log."; + private static final String INSTANCE = "submission"; public AttachmentsMismatch() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/InvalidEventLog.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/InvalidEventLog.java index 34afda8b034db2911505bd4e2d0a1bb9a0f03a87..94996b1c29da4d0f5ebc893dee2e304867f49d07 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/InvalidEventLog.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/InvalidEventLog.java @@ -2,13 +2,14 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.submission; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class InvalidEventLog extends Problem { - private static final String type = SCHEMA_URL + "invalid-event-log"; - private static final String title = "Inkonsistentes Event-Log"; - private static final String detail = "Das Event-Log ist inkonsistent."; - private static final String instance = "submission"; +public final class InvalidEventLog extends Problem { + + private static final String TYPE = SCHEMA_URL + "invalid-event-log"; + private static final String TITLE = "Inconsistent Event-Log"; + private static final String DETAIL = "The Event-Log is inconsistent."; + private static final String INSTANCE = "submission"; public InvalidEventLog() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/MissingAuthenticationTags.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/MissingAuthenticationTags.java index c63029458d64288e7696291af7cc7a0936b0cc6f..ef1940f737b3e784deb6544bc5b922e5dd0597ac 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/MissingAuthenticationTags.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/MissingAuthenticationTags.java @@ -2,15 +2,13 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.submission; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -import static java.lang.String.format; - public class MissingAuthenticationTags extends Problem { - private static final String type = SCHEMA_URL + "missing-authentication-tags"; - private static final String title = "Fehlende Authentication-Tags"; - private static final String detail = "Das Event 'submit-submission' enthält keine Authentication-Tags."; - private static final String instance = "submission"; + private static final String TYPE = SCHEMA_URL + "missing-authentication-tags"; + private static final String TITLE = "Authentication tags missing"; + private static final String DETAIL = "The 'submit-submission' event does not contain authentication tags."; + private static final String INSTANCE = "submission"; - public MissingAuthenticationTags(final String attachmentId){ - super(type, title, format(detail, attachmentId), format(instance, attachmentId)); + public MissingAuthenticationTags(){ + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/NotExactlyOneSubmitEvent.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/NotExactlyOneSubmitEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..19c4c48bf36338d989407f350f85f49520dcca24 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/NotExactlyOneSubmitEvent.java @@ -0,0 +1,16 @@ +package dev.fitko.fitconnect.api.domain.model.event.problems.submission; + +import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; + +public class NotExactlyOneSubmitEvent extends Problem { + + private static final String TYPE = SCHEMA_URL + "invalid-event-log"; + private static final String TITLE = "Inconsistent Event-Log"; + private static final String DETAIL = "The Event-Log is inconsistent because it does not contain exactly one 'submit' event."; + private static final String INSTANCE = "submission"; + + public NotExactlyOneSubmitEvent() { + super(TYPE, TITLE, DETAIL, INSTANCE); + } + +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/SubmissionTimeout.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/SubmissionTimeout.java index deaa8eb8145e36e99b8e00ec7f26d86d4289dc2a..1d690d87e27e3b4857a55277d369669c37e5786a 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/SubmissionTimeout.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/event/problems/submission/SubmissionTimeout.java @@ -2,13 +2,13 @@ package dev.fitko.fitconnect.api.domain.model.event.problems.submission; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; -public class SubmissionTimeout extends Problem { - private static final String type = SCHEMA_URL + "timeout"; - private static final String title = "Zeitablauf"; - private static final String detail = "Die Einreichung wurde automatisch zurückgewiesen."; - private static final String instance = "submission"; +public final class SubmissionTimeout extends Problem { + private static final String TYPE = SCHEMA_URL + "timeout"; + private static final String TITLE = "Submission Timeout"; + private static final String DETAIL = "The submission was automatically rejected."; + private static final String INSTANCE = "submission"; public SubmissionTimeout() { - super(type, title, detail, instance); + super(TYPE, TITLE, DETAIL, INSTANCE); } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/AdditionalReferenceInfo.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/AdditionalReferenceInfo.java index bf180ceb8977ae03ff91e36fd2908d7b88834591..4f4c972e3d4d8cd1c14376cb40d5e2c83b19efc3 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/AdditionalReferenceInfo.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/AdditionalReferenceInfo.java @@ -2,9 +2,13 @@ package dev.fitko.fitconnect.api.domain.model.metadata; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@NoArgsConstructor +@AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) public class AdditionalReferenceInfo { @@ -13,6 +17,29 @@ public class AdditionalReferenceInfo { @JsonProperty("applicationDate") private String applicationDate; + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String senderReference; + private String applicationDate; + + public Builder withSenderReference(final String senderReference) { + this.senderReference = senderReference; + return this; + } + + public Builder withApplicationDate(final String applicationDate) { + this.applicationDate = applicationDate; + return this; + } + + public AdditionalReferenceInfo build() { + return new AdditionalReferenceInfo(senderReference, applicationDate); + } + } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/AuthenticationInformation.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/AuthenticationInformation.java index 0ce795d7c6a22a67b4912c6a2b0d8a75f039e15a..3095d6d803cf1ab8b84c9c945f0cf38ba1dd8817 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/AuthenticationInformation.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/AuthenticationInformation.java @@ -1,11 +1,13 @@ package dev.fitko.fitconnect.api.domain.model.metadata; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; import lombok.Data; -import lombok.NoArgsConstructor; @Data -@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) public class AuthenticationInformation { @JsonProperty("type") @@ -16,4 +18,32 @@ public class AuthenticationInformation { @JsonProperty("content") private String content; + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String type; + private String version; + private String content; + + public Builder withType(final String type) { + this.type = type; + return this; + } + + public Builder withVersion(final String version) { + this.version = version; + return this; + } + + public Builder withContent(final String content) { + this.content = content; + return this; + } + public AuthenticationInformation build() { + return new AuthenticationInformation(type, version, content); + } + } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/ContentStructure.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/ContentStructure.java index c0114843a36319e821ef7f8c1745fd6868fb2350..1a0d4b6dafaae8076cf9b1274849fc0ada94f997 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/ContentStructure.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/ContentStructure.java @@ -2,7 +2,7 @@ package dev.fitko.fitconnect.api.domain.model.metadata; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import lombok.NoArgsConstructor; @@ -17,5 +17,5 @@ public class ContentStructure { private Data data; @JsonProperty("attachments") - private List<Attachment> attachments; + private List<ApiAttachment> attachments; } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/Metadata.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/Metadata.java index 722cca65a189eefcd9ef6f42f0b202237fb13255..ce940361d9e4b4ff200162501262273587bd299e 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/Metadata.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/Metadata.java @@ -2,12 +2,18 @@ package dev.fitko.fitconnect.api.domain.model.metadata; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import dev.fitko.fitconnect.api.domain.model.metadata.payment.PaymentInformation; +import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import java.util.List; @Data +@EqualsAndHashCode +@AllArgsConstructor @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) public class Metadata { @@ -15,15 +21,15 @@ public class Metadata { @JsonProperty("$schema") private String schema; - @JsonProperty("contentStructure") private ContentStructure contentStructure; - @JsonProperty("publicServiceType") private PublicServiceType publicServiceType; - @JsonProperty("authenticationInformation") private List<AuthenticationInformation> authenticationInformation; - @JsonProperty("additionalReferenceInfo") + private PaymentInformation paymentInformation; + + private ReplyChannel replyChannel; + private AdditionalReferenceInfo additionalReferenceInfo; } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/attachment/Attachment.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/attachment/ApiAttachment.java similarity index 96% rename from api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/attachment/Attachment.java rename to api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/attachment/ApiAttachment.java index a1dc46ef9331eb55efd8654badab2156ac481c00..56f984c602424ad8d98720b947c2cddd97b4109b 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/attachment/Attachment.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/attachment/ApiAttachment.java @@ -12,7 +12,7 @@ import java.util.UUID; @Data @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) -public class Attachment { +public class ApiAttachment { @JsonProperty("hash") private Hash hash; diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/attachment/AttachmentForValidation.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/attachment/AttachmentForValidation.java new file mode 100644 index 0000000000000000000000000000000000000000..8fc92512872a5ad643559b6b0b31030734cdf1a9 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/attachment/AttachmentForValidation.java @@ -0,0 +1,25 @@ +package dev.fitko.fitconnect.api.domain.model.metadata.attachment; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.UUID; + +@Data +@AllArgsConstructor +public class AttachmentForValidation { + + private UUID attachmentId; + private String metadataHash; + private byte[] decryptedData; + private String encryptedData; + private ApiAttachment attachmentMetadata; + + public AttachmentForValidation(final ApiAttachment attachmentMetadata, final String encryptedData, final byte[] decryptedData){ + attachmentId = attachmentMetadata.getAttachmentId(); + metadataHash = attachmentMetadata.getHash().getContent(); + this.attachmentMetadata = attachmentMetadata; + this.decryptedData = decryptedData; + this.encryptedData = encryptedData; + } +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/payment/PaymentInformation.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/payment/PaymentInformation.java new file mode 100644 index 0000000000000000000000000000000000000000..9eb256b894e0a734ac1a3e356470cccf204f8141 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/payment/PaymentInformation.java @@ -0,0 +1,85 @@ +package dev.fitko.fitconnect.api.domain.model.metadata.payment; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +import java.net.URI; +import java.util.Date; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class PaymentInformation { + + private URI transactionUrl; + private String transactionId; + private String transactionReference; + private Date transactionTimestamp; + private PaymentMethod paymentMethod; + private String paymentMethodDetail; + private PaymentStatus status; + private double grossAmount; + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private URI transactionUrl; + private @NonNull String transactionId; + private @NonNull String transactionReference; + private @NonNull Date transactionTimestamp; + private @NonNull PaymentMethod paymentMethod; + private String paymentMethodDetail; + private @NonNull PaymentStatus status; + private double grossAmount; + + public Builder withTransactionUrl(final URI transactionUrl) { + this.transactionUrl = transactionUrl; + return this; + } + + public Builder withTransactionId(final String transactionId) { + this.transactionId = transactionId; + return this; + } + + public Builder withTransactionReference(final String transactionReference) { + this.transactionReference = transactionReference; + return this; + } + + public Builder withTransactionTimestamp(final Date transactionTimestamp) { + this.transactionTimestamp = transactionTimestamp; + return this; + } + + public Builder withPaymentMethod(final PaymentMethod paymentMethod) { + this.paymentMethod = paymentMethod; + return this; + } + + public Builder withPaymentMethodDetail(final String paymentMethodDetail) { + this.paymentMethodDetail = paymentMethodDetail; + return this; + } + + public Builder withStatus(final PaymentStatus status) { + this.status = status; + return this; + } + + public Builder withGrossAmount(final double grossAmount) { + this.grossAmount = grossAmount; + return this; + } + + public PaymentInformation build() { + return new PaymentInformation(transactionUrl, transactionId, transactionReference, transactionTimestamp, paymentMethod, paymentMethodDetail, status, grossAmount); + } + } +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/payment/PaymentMethod.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/payment/PaymentMethod.java new file mode 100644 index 0000000000000000000000000000000000000000..197add8c4d15a3e26769292bf77e44dc58457435 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/payment/PaymentMethod.java @@ -0,0 +1,6 @@ +package dev.fitko.fitconnect.api.domain.model.metadata.payment; + +public enum PaymentMethod { + + GIROPAY, PAYDIRECT, CREDITCARD, PAYPAL, INVOICE, OTHER +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/payment/PaymentStatus.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/payment/PaymentStatus.java new file mode 100644 index 0000000000000000000000000000000000000000..3676d41d3214e7018f315a7a92c171a43c9d1820 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/metadata/payment/PaymentStatus.java @@ -0,0 +1,6 @@ +package dev.fitko.fitconnect.api.domain.model.metadata.payment; + +public enum PaymentStatus { + + INITIAL, BOOKED, FAILED, CANCELED +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/DeMail.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/DeMail.java new file mode 100644 index 0000000000000000000000000000000000000000..d8794b81c51a5d56cacc3acd777628131eeba449 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/DeMail.java @@ -0,0 +1,17 @@ +package dev.fitko.fitconnect.api.domain.model.replychannel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class DeMail { + + @JsonProperty("address") + private String address; +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/Elster.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/Elster.java new file mode 100644 index 0000000000000000000000000000000000000000..04234e8e140986c9e0773b1a99b02242644ead16 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/Elster.java @@ -0,0 +1,23 @@ +package dev.fitko.fitconnect.api.domain.model.replychannel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Elster { + + @JsonProperty("accountId") + private String accountId; + + @JsonProperty("lieferTicket") + private String deliveryTicket; + + @JsonProperty("geschaeftszeichen") + private String reference; +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/Email.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/Email.java new file mode 100644 index 0000000000000000000000000000000000000000..2d7c7ffc6355ab225c041a27223f29b407fef383 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/Email.java @@ -0,0 +1,24 @@ +package dev.fitko.fitconnect.api.domain.model.replychannel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Email { + + @JsonProperty("address") + private String address; + + @JsonProperty("usePgp") + private Boolean usePgp; + + @JsonProperty("pgpPublicKey") + private String pgpPublicKey; +} + diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/Fink.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/Fink.java new file mode 100644 index 0000000000000000000000000000000000000000..47b6181fd8ee7f11521c1de2e1aca477da6ad5ea --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/Fink.java @@ -0,0 +1,20 @@ +package dev.fitko.fitconnect.api.domain.model.replychannel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Fink { + + @JsonProperty("finkPostfachRef") + private String finkPostboxRef; + + @JsonProperty("host") + private String host; +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/ReplyChannel.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/ReplyChannel.java new file mode 100644 index 0000000000000000000000000000000000000000..1d667f4627eb3ea4da36516714f878d2f523dd65 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/replychannel/ReplyChannel.java @@ -0,0 +1,47 @@ +package dev.fitko.fitconnect.api.domain.model.replychannel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class ReplyChannel { + + @JsonProperty("deMail") + private DeMail deMail; + + @JsonProperty("elster") + private Elster elster; + + @JsonProperty("eMail") + private Email eMail; + + @JsonProperty("fink") + private Fink fink; + + public static ReplyChannel fromFink(final String finkPostboxRef, final String host) { + return new ReplyChannel(null, null, null, new Fink(finkPostboxRef, host)); + } + + public static ReplyChannel fromEmailWithPgp(final String address, final String pgpPublicKey) { + return new ReplyChannel(null, null, new Email(address, true, pgpPublicKey), null); + } + + public static ReplyChannel fromEmail(final String address) { + return new ReplyChannel(null, null, new Email(address, false, null), null); + } + + public static ReplyChannel fromDeMail(final String address) { + return new ReplyChannel(new DeMail(address), null, null, null); + } + + public static ReplyChannel fromElster(final String accountId, final String deliveryTicket, final String reference) { + return new ReplyChannel(null, new Elster(accountId, deliveryTicket, reference), null, null); + } +} + diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/route/RouteDestination.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/route/RouteDestination.java index 8db7011a36135a10fa6d3d1ce169bc4b2142d67c..b76381f02dc73f75728ff4804fe6b5df32959226 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/model/route/RouteDestination.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/model/route/RouteDestination.java @@ -2,10 +2,10 @@ package dev.fitko.fitconnect.api.domain.model.route; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import dev.fitko.fitconnect.api.domain.model.destination.ReplyChannel; import dev.fitko.fitconnect.api.domain.model.destination.StatusEnum; import dev.fitko.fitconnect.api.domain.model.jwk.ApiJwkSet; import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema; +import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel; import lombok.Data; import java.util.ArrayList; diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/validation/ValidationContext.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/validation/ValidationContext.java index 456555ea39494dd7dc190a1d70d4766f80551565..7f59e8f0217aaca96bc107cb26d976e17357de15 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/validation/ValidationContext.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/validation/ValidationContext.java @@ -39,7 +39,7 @@ public class ValidationContext { } } - public void addResult(final boolean test, final String message) { + public void addErrorIfTestFailed(final boolean test, final String message) { if (!test) { addResult(ValidationResult.error(new ValidationException(message))); } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/domain/validation/ValidationResult.java b/api/src/main/java/dev/fitko/fitconnect/api/domain/validation/ValidationResult.java index 36cd9fd31a656701b2d68210db4eeb1561ba5caa..129780e15ceb875ba1a9538599e2ccd3716bd26a 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/domain/validation/ValidationResult.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/domain/validation/ValidationResult.java @@ -1,39 +1,135 @@ package dev.fitko.fitconnect.api.domain.validation; +import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; + +import java.util.ArrayList; +import java.util.List; + /** * Wrapper for validations including an exception */ public final class ValidationResult { private final boolean isValid; - private Exception error; + private final Exception error; + + private final List<Problem> validationProblems = new ArrayList<>(); private ValidationResult(final boolean isValid) { this.isValid = isValid; + error = null; } - private ValidationResult(final boolean isValid, final Exception exception) { + private ValidationResult(final boolean isValid, final Exception error) { this.isValid = isValid; - this.error = exception; + this.error = error; + } + + private ValidationResult(final Problem problem) { + isValid = false; + error = null; + validationProblems.add(problem); } + private ValidationResult(final List<Problem> problems) { + isValid = false; + error = null; + validationProblems.addAll(problems); + } + + private ValidationResult(final Exception error, final Problem problem) { + isValid = false; + this.error = error; + validationProblems.add(problem); + } + + /** + * Create new valid result. + * + * @return the valid result + */ public static ValidationResult ok() { return new ValidationResult(true); } + /** + * Create new failed result with an exception. + * + * @return the invalid result + */ public static ValidationResult error(final Exception exception) { return new ValidationResult(false, exception); } + /** + * Create new failed result with a {@link Problem}. + * + * @return the invalid result + */ + public static ValidationResult problem(final Problem problem) { + return new ValidationResult(problem); + } + + /** + * Create new failed result with a list of {@link Problem}. + * + * @return the invalid result + */ + public static ValidationResult problems(final List<Problem> problems) { + return new ValidationResult(problems); + } + + /** + * Create new failed result with an Exception and a {@link Problem}. + * + * @return the invalid result + */ + public static ValidationResult withErrorAndProblem(final Exception exception, final Problem problem) { + return new ValidationResult(exception, problem); + } + + /** + * Successful validation without errors. + * + * @return true if valid + */ public boolean isValid() { - return this.isValid; + return isValid; } + /** + * Failed validation with an error. + * + * @return true if an error occurred + */ public boolean hasError() { - return !this.isValid || this.error != null; + return error != null; + } + + /** + * Failed validation with a problem error that gets auto-rejected. + * + * @return true if a problem occurred + */ + public boolean hasProblems() { + return !validationProblems.isEmpty(); + } + + /** + * Gets the problem that was detected during validation. + * + * @return {@link Problem} + */ + public List<Problem> getProblems() { + return validationProblems; } + /** + * Gets the exception that occurred during validation. + * + * @return {@link Exception} + */ public Exception getError() { - return this.error; + return error; } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/exceptions/AuthenticationTagsEmptyException.java b/api/src/main/java/dev/fitko/fitconnect/api/exceptions/AuthenticationTagsEmptyException.java new file mode 100644 index 0000000000000000000000000000000000000000..8b4ad53aa037dc3cde571b77dde849582a952d83 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/exceptions/AuthenticationTagsEmptyException.java @@ -0,0 +1,12 @@ +package dev.fitko.fitconnect.api.exceptions; + +public class AuthenticationTagsEmptyException extends RuntimeException { + + public AuthenticationTagsEmptyException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } + + public AuthenticationTagsEmptyException(final String errorMessage) { + super(errorMessage); + } +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/exceptions/FileHandlingException.java b/api/src/main/java/dev/fitko/fitconnect/api/exceptions/FileHandlingException.java new file mode 100644 index 0000000000000000000000000000000000000000..dfe704d8d1b0e97e652029ba1619fa7294cade56 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/exceptions/FileHandlingException.java @@ -0,0 +1,8 @@ +package dev.fitko.fitconnect.api.exceptions; + +public class FileHandlingException extends RuntimeException { + + public FileHandlingException(Exception exception) { + super(exception); + } +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/exceptions/RestApiException.java b/api/src/main/java/dev/fitko/fitconnect/api/exceptions/RestApiException.java index 6fdfc7cd3567d182f658a81c1d9add446743ca1a..f745958c039fd7d0d1004aec734b5f178bd72fc3 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/exceptions/RestApiException.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/exceptions/RestApiException.java @@ -1,7 +1,13 @@ package dev.fitko.fitconnect.api.exceptions; +import lombok.Getter; +import org.springframework.http.HttpStatus; + public class RestApiException extends RuntimeException { + @Getter + private HttpStatus httpStatus; + public RestApiException(final String errorMessage, final Throwable error) { super(errorMessage, error); } @@ -9,4 +15,9 @@ public class RestApiException extends RuntimeException { public RestApiException(final String errorMessage) { super(errorMessage); } + + public RestApiException(final HttpStatus httpStatus, final String errorMessage) { + super(errorMessage); + this.httpStatus = httpStatus; + } } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/exceptions/RootCertificateException.java b/api/src/main/java/dev/fitko/fitconnect/api/exceptions/RootCertificateException.java new file mode 100644 index 0000000000000000000000000000000000000000..e7a577d2b9cd0546c627695a3732bad16398cb2c --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/exceptions/RootCertificateException.java @@ -0,0 +1,8 @@ +package dev.fitko.fitconnect.api.exceptions; + +public class RootCertificateException extends RuntimeException { + + public RootCertificateException(Exception exception) { + super(exception); + } +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/exceptions/SubmissionRequestException.java b/api/src/main/java/dev/fitko/fitconnect/api/exceptions/SubmissionRequestException.java new file mode 100644 index 0000000000000000000000000000000000000000..708c3355cbee2f243e09dd4f6771d37d8f48fe83 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/exceptions/SubmissionRequestException.java @@ -0,0 +1,12 @@ +package dev.fitko.fitconnect.api.exceptions; + +public class SubmissionRequestException extends RuntimeException { + + public SubmissionRequestException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } + + public SubmissionRequestException(final String errorMessage) { + super(errorMessage); + } +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/exceptions/SubmitEventNotFoundException.java b/api/src/main/java/dev/fitko/fitconnect/api/exceptions/SubmitEventNotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..5075c4886298d75067ea9de71fcef7348c1a89c0 --- /dev/null +++ b/api/src/main/java/dev/fitko/fitconnect/api/exceptions/SubmitEventNotFoundException.java @@ -0,0 +1,12 @@ +package dev.fitko.fitconnect.api.exceptions; + +public class SubmitEventNotFoundException extends RuntimeException { + + public SubmitEventNotFoundException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } + + public SubmitEventNotFoundException(final String errorMessage) { + super(errorMessage); + } +} diff --git a/api/src/main/java/dev/fitko/fitconnect/api/services/Sender.java b/api/src/main/java/dev/fitko/fitconnect/api/services/Sender.java index 1078da6fa66a0a20d17b8cf2bc76fd07999fbb96..d7ce15ff790f1de2d350bfa0132a7239ce5559e0 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/services/Sender.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/services/Sender.java @@ -3,10 +3,10 @@ package dev.fitko.fitconnect.api.services; import com.nimbusds.jose.jwk.RSAKey; import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; -import dev.fitko.fitconnect.api.domain.model.event.EventStatus; +import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; -import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.submission.CreateSubmission; import dev.fitko.fitconnect.api.domain.model.submission.Submission; @@ -22,27 +22,12 @@ import java.util.UUID; * A technical system that creates a submission via the FIT-Connect Submission API. * <p> * The Sender acts as a common interface wrapping all client functionality for authenticating <p> - * and creating a valid {@link SubmitSubmission} including encrypted {@link Data} and {@link Attachment}s + * and creating a valid {@link SubmitSubmission} including encrypted {@link Data} and {@link ApiAttachment}s * * @see <a href="https://docs.fitko.de/fit-connect/docs/sending/overview">Sending Submissions</a> */ public interface Sender { - /** - * Validates the public key consisting of the following steps: - * <p> - * <ul> - * <li>checks if the JSON Web Key is suitable for the encryption</li> - * <li>checks if the public key is matching the certificate referenced in the JWK</li> - * <li>checks the certificate chain up to the root certificate</li> - * <li>checks against a certificate revocation list and/or an OSCP-endpoint with signed response</li> - * </ul> - * @param publicKey the public JWK - * - * @return {@link ValidationResult} that includes an error if the validation failed - */ - ValidationResult validatePublicKey(RSAKey publicKey); - /** * Validates the {@link Metadata} structure against a given JSON-schema to ensure its correctness. * @@ -102,7 +87,7 @@ public interface Sender { SubmissionForPickup createSubmission(CreateSubmission submission) throws SubmissionNotCreatedException; /** - * Uploads encrypted {@link Attachment}s to for given submission id. + * Uploads encrypted {@link ApiAttachment}s to for given submission id. * * @param submissionId unique identifier of the submission * @param attachmentId unique identifier of the attachment @@ -125,7 +110,7 @@ public interface Sender { * * @param destinationId unique identifier of the destination * - * @return the public key for encrypting {@link Metadata}, {@link Data} and {@link Attachment}s + * @return the public key for encrypting {@link Metadata}, {@link Data} and {@link ApiAttachment}s * * @throws KeyNotRetrievedException if a technical problem occurred fetching the key */ @@ -174,7 +159,7 @@ public interface Sender { * @param submissionId unique identifier of the submission the log should be retrieved for * @param authenticationTags {@link AuthenticationTags} used for SET-Event integrity validation * - * @return {@link EventStatus} the current status + * @return {@link SubmissionStatus} the current status */ - EventStatus getLastedEvent(UUID destinationId, UUID caseId, UUID submissionId, AuthenticationTags authenticationTags) throws RestApiException; + SubmissionStatus getLastedEvent(UUID destinationId, UUID caseId, UUID submissionId, AuthenticationTags authenticationTags) throws RestApiException; } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/services/Subscriber.java b/api/src/main/java/dev/fitko/fitconnect/api/services/Subscriber.java index ebe4ee0a3004c078516138b38189847b38d173ad..946fed1a5daaaf43de0c92f98b37fb90c323e716 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/services/Subscriber.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/services/Subscriber.java @@ -2,14 +2,19 @@ package dev.fitko.fitconnect.api.services; import com.nimbusds.jose.jwk.RSAKey; import dev.fitko.fitconnect.api.domain.model.destination.Destination; +import dev.fitko.fitconnect.api.domain.model.event.Event; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; import dev.fitko.fitconnect.api.domain.model.event.EventPayload; +import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; -import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentForValidation; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; +import dev.fitko.fitconnect.api.exceptions.DecryptionException; +import dev.fitko.fitconnect.api.exceptions.RestApiException; import java.util.List; import java.util.Set; @@ -29,7 +34,7 @@ public interface Subscriber { * @param encryptedContent JWE encrypted content that should be decrypted * @return the decrypted content as byte[] */ - byte[] decryptStringContent(RSAKey privateKey, String encryptedContent); + byte[] decryptStringContent(RSAKey privateKey, String encryptedContent) throws DecryptionException; /** * Polls available {@link SubmissionForPickup}s for a given destinationId. @@ -39,7 +44,16 @@ public interface Subscriber { * @param offset position in the dataset * @return list of found {@link SubmissionForPickup}s */ - Set<SubmissionForPickup> pollAvailableSubmissions(UUID destinationId, int offset, int limit); + Set<SubmissionForPickup> pollAvailableSubmissionsForDestination(UUID destinationId, int offset, int limit) throws RestApiException; + + /** + * Polls available {@link SubmissionForPickup}s for all destinations the {@link Subscriber} is assigned to. + * + * @param limit number of submissions in result (max. is 500) + * @param offset position in the dataset + * @return list of found {@link SubmissionForPickup}s + */ + Set<SubmissionForPickup> pollAvailableSubmissions(int offset, int limit); /** * Gets a {@link Submission}. @@ -47,40 +61,62 @@ public interface Subscriber { * @param submissionId the unique identifier of a {@link Submission} * @return the requested {@link Submission} */ - Submission getSubmission(UUID submissionId); + Submission getSubmission(UUID submissionId) throws RestApiException; /** - * Loads encrypted {@link Attachment} for a {@link Submission}. + * Loads encrypted {@link ApiAttachment} for a {@link Submission}. * * @param submissionId the unique identifier of a {@link Submission} * @param attachmentId unique identifier of the attachments * @return encrypted JWE string of attachment */ - String fetchAttachment(UUID submissionId, UUID attachmentId); + String fetchAttachment(UUID submissionId, UUID attachmentId) throws RestApiException; /** * Retrieve the entire event log for a submissions caseId of a specific destination. * - * @param caseId unique identifier of the case the log should be retrieved for + * @param caseId unique identifier of the case the log should be retrieved for * @param destinationId unique identifier of the {@link Destination} the log should be retrieved for - * * @return List of {@link EventLogEntry}s for the given case */ - List<EventLogEntry> getEventLog(UUID caseId, UUID destinationId); + List<EventLogEntry> getEventLog(UUID caseId, UUID destinationId) throws RestApiException; /** - * Validates the {@link Metadata} structure against a given JSON-schema to ensure its correctness. + * Validates the {@link Metadata} structure and contents 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 + * @param submission the {@link Submission} of the validated metadata + * @param authenticationTags the {@link AuthenticationTags} of the validated metadata + * @return a {@link ValidationResult}, contains an error if the {@link Metadata} is invalid, e.g. doesn't match the schema + */ + ValidationResult validateMetadata(Metadata metadata, Submission submission, AuthenticationTags authenticationTags); + + /** + * Validates the attachment structure and contents to ensure its correctness. + * + * @param attachmentsForValidation list of attachments containing the hash, decrypted and encrypted data needed for validation + * @param authenticationTags the {@link AuthenticationTags} of the validated attachments + + * @return a {@link ValidationResult}, contains an error if the attachment is invalid */ - ValidationResult validateMetadata(Metadata metadata); + ValidationResult validateAttachments(List<AttachmentForValidation> attachmentsForValidation, AuthenticationTags authenticationTags); /** - * Validates data integrity of {@link Attachment} or {@link Data}. + * Validates the {@link Data} structure and contents to ensure its correctness. + * + * @param data the unencrypted data as byte[] + * @param submission the {@link Submission} of the validated data + * @param metadata the {@link Metadata} of the validated data + * @param authenticationTags the {@link AuthenticationTags} of the validated data + * @return a {@link ValidationResult}, contains an error if the {@link Metadata} is invalid, e.g. doesn't match the schema + */ + ValidationResult validateData(byte[] data, Submission submission, Metadata metadata, AuthenticationTags authenticationTags); + + /** + * Validates data integrity of {@link ApiAttachment} or {@link Data}. * * @param originalHash original hash value of raw data as hex string - * @param data unencrypted data as byte[] + * @param data unencrypted data as byte[] * @return a {@link ValidationResult}, contains an error if the hash values are not equal */ ValidationResult validateHashIntegrity(String originalHash, byte[] data); @@ -88,9 +124,9 @@ public interface Subscriber { /** * Checks if a received callback can be trusted by validating the provided request data * - * @param hmac authentication code provided by the callback - * @param timestamp timestamp provided by the callback - * @param httpBody HTTP body provided by the callback + * @param hmac authentication code provided by the callback + * @param timestamp timestamp provided by the callback + * @param httpBody HTTP body provided by the callback * @param callbackSecret secret owned by the client, which is used to calculate the hmac * @return {@code true} if hmac and timestamp provided by the callback meet the required conditions */ @@ -100,7 +136,6 @@ public interface Subscriber { * Sends a confirmation event if the received submission matches all validations. * * @param eventPayload contains submissionId, caseId, destination and a list of problems - * @see <a href="https://docs.fitko.de/fit-connect/docs/receiving/process-and-acknowledge">Process And Acknowledge</a> */ void acceptSubmission(EventPayload eventPayload); @@ -109,8 +144,16 @@ public interface Subscriber { * Sends a rejection event if the received submission violates any validation rule. * * @param eventPayload contains submissionId, caseId, destination and a list of problems - * @see <a href="https://docs.fitko.de/fit-connect/docs/receiving/process-and-acknowledge">Process And Acknowledge</a> */ void rejectSubmission(EventPayload eventPayload); + + /** + * Get event from event-log filtered by a given event and submission. + * + * @param event event type to filter the event-log by + * @param submission submission to filter the event-log by + * @return {@link AuthenticationTags} of metadata, data and attachments + */ + AuthenticationTags getAuthenticationTagsForEvent(Event event, Submission submission); } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/services/crypto/CryptoService.java b/api/src/main/java/dev/fitko/fitconnect/api/services/crypto/CryptoService.java index 05c7476386a18016bb8d0e45a3846f0b84da6613..fb6d70e73f17a1ea0b7959d297a3e5f9ff675124 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/services/crypto/CryptoService.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/services/crypto/CryptoService.java @@ -1,31 +1,20 @@ package dev.fitko.fitconnect.api.services.crypto; import com.nimbusds.jose.jwk.RSAKey; -import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.submission.SubmitSubmission; import dev.fitko.fitconnect.api.exceptions.DecryptionException; import dev.fitko.fitconnect.api.exceptions.EncryptionException; /** - * A service that allows to encrypt and decrypt {@link Data} and {@link Attachment}s of a {@link SubmitSubmission} + * A service that allows to encrypt and decrypt {@link Data} and {@link ApiAttachment}s of a {@link SubmitSubmission} * via JSON-Web-Encryption. * * @see <a href="https://datatracker.ietf.org/doc/html/rfc7516">JSON-Web-Encryption</a> */ public interface CryptoService { - /** - * Decrypts a JWE encrypted string with the given private key. - * - * @param privateKey RSA private key for decryption of JWE - * @param encryptedData serialized encrypted JWE Hex string - * @return decrypted JWE string - * - * @throws DecryptionException if the payload cannot be decrypted or there was an issue with the key - */ - String decryptString(RSAKey privateKey, String encryptedData) throws DecryptionException; - /** * Decrypts a JWE encrypted string with the given private key. * @@ -35,18 +24,7 @@ public interface CryptoService { * * @throws DecryptionException if the payload cannot be decrypted or there was an issue with the key */ - byte[] decryptBytes(RSAKey privateKey, String encryptedData) throws DecryptionException; - - /** - * Encrypts a string with the given public key. - * - * @param encryptionKey RSA public key for encryption of string payload - * @param data string data that should be encrypted - * @return Hex string of the encrypted JWE object - * - * @throws EncryptionException if the payload cannot be encrypted or there was an issue with the key - */ - String encryptString(RSAKey encryptionKey, String data) throws EncryptionException; + byte[] decryptToBytes(RSAKey privateKey, String encryptedData) throws DecryptionException; /** * Encrypts an object with the given public key. diff --git a/api/src/main/java/dev/fitko/fitconnect/api/services/events/EventLogService.java b/api/src/main/java/dev/fitko/fitconnect/api/services/events/EventLogService.java index 25ce7c967f0a090f8bf8789c42c0c0148578cb98..095dfa3982a1c4d1b46a1a10573f3b4afc8a8e57 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/services/events/EventLogService.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/services/events/EventLogService.java @@ -1,13 +1,13 @@ package dev.fitko.fitconnect.api.services.events; import dev.fitko.fitconnect.api.domain.model.destination.Destination; +import dev.fitko.fitconnect.api.domain.model.event.Event; import dev.fitko.fitconnect.api.domain.model.event.EventLog; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; -import dev.fitko.fitconnect.api.domain.model.event.EventStatus; +import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.exceptions.EventLogException; -import dev.fitko.fitconnect.api.exceptions.RestApiException; import java.util.List; import java.util.UUID; @@ -27,8 +27,9 @@ public interface EventLogService { * @param caseId unique case identifier * @param destinationId unique identifier of the destination * @return list of {@link EventLogEntry} + * @throws EventLogException if a technical error occurred or the validation failed */ - List<EventLogEntry> getEventLog(UUID caseId, UUID destinationId) throws RestApiException, EventLogException; + List<EventLogEntry> getEventLog(UUID caseId, UUID destinationId) throws EventLogException; /** * Retrieve the current status of a {@link Submission}. @@ -37,16 +38,27 @@ public interface EventLogService { * @param caseId unique identifier of the case the log should be retrieved for * @param submissionId unique identifier of the submission the log should be retrieved for * @param authenticationTags {@link AuthenticationTags} used for SET-Event integrity validation - * - * @return {@link EventStatus} the current status + * @return {@link SubmissionStatus} the current status + * @throws EventLogException if a technical error occurred or the validation failed */ - EventStatus getLastedEvent(UUID destinationId, UUID caseId, UUID submissionId, AuthenticationTags authenticationTags) throws RestApiException, EventLogException; + SubmissionStatus getLastedEvent(UUID destinationId, UUID caseId, UUID submissionId, AuthenticationTags authenticationTags) throws EventLogException; /** * Send an event for a given caseId. * * @param caseId unique case identifier * @param signedAndSerializedSET the serialised and signed event as SET string + * @throws EventLogException if a technical error occurred + */ + void sendEvent(UUID caseId, String signedAndSerializedSET) throws EventLogException; + + /** + * Get authentication tags for a given event and caseId. + * + * @param event the event type to filter the log for + * @param submission submission data + * @return {@link AuthenticationTags} for metadata, data and attachments + * @throws EventLogException if a technical error occurred or the validation failed */ - void sendEvent(UUID caseId, String signedAndSerializedSET) throws RestApiException; + AuthenticationTags getAuthenticationTagsForEvent(Event event, Submission submission) throws EventLogException; } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/services/keys/KeyService.java b/api/src/main/java/dev/fitko/fitconnect/api/services/keys/KeyService.java index 040dc4166d8c5b001ff46a63f5ac566f432e11c6..e627b3cefa2fa1fe9e3131672e017ea7d653c3af 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/services/keys/KeyService.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/services/keys/KeyService.java @@ -1,5 +1,6 @@ package dev.fitko.fitconnect.api.services.keys; +import com.nimbusds.jose.jwk.KeyOperation; import com.nimbusds.jose.jwk.RSAKey; import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.services.validation.ValidationService; @@ -19,7 +20,7 @@ public interface KeyService { * * @param destinationId unique identifier of the {@link Destination} * - * @return validated {@link RSAKey} (@see {@link ValidationService#validateEncryptionPublicKey(RSAKey)}) + * @return validated {@link RSAKey} (@see {@link ValidationService#validatePublicKey(RSAKey, KeyOperation)}}) */ RSAKey getPublicEncryptionKey(UUID destinationId); @@ -28,7 +29,7 @@ public interface KeyService { * * @param destinationId unique identifier of the {@link Destination} * @param keyId unique identifier of the {@link RSAKey} - * @return validated {@link RSAKey} (@see {@link ValidationService#validateEncryptionPublicKey(RSAKey)}) + * @return validated {@link RSAKey} (@see {@link ValidationService#validatePublicKey(RSAKey, KeyOperation)}}) */ RSAKey getPublicSignatureKey(UUID destinationId, String keyId); @@ -36,7 +37,7 @@ public interface KeyService { * Get a public signature key for a given key-id from the self-service portal well-known keys. * * @param keyId unique identifier of the {@link RSAKey} - * @return validated {@link RSAKey} (@see {@link ValidationService#validateEncryptionPublicKey(RSAKey)}) + * @return validated {@link RSAKey} (@see {@link ValidationService#validatePublicKey(RSAKey, KeyOperation)}}) */ RSAKey getPortalPublicKey(String keyId); @@ -44,7 +45,7 @@ public interface KeyService { * Get a public signature key for a given key-id from the submission service well-known keys. * * @param keyId unique identifier of the {@link RSAKey} - * @return validated {@link RSAKey} (@see {@link ValidationService#validateEncryptionPublicKey(RSAKey)}) + * @return validated {@link RSAKey} (@see {@link ValidationService#validatePublicKey(RSAKey, KeyOperation)}}) */ RSAKey getSubmissionServicePublicKey(String keyId); @@ -54,7 +55,7 @@ public interface KeyService { * * @param url custom url to load the well known keys from * @param keyId unique identifier of the {@link RSAKey} the well known keys are filtered by - * @return validated {@link RSAKey} (@see {@link ValidationService#validateEncryptionPublicKey(RSAKey)}) + * @return validated {@link RSAKey} (@see {@link ValidationService#validatePublicKey(RSAKey, KeyOperation)}}) */ RSAKey getWellKnownKeysForSubmissionUrl(String url, String keyId); } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/services/submission/SubmissionService.java b/api/src/main/java/dev/fitko/fitconnect/api/services/submission/SubmissionService.java index fe6e1857be85406e8a25fd415f93af31a688a670..5f49f41da3c6b6bff5c30ef75f3bf1f5e3c1308e 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/services/submission/SubmissionService.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/services/submission/SubmissionService.java @@ -2,7 +2,7 @@ package dev.fitko.fitconnect.api.services.submission; import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; -import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.submission.CreateSubmission; import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; @@ -16,7 +16,7 @@ import java.util.UUID; /** * A service that provides access to the FIT-Connect REST-API and allows creating and loading {@link Submission}s - * and {@link Attachment}s. + * and {@link ApiAttachment}s. * * @see <a href="https://docs.fitko.de/fit-connect/docs/apis/submission-api">FIT-Connect Submission API</a> */ @@ -25,12 +25,12 @@ public interface SubmissionService { /** * Announce a new submission. This step is necessary before actually {@link #sendSubmission(SubmitSubmission) sending} the submission. * - * @param submission submission that contains all {@link Attachment}s that should be announced, as well as a specified {@link ServiceType} + * @param submission submission that contains all {@link ApiAttachment}s that should be announced, as well as a specified {@link ServiceType} * @return announced submission with submissionId, caseId and destinationID * * @see <a href="https://docs.fitko.de/fit-connect/docs/sending/start-submission">Announcing a submission</a> */ - SubmissionForPickup announceSubmission(CreateSubmission submission); + SubmissionForPickup announceSubmission(CreateSubmission submission) throws RestApiException; /** * Send a submission that was already {@link #announceSubmission(CreateSubmission) announced} before. @@ -38,7 +38,7 @@ public interface SubmissionService { * @param submission submission including the encrypted {@link Data} and {@link Metadata} * @return the submission that was sent */ - Submission sendSubmission(SubmitSubmission submission); + Submission sendSubmission(SubmitSubmission submission) throws RestApiException; /** * Get a {@link Submission} by id. @@ -46,7 +46,16 @@ public interface SubmissionService { * @param submissionId unique submission identifier * @return submission matching the given id */ - Submission getSubmission(UUID submissionId); + Submission getSubmission(UUID submissionId) throws RestApiException; + + /** + * Get all available submissions. + * + * @param offset position in the dataset + * @param limit number of submissions in result (max. is 500) + * @return SubmissionsForPickup containing a list of submissions + */ + SubmissionsForPickup pollAvailableSubmissions(int offset, int limit); /** * Get all available submissions for a given {@link Destination}. @@ -56,25 +65,25 @@ public interface SubmissionService { * @param limit number of submissions in result (max. is 500) * @return SubmissionsForPickup containing a list of submissions */ - SubmissionsForPickup pollAvailableSubmissions(UUID destinationId, int offset, int limit); + SubmissionsForPickup pollAvailableSubmissionsForDestination(UUID destinationId, int offset, int limit) throws RestApiException; /** - * Upload an encrypted {@link Attachment}. + * Upload an encrypted {@link ApiAttachment}. * * @param submissionId unique identifier of an announced submission * @param attachmentId unique destination identifier * @param encryptedAttachment JWE encrypted attachment payload */ - void uploadAttachment(UUID submissionId, UUID attachmentId, String encryptedAttachment); + void uploadAttachment(UUID submissionId, UUID attachmentId, String encryptedAttachment) throws RestApiException; /** - * Get an {@link Attachment} by id for a given {@link Submission}. + * Get an {@link ApiAttachment} by id for a given {@link Submission}. * * @param submissionId unique submission identifier * @param attachmentId unique attachment identifier * @return encrypted string of the attachment data */ - String getAttachment(UUID submissionId, UUID attachmentId); + String getAttachment(UUID submissionId, UUID attachmentId) throws RestApiException; /** * Get the submissions {@link Destination} by id. @@ -84,5 +93,5 @@ public interface SubmissionService { * * @throws RestApiException if an error occurred */ - Destination getDestination(UUID destinationID); + Destination getDestination(UUID destinationID) throws RestApiException; } diff --git a/api/src/main/java/dev/fitko/fitconnect/api/services/validation/ValidationService.java b/api/src/main/java/dev/fitko/fitconnect/api/services/validation/ValidationService.java index c5d747b3f9e54e225c6f9ef0d55aad86628c4f4d..8941384a1f953f6fdd0d2cf2c1b8fe0f356278e8 100644 --- a/api/src/main/java/dev/fitko/fitconnect/api/services/validation/ValidationService.java +++ b/api/src/main/java/dev/fitko/fitconnect/api/services/validation/ValidationService.java @@ -1,9 +1,16 @@ package dev.fitko.fitconnect.api.services.validation; +import com.nimbusds.jose.jwk.KeyOperation; import com.nimbusds.jose.jwk.RSAKey; +import dev.fitko.fitconnect.api.domain.model.destination.Destination; +import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentForValidation; +import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; +import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; +import java.util.List; import java.util.Map; /** @@ -14,28 +21,14 @@ import java.util.Map; public interface ValidationService { /** - * Validates the public key consisting of the following steps: - * <p> - * <ul> - * <li>checks if the JSON Web Key has key operation `wrap_key`</li> - * <li>checks if the JSON Web Key is suitable for the encryption</li> - * <li>checks if the public key is matching the certificate referenced in the JWK</li> - * <li>checks the certificate chain up to the root certificate</li> - * <li>checks against a certificate revocation list and/or an OSCP-endpoint with signed response</li> - * </ul> - * @param publicKey the public JWK + * Validates the public key for integrity. * - * @return {@link ValidationResult} that includes an error if the validation failed - */ - ValidationResult validateEncryptionPublicKey(RSAKey publicKey); - - /** - * Validates the public signature key with key-operation `verify` + * @param publicKey the public JWK + * @param keyOperation key operation the public key be validated with, represents {@code key_ops} parameter in a JWK * - * @param signatureKey the public signature JWK * @return {@link ValidationResult} that includes an error if the validation failed */ - ValidationResult validateSignaturePublicKey(RSAKey signatureKey); + ValidationResult validatePublicKey(final RSAKey publicKey, KeyOperation keyOperation); /** * Validates the metadata against a given schema. @@ -46,6 +39,38 @@ public interface ValidationService { */ ValidationResult validateMetadataSchema(Metadata metadata); + /** + * Validates the {@link Metadata} structure and contents to ensure its correctness. + * + * @param metadata the {@link Metadata} object that is validated + * @param submission the {@link Submission} of the validated metadata + * @param destination the {@link Destination} of the validated metadata + * @param authenticationTags the {@link AuthenticationTags} of the validated metadata + * @return a {@link ValidationResult}, contains an error if the {@link Metadata} is invalid, e.g. doesn't match the schema + */ + ValidationResult validateMetadata(Metadata metadata, Submission submission, Destination destination, AuthenticationTags authenticationTags); + + /** + * Validates the attachment structure and contents to ensure its correctness. + * + * @param attachmentsForValidation list of attachments containing the hash, decrypted and encrypted data needed for validation + * @param authenticationTags the {@link AuthenticationTags} of the validated attachments + + * @return a {@link ValidationResult}, contains an error if the attachment is invalid + */ + ValidationResult validateAttachments(List<AttachmentForValidation> attachmentsForValidation, final AuthenticationTags authenticationTags); + + /** + * Validates the {@link Data} structure and contents to ensure its correctness. + * + * @param decryptedData the unencrypted data as byte[] + * @param submission the {@link Submission} of the validated data + * @param metadata the {@link Metadata} of the validated data + * @param authenticationTags the {@link AuthenticationTags} of the validated data + * @return a {@link ValidationResult}, contains an error if the {@link Metadata} is invalid, e.g. doesn't match the schema + */ + ValidationResult validateData(byte[] decryptedData, Submission submission, Metadata metadata, AuthenticationTags authenticationTags); + /** * Validates a set event against a given schema. * @@ -101,4 +126,5 @@ public interface ValidationService { * @return {@code true} if hmac and timestamp provided by the callback meet the required conditions */ ValidationResult validateCallback(String hmac, Long timestamp, String httpBody, String callbackSecret); + } diff --git a/client/pom.xml b/client/pom.xml index ff3d13a833016a5a1fe1c361c4e4aa46eb66bf92..ae7ddcc8f7ca0cb8ebd2ab71993c8674465d47aa 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -63,11 +63,6 @@ <artifactId>hamcrest-all</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>org.awaitility</groupId> - <artifactId>awaitility</artifactId> - <scope>test</scope> - </dependency> </dependencies> <build> @@ -129,6 +124,10 @@ <commitIdGenerationMode>full</commitIdGenerationMode> </configuration> </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + </plugin> </plugins> </build> diff --git a/client/src/main/java/dev/fitko/fitconnect/client/RoutingClient.java b/client/src/main/java/dev/fitko/fitconnect/client/RoutingClient.java index f91e6e2956ab541239f57752b93247855fcb27f1..c4438300daff840e8dcc9090eb3688004a22651b 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/RoutingClient.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/RoutingClient.java @@ -30,7 +30,6 @@ public final class RoutingClient { * @throws RoutingException if the signature validation failed * @throws RestApiException if a technical error occurred during the query */ - public List<Route> findDestinations(final DestinationSearch search) throws RoutingException, RestApiException { final RouteResult routeResult = routingService.getRoutes(search.getLeikaKey(), search.getArs(), search.getAgs(), search.getAreaId(), search.getOffset(), search.getLimit()); final ValidationResult result = routeVerifier.validateRouteDestinations(routeResult.getRoutes(), search.getLeikaKey(), search.getArs()); diff --git a/client/src/main/java/dev/fitko/fitconnect/client/SenderClient.java b/client/src/main/java/dev/fitko/fitconnect/client/SenderClient.java index 523193e4379bd075df2c4a7e5dd5a517df80fe79..dd00d276ec1e7e2e1a88de1e99936e39e9d1e7cd 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/SenderClient.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/SenderClient.java @@ -1,23 +1,23 @@ package dev.fitko.fitconnect.client; -import com.nimbusds.jose.jwk.RSAKey; import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; -import dev.fitko.fitconnect.api.domain.model.event.EventStatus; +import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.submission.SentSubmission; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.SubmitSubmission; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; +import dev.fitko.fitconnect.api.exceptions.InvalidKeyException; +import dev.fitko.fitconnect.api.exceptions.KeyNotRetrievedException; import dev.fitko.fitconnect.api.services.Sender; -import dev.fitko.fitconnect.client.sender.model.EncryptedSubmissionPayload; -import dev.fitko.fitconnect.client.sender.model.SubmissionPayload; +import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission; +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; import dev.fitko.fitconnect.client.sender.strategies.SendEncryptedSubmissionStrategy; import dev.fitko.fitconnect.client.sender.strategies.SendNewSubmissionStrategy; import dev.fitko.fitconnect.client.util.ValidDataGuard; import java.util.List; -import java.util.Optional; import java.util.UUID; /** @@ -35,14 +35,15 @@ public class SenderClient { } /** - * Retrieve the public encryption key + * Retrieve the public encryption key for a given destination. * * @param destinationId unique identifier of a {@link Destination} * @return optional string containing the public JWK + * @throws KeyNotRetrievedException is there was a technical problem retrieving the key + * @throws InvalidKeyException if the key validation failed */ - public Optional<String> getPublicKey(final UUID destinationId) { - final Optional<RSAKey> key = Optional.ofNullable(sender.getEncryptionKeyForDestination(destinationId)); - return key.isEmpty() ? Optional.empty() : Optional.of(key.get().toJSONString()); + public String getPublicKeyForDestination(final UUID destinationId) throws KeyNotRetrievedException, InvalidKeyException { + return sender.getEncryptionKeyForDestination(destinationId).toJSONString(); } /** @@ -60,9 +61,9 @@ public class SenderClient { * Retrieve the current status of a {@link Submission}. * * @param sentSubmission the original {@link SentSubmission} the status should be retrieved for - * @return {@link EventStatus} the current status + * @return {@link SubmissionStatus} the current status */ - public EventStatus getStatusForSubmission(final SentSubmission sentSubmission) { + public SubmissionStatus getStatusForSubmission(final SentSubmission sentSubmission) { final UUID destinationId = sentSubmission.getDestinationId(); final UUID caseId = sentSubmission.getCaseId(); final UUID submissionId = sentSubmission.getSubmissionId(); @@ -88,9 +89,9 @@ public class SenderClient { * * @return {@link SentSubmission} object of the handed in submission */ - public SentSubmission submit(final SubmissionPayload submissionPayload) { - dataGuard.ensureValidDataPayload(submissionPayload); - return new SendNewSubmissionStrategy(sender).send(submissionPayload); + public SentSubmission send(final SendableSubmission sendableSubmission) { + dataGuard.ensureValidDataPayload(sendableSubmission); + return new SendNewSubmissionStrategy(sender).send(sendableSubmission); } /** @@ -98,8 +99,8 @@ public class SenderClient { * * @return {@link SentSubmission} object of the handed in submission */ - public SentSubmission submit(final EncryptedSubmissionPayload encryptedSubmissionPayload) { - dataGuard.ensureValidDataPayload(encryptedSubmissionPayload); - return new SendEncryptedSubmissionStrategy(sender).send(encryptedSubmissionPayload); + public SentSubmission send(final SendableEncryptedSubmission sendableEncryptedSubmission) { + dataGuard.ensureValidDataPayload(sendableEncryptedSubmission); + return new SendEncryptedSubmissionStrategy(sender).send(sendableEncryptedSubmission); } } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/SubscriberClient.java b/client/src/main/java/dev/fitko/fitconnect/client/SubscriberClient.java index 4c75814f737c024c901915f335fd93289a7d6b27..dadba3618a93729b84f820d870f0b06ca353e384 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/SubscriberClient.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/SubscriberClient.java @@ -1,37 +1,33 @@ package dev.fitko.fitconnect.client; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.nimbusds.jose.jwk.RSAKey; import dev.fitko.fitconnect.api.domain.model.destination.Destination; +import dev.fitko.fitconnect.api.domain.model.event.Event; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; +import dev.fitko.fitconnect.api.domain.model.event.EventPayload; +import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; -import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; +import dev.fitko.fitconnect.api.domain.model.submission.SentSubmission; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; -import dev.fitko.fitconnect.api.exceptions.DataIntegrityException; -import dev.fitko.fitconnect.api.exceptions.DecryptionException; -import dev.fitko.fitconnect.api.exceptions.EventCreationException; -import dev.fitko.fitconnect.api.exceptions.RestApiException; +import dev.fitko.fitconnect.api.exceptions.SubmissionRequestException; import dev.fitko.fitconnect.api.services.Subscriber; +import dev.fitko.fitconnect.client.sender.model.Attachment; import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission; import dev.fitko.fitconnect.client.subscriber.model.DecryptedAttachmentPayload; -import dev.fitko.fitconnect.client.subscriber.model.ReceivedAttachment; import dev.fitko.fitconnect.client.subscriber.model.ReceivedData; import dev.fitko.fitconnect.core.util.StopWatch; +import dev.fitko.fitconnect.client.subscriber.SubmissionReceiver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.function.Function; import java.util.stream.Collectors; import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; @@ -42,36 +38,37 @@ import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKN public class SubscriberClient { private static final Logger LOGGER = LoggerFactory.getLogger(SubscriberClient.class); - private static final ObjectMapper MAPPER = new ObjectMapper().configure(FAIL_ON_UNKNOWN_PROPERTIES, false); + private static final int DEFAULT_SUBMISSION_LIMIT = 500; + private final Subscriber subscriber; - private final RSAKey privateKey; + private final SubmissionReceiver submissionReceiver; - public SubscriberClient(final Subscriber subscriber, final RSAKey privateKey) { + public SubscriberClient(final Subscriber subscriber, final SubmissionReceiver submissionReceiver) { this.subscriber = subscriber; - this.privateKey = privateKey; + this.submissionReceiver = submissionReceiver; } /** - * Loads a list of available {@link SubmissionForPickup} that were submitted to the {@link Subscriber}. + * Loads a list of available {@link SubmissionForPickup} that were submitted to the {@link Subscriber}s destination. * - * @param destinationId unique identifier for a destination + * @param destinationId filter criterion for the submissions * @return set of the first 0..500 available submissions */ - public Set<SubmissionForPickup> getAvailableSubmissions(final UUID destinationId) { - return getAvailableSubmissions(destinationId, 0, DEFAULT_SUBMISSION_LIMIT); + public Set<SubmissionForPickup> getAvailableSubmissionsForDestination(final UUID destinationId) { + return getAvailableSubmissionsForDestination(destinationId, 0, DEFAULT_SUBMISSION_LIMIT); } /** - * Loads a list of available {@link SubmissionForPickup} that were submitted to the {@link Subscriber}. + * Loads a list of available {@link SubmissionForPickup} that were submitted to the {@link Subscriber}s destination. * - * @param destinationId unique identifier for a destination + * @param destinationId filter criterion for the submissions * @param offset position in the dataset * @param limit number of submissions in result (max. is 500) * @return set of available submissions in the given range */ - public Set<SubmissionForPickup> getAvailableSubmissions(final UUID destinationId, final int offset, final int limit) { - final Set<SubmissionForPickup> submissions = subscriber.pollAvailableSubmissions(destinationId, offset, limit); + public Set<SubmissionForPickup> getAvailableSubmissionsForDestination(final UUID destinationId, final int offset, final int limit) { + final Set<SubmissionForPickup> submissions = subscriber.pollAvailableSubmissionsForDestination(destinationId, offset, limit); LOGGER.info("Received {} submission(s) for destination {}", submissions.size(), destinationId); return submissions; } @@ -90,140 +87,46 @@ public class SubscriberClient { /** * Loads a single {@link SubmissionForPickup} by id. * - * @param submissionId unique identifier of a {@link SubmissionForPickup} - * @return {@link ReceivedSubmission} to get the submission {@link Metadata}, {@link Data} and {@link Attachment}s as well as + * @param submission {@link SentSubmission} that is requested + * @return {@link ReceivedSubmission} to get the submission {@link Metadata}, {@link Data} and {@link ApiAttachment}s as well as * accept or reject the loaded submission */ - public ReceivedSubmission requestSubmission(final UUID submissionId) { - - try { - - final var startTimeDownloadSubmission = StopWatch.start(); - final Submission submission = subscriber.getSubmission(submissionId); - LOGGER.info("Downloading submission took {}", StopWatch.stopWithFormattedTime(startTimeDownloadSubmission)); - - LOGGER.info("Decrypting metadata ..."); - final Metadata metadata = decryptMetadata(submission.getEncryptedMetadata()); - final ValidationResult metadataValidation = subscriber.validateMetadata(metadata); - if (metadataValidation.hasError()) { - LOGGER.error("Metadata does not match schema", metadataValidation.getError()); - return null; - } - - LOGGER.info("Decrypting data ..."); - final byte[] decryptedData = decryptData(submission.getEncryptedData()); - final String hashFromSender = getDataHashFromMetadata(metadata); - final ValidationResult dataValidation = subscriber.validateHashIntegrity(hashFromSender, decryptedData); - if (dataValidation.hasError()) { - LOGGER.error("Data might be corrupted, hash-sum does not match", dataValidation.getError()); - return null; - } - - LOGGER.info("Loading and decrypting attachments ..."); - final List<Attachment> attachmentMetadata = metadata.getContentStructure().getAttachments(); - final List<DecryptedAttachmentPayload> decryptedAttachmentPayloads = loadAttachments(submissionId, attachmentMetadata); - final ValidationResult attachmentValidation = validateAttachments(decryptedAttachmentPayloads); - if (attachmentValidation.hasError()) { - LOGGER.error("One of the attachments is invalid", attachmentValidation.getError()); - return null; - } - - return buildReceivedSubmission(submission, metadata, decryptedData, decryptedAttachmentPayloads); - - } catch (final DecryptionException e) { - LOGGER.error("Decrypting payload failed", e); - } catch (final RestApiException e) { - LOGGER.error("API request failed", e); - } catch (final EventCreationException e) { - LOGGER.error("Creating SET event failed", e); - } catch (final IOException e) { - LOGGER.error("Reading metadata failed", e); - } - - return null; + public ReceivedSubmission requestSubmission(final SubmissionForPickup submission) { + return requestSubmission(submission.getSubmissionId()); } - public ValidationResult validateCallback(final String hmac, final Long timestamp, final String httpBody, final String callbackSecret) { - return subscriber.validateCallback(hmac, timestamp, httpBody, callbackSecret); - } - - private ReceivedSubmission buildReceivedSubmission(final Submission submission, final Metadata metadata, final byte[] decryptedData, final List<DecryptedAttachmentPayload> attachments) { - final MimeType mimeType = metadata.getContentStructure().getData().getSubmissionSchema().getMimeType(); - final ReceivedData receivedData = new ReceivedData(new String(decryptedData, StandardCharsets.UTF_8), mimeType); - final List<ReceivedAttachment> receivedAttachments = attachments.stream().map(mapToReceivedAttachment()).collect(Collectors.toList()); - return new ReceivedSubmission(subscriber, submission, metadata, receivedData, receivedAttachments); - } - - private static Function<DecryptedAttachmentPayload, ReceivedAttachment> mapToReceivedAttachment() { - return payload -> ReceivedAttachment.builder() - .attachmentId(payload.getAttachmentMetadata().getAttachmentId()) - .filename(payload.getAttachmentMetadata().getFilename()) - .mimeType(payload.getAttachmentMetadata().getMimeType()) - .description(payload.getAttachmentMetadata().getDescription()) - .data(payload.getDecryptedContent()) - .build(); - } - - private List<DecryptedAttachmentPayload> loadAttachments(final UUID submissionId, final List<Attachment> attachmentMetadata) { - if (attachmentMetadata == null || attachmentMetadata.isEmpty()) { - LOGGER.info("Submission contains no attachments"); - return Collections.emptyList(); - } - final List<DecryptedAttachmentPayload> receivedAttachments = new ArrayList<>(); - for (final Attachment metadata : attachmentMetadata) { - final String encryptedAttachment = downloadAttachment(submissionId, metadata); - final byte[] decryptedAttachment = decryptAttachment(metadata, encryptedAttachment); - final DecryptedAttachmentPayload decryptedAttachmentPayload = DecryptedAttachmentPayload.builder() - .decryptedContent(decryptedAttachment) - .attachmentMetadata(metadata) - .build(); - receivedAttachments.add(decryptedAttachmentPayload); - } - return receivedAttachments; - } - - private byte[] decryptAttachment(final Attachment metadata, final String encryptedAttachment) { - final var startDecryption = StopWatch.start(); - final byte[] decryptedAttachment = subscriber.decryptStringContent(privateKey, encryptedAttachment); - LOGGER.info("Decrypting attachment {} took {}", metadata.getAttachmentId(), StopWatch.stopWithFormattedTime(startDecryption)); - return decryptedAttachment; - } - - private String downloadAttachment(final UUID submissionId, final Attachment metadata) { - final var startDownload = StopWatch.start(); - final String encryptedAttachment = subscriber.fetchAttachment(submissionId, metadata.getAttachmentId()); - LOGGER.info("Downloading attachment {} took {}", metadata.getAttachmentId(), StopWatch.stopWithFormattedTime(startDownload)); - return encryptedAttachment; - } - - private byte[] decryptData(final String encryptedData) { - return subscriber.decryptStringContent(privateKey, encryptedData); - } - - private Metadata decryptMetadata(final String encryptedMetadata) throws IOException { - final byte[] metadataBytes = subscriber.decryptStringContent(privateKey, encryptedMetadata); - return MAPPER.readValue(metadataBytes, Metadata.class); + /** + * Loads a single {@link SubmissionForPickup} by id. Auto-rejects invalid submissions where e.g. a validation failed. + * + * @param submissionId unique identifier of the requested submission + * @return {@link ReceivedSubmission} to get the submission {@link Metadata}, {@link Data} and {@link Attachment}s as well as accept or reject the loaded submission + * @throws SubmissionRequestException if a technical error occurred or validation failed + */ + public ReceivedSubmission requestSubmission(final UUID submissionId) throws SubmissionRequestException { + return submissionReceiver.receiveSubmission(submissionId); } - private ValidationResult validateAttachments(final List<DecryptedAttachmentPayload> decryptedAttachmentPayloads) { - for (final DecryptedAttachmentPayload decryptedAttachment : decryptedAttachmentPayloads) { - final Attachment attachmentMetadata = decryptedAttachment.getAttachmentMetadata(); - final UUID attachmentId = attachmentMetadata.getAttachmentId(); - final ValidationResult result = subscriber.validateHashIntegrity(attachmentMetadata.getHash().getContent(), decryptedAttachment.getDecryptedContent()); - if (result.hasError()) { - LOGGER.error("Attachment data for id {} is corrupted", attachmentId, result.getError()); - return ValidationResult.error(new DataIntegrityException("Attachment " + attachmentId + " is corrupt")); - } else { - LOGGER.info("Attachment {} with id {} is valid", attachmentMetadata.getFilename(), attachmentId); - } - } - return ValidationResult.ok(); + /** + * Send a reject-submission {@link Event} to confirm that the handed in submission was rejected. + * + * @param submissionForPickup the submission that should be rejected + * @param rejectionProblems list of {@link Problem}s that give more detailed information on why the submission was rejected + * @see <a href="https://docs.fitko.de/fit-connect/docs/getting-started/event-log/events">FIT-Connect Events</a> + */ + public void rejectSubmission(final SubmissionForPickup submissionForPickup, final List<Problem> rejectionProblems) { + subscriber.rejectSubmission(EventPayload.forRejectEvent(submissionForPickup, rejectionProblems)); } - private String getDataHashFromMetadata(final Metadata metadata) { - return metadata.getContentStructure() - .getData() - .getHash() - .getContent(); + /** + * Checks if a received callback can be trusted by validating the provided request data + * + * @param hmac authentication code provided by the callback + * @param timestamp timestamp provided by the callback + * @param httpBody HTTP body provided by the callback + * @param callbackSecret secret owned by the client, which is used to calculate the hmac + * @return {@code true} if hmac and timestamp provided by the callback meet the required conditions + */ + public ValidationResult validateCallback(final String hmac, final Long timestamp, final String httpBody, final String callbackSecret) { + return subscriber.validateCallback(hmac, timestamp, httpBody, callbackSecret); } } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/cli/CommandExecutor.java b/client/src/main/java/dev/fitko/fitconnect/client/cli/CommandExecutor.java index 7410ce89e10fa6bf8d10e83ffdd5bf56e590eea4..e1f19a66702c72ce2a572d2df27124044e888dbd 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/cli/CommandExecutor.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/cli/CommandExecutor.java @@ -7,23 +7,27 @@ import dev.fitko.fitconnect.client.SenderClient; import dev.fitko.fitconnect.client.SubscriberClient; import dev.fitko.fitconnect.client.cli.batch.BatchImporter; import dev.fitko.fitconnect.client.cli.batch.ImportRecord; -import dev.fitko.fitconnect.client.cli.commands.*; +import dev.fitko.fitconnect.client.cli.commands.GetAllSubmissionsCommand; +import dev.fitko.fitconnect.client.cli.commands.GetOneSubmissionCommand; +import dev.fitko.fitconnect.client.cli.commands.ListAllSubmissionsCommand; +import dev.fitko.fitconnect.client.cli.commands.SendBatchCommand; +import dev.fitko.fitconnect.client.cli.commands.SendSubmissionCommand; import dev.fitko.fitconnect.client.cli.util.AttachmentDataType; -import dev.fitko.fitconnect.client.sender.SubmissionBuilder; -import dev.fitko.fitconnect.client.sender.model.SubmissionPayload; +import dev.fitko.fitconnect.client.sender.model.Attachment; +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission; -import dev.fitko.fitconnect.client.subscriber.model.ReceivedAttachment; import dev.fitko.fitconnect.core.util.StopWatch; +import org.apache.tika.Tika; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -59,7 +63,7 @@ class CommandExecutor { void getAllSubmissions(final GetAllSubmissionsCommand getAllSubmissionsCommand) throws IOException { final var destinationId = getAllSubmissionsCommand.destinationId; LOGGER.info("Getting all available submissions for destination {}", destinationId); - final Set<SubmissionForPickup> submissions = subscriberClient.getAvailableSubmissions(destinationId); + final Set<SubmissionForPickup> submissions = subscriberClient.getAvailableSubmissionsForDestination(destinationId); for (final SubmissionForPickup submission : submissions) { final GetOneSubmissionCommand getOneSubmissionCommand = new GetOneSubmissionCommand(); getOneSubmissionCommand.submissionId = submission.getSubmissionId(); @@ -71,15 +75,16 @@ class CommandExecutor { void listSubmissions(final ListAllSubmissionsCommand listAllSubmissionsCommand) { final var destinationId = listAllSubmissionsCommand.destinationId; LOGGER.info("Listing available submissions for destination {}", destinationId); - for (final SubmissionForPickup submission : subscriberClient.getAvailableSubmissions(destinationId)) { + for (final SubmissionForPickup submission : subscriberClient.getAvailableSubmissionsForDestination(destinationId)) { LOGGER.info("caseId: {} - submissionId: {}", submission.getCaseId(), submission.getSubmissionId()); } } SentSubmission sendSubmission(final SendSubmissionCommand sendSubmissionCommand) throws IOException { LOGGER.info("Sending new submission to destination {}", sendSubmissionCommand.destinationId); + final Tika mimeTypeDetector = new Tika(); final var startTime = StopWatch.start(); - final List<File> attachments = sendSubmissionCommand.attachments.stream().map(File::new).collect(Collectors.toList()); + final List<Attachment> attachments = sendSubmissionCommand.attachments.stream().map(toAttachment(mimeTypeDetector)).collect(Collectors.toList()); final SentSubmission submission; if (sendSubmissionCommand.dataType == AttachmentDataType.json) { submission = sendWithJsonData(sendSubmissionCommand, attachments); @@ -89,7 +94,6 @@ class CommandExecutor { LOGGER.info("Import took {}", StopWatch.stopWithFormattedTime(startTime)); return submission; } - void sendBatch(final SendBatchCommand sendBatchCommand) throws BatchImportException, IOException { final List<ImportRecord> importRecords = batchImporter.readRecords(sendBatchCommand.dataPath); LOGGER.info("Sending batch of {} submissions", importRecords.size()); @@ -106,28 +110,28 @@ class CommandExecutor { LOGGER.info("DONE ! Finished batch import of {} submissions in {}", importCount, StopWatch.stopWithFormattedTime(startTime)); } - private SentSubmission sendWithJsonData(final SendSubmissionCommand sendSubmissionCommand, final List<File> attachments) throws IOException { + private SentSubmission sendWithJsonData(final SendSubmissionCommand sendSubmissionCommand, final List<Attachment> attachments) throws IOException { - final SubmissionPayload submissionPayload = SubmissionBuilder.Builder() - .withAttachments(attachments) - .withJsonData(getDataAsString(sendSubmissionCommand.data), sendSubmissionCommand.schemaUri) - .withDestination(sendSubmissionCommand.destinationId) - .withServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey) + final SendableSubmission sendableSubmission = SendableSubmission.Builder() + .setDestination(sendSubmissionCommand.destinationId) + .setServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey) + .setJsonData(getDataAsString(sendSubmissionCommand.data), sendSubmissionCommand.schemaUri) + .addAttachments(attachments) .build(); - return senderClient.submit(submissionPayload); + return senderClient.send(sendableSubmission); } - private SentSubmission sendWithXmlData(final SendSubmissionCommand sendSubmissionCommand, final List<File> attachments) throws IOException { + private SentSubmission sendWithXmlData(final SendSubmissionCommand sendSubmissionCommand, final List<Attachment> attachments) throws IOException { - final SubmissionPayload submissionPayload = SubmissionBuilder.Builder() - .withAttachments(attachments) - .withXmlData(getDataAsString(sendSubmissionCommand.data), sendSubmissionCommand.schemaUri) - .withDestination(sendSubmissionCommand.destinationId) - .withServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey) + final SendableSubmission sendableSubmission = SendableSubmission.Builder() + .setDestination(sendSubmissionCommand.destinationId) + .setServiceType(sendSubmissionCommand.serviceName, sendSubmissionCommand.leikaKey) + .setXmlData(getDataAsString(sendSubmissionCommand.data), sendSubmissionCommand.schemaUri) + .addAttachments(attachments) .build(); - return senderClient.submit(submissionPayload); + return senderClient.send(sendableSubmission); } private String getTargetFolderPath(final GetOneSubmissionCommand getOneSubmissionCommand) { @@ -143,6 +147,9 @@ class CommandExecutor { return Files.readString(Path.of(dataPath)); } + private Function<String, Attachment> toAttachment(final Tika mimeTypeDetector) { + return path -> Attachment.fromPath(Path.of(path), mimeTypeDetector.detect(path), Path.of(path).getFileName().toString(), "attachment"); + } private void writeData(final ReceivedSubmission receivedSubmission, final String dataDirPath) throws IOException { LOGGER.info("Creating data directory for submission in {}", dataDirPath); @@ -151,12 +158,12 @@ class CommandExecutor { final var fileEnding = AttachmentDataType.getFileTypeFromMimeType(receivedSubmission.getDataMimeType()); final var filePath = Path.of(dataDirPath + "/data." + fileEnding); LOGGER.info("Writing data.{}", fileEnding); - Files.write(filePath, receivedSubmission.getData().getBytes(StandardCharsets.UTF_8)); + Files.write(filePath, receivedSubmission.getDataAsString().getBytes(StandardCharsets.UTF_8)); - for (final ReceivedAttachment attachment : receivedSubmission.getAttachments()) { - final String filename = attachment.getFilename(); + for (final Attachment attachment : receivedSubmission.getAttachments()) { + final String filename = attachment.getFileName(); LOGGER.info("Writing attachment {}", filename); - Files.write(Path.of(dataDirPath, filename), attachment.getData()); + Files.write(Path.of(dataDirPath, filename), attachment.getDataAsBytes()); } } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/cli/CommandLineRunner.java b/client/src/main/java/dev/fitko/fitconnect/client/cli/CommandLineRunner.java index d8d2ea66c97b0202c5f1226edef866d160bf0df4..8b0cd7ef434ad68e5cb76abd97ecf2be2e24a4c1 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/cli/CommandLineRunner.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/cli/CommandLineRunner.java @@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.file.Path; import java.util.stream.Collectors; public final class CommandLineRunner { @@ -29,16 +30,16 @@ public final class CommandLineRunner { private static ApplicationConfig loadConfig() { try { - return ApplicationConfigLoader.loadConfig(DEFAULT_CONFIG_NAME); + return ApplicationConfigLoader.loadConfigFromEnvironment(); } catch (final Exception e) { - LOGGER.warn("Could not load default config {} {}", DEFAULT_CONFIG_NAME, e); - return null; + LOGGER.warn("Could not load config from environment, loading default config {} {}", DEFAULT_CONFIG_NAME, e); + return ApplicationConfigLoader.loadConfigFromPath(Path.of(DEFAULT_CONFIG_NAME)); } } private static CommandLineClient getCommandLineClient(final ApplicationConfig config) { - final SenderClient senderClient = config == null ? ClientFactory.senderClient() : ClientFactory.senderClient(config); - final var subscriberClient = config == null ? ClientFactory.subscriberClient() : ClientFactory.subscriberClient(config); + final SenderClient senderClient = ClientFactory.getSenderClient(config); + final var subscriberClient = ClientFactory.getSubscriberClient(config); return new CommandLineClient(senderClient, subscriberClient); } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/cli/util/AttachmentDataType.java b/client/src/main/java/dev/fitko/fitconnect/client/cli/util/AttachmentDataType.java index 57059cd41afd7e97558184abecf7442c68759d01..88adcd6936c017b2379dc3b7a79730546a471b7d 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/cli/util/AttachmentDataType.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/cli/util/AttachmentDataType.java @@ -12,8 +12,8 @@ public enum AttachmentDataType { this.fileType = fileType; } - public static String getFileTypeFromMimeType(final MimeType mimeType) { - switch (mimeType) { + public static String getFileTypeFromMimeType(final String mimeType) { + switch (MimeType.fromValue(mimeType)) { case APPLICATION_JSON: return json.fileType; case APPLICATION_XML: diff --git a/client/src/main/java/dev/fitko/fitconnect/client/factory/ApplicationConfigLoader.java b/client/src/main/java/dev/fitko/fitconnect/client/factory/ApplicationConfigLoader.java index 09269e0bf79d9a27cb06cb8140a4b8c36d7d7acd..0fb4bc65655f2ef0acdd3fbaf0f3165edbebf74d 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/factory/ApplicationConfigLoader.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/factory/ApplicationConfigLoader.java @@ -1,18 +1,20 @@ package dev.fitko.fitconnect.client.factory; import dev.fitko.fitconnect.api.config.ApplicationConfig; -import dev.fitko.fitconnect.api.config.BuildInfo; import dev.fitko.fitconnect.api.exceptions.InitializationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; public final class ApplicationConfigLoader { - private static final String BUILD_INFO_PATH = "buildinfo.yaml"; + private static final Logger LOGGER = LoggerFactory.getLogger(ClientFactory.class); + private static final String CONFIG_ENV_KEY_NAME = "FIT_CONNECT_CONFIG"; private ApplicationConfigLoader() { } @@ -24,23 +26,25 @@ public final class ApplicationConfigLoader { * @return ApplicationConfig * @throws InitializationException if the config file could not be loaded */ - public static ApplicationConfig loadConfig(final Path configPath) { + public static ApplicationConfig loadConfigFromPath(final Path configPath) { try { - return loadConfigFromYaml(Files.readString(configPath)); + return loadConfigFromYamlString(Files.readString(configPath)); } catch (final IOException | NullPointerException e) { + LOGGER.error("config file could not be loaded from path {}", configPath); throw new InitializationException(e.getMessage(), e); } } /** - * Load ApplicationConfig from path string. + * Load ApplicationConfig from FIT_CONNECT_CONFIG environment variable. + * Make sure this variable is set and points to a config.yaml. * - * @param configPath string of path to config file * @return ApplicationConfig * @throws InitializationException if the config file could not be loaded */ - public static ApplicationConfig loadConfig(final String configPath) { - return loadConfig(Path.of(configPath)); + public static ApplicationConfig loadConfigFromEnvironment() { + final Path configPath = getPathFromEnvironment(); + return loadConfigFromPath(configPath); } /** @@ -49,17 +53,17 @@ public final class ApplicationConfigLoader { * @param configYaml string content of the yaml config file * @return ApplicationConfig */ - public static ApplicationConfig loadConfigFromYaml(final String configYaml) { + public static ApplicationConfig loadConfigFromYamlString(final String configYaml) { return new Yaml().loadAs(configYaml, ApplicationConfig.class); } - /** - * Load BuildInfo properties. - * - * @return {@link BuildInfo} - */ - public static BuildInfo loadBuildInfo() { - final InputStream resource = ApplicationConfigLoader.class.getClassLoader().getResourceAsStream(BUILD_INFO_PATH); - return new Yaml().loadAs(resource, BuildInfo.class); + private static Path getPathFromEnvironment() { + try { + return Path.of(System.getenv(CONFIG_ENV_KEY_NAME)); + } catch (final NullPointerException | InvalidPathException e) { + LOGGER.error("Environment variable {} could not be loaded", CONFIG_ENV_KEY_NAME); + throw new InitializationException(e.getMessage(), e); + } } + } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/factory/ClientFactory.java b/client/src/main/java/dev/fitko/fitconnect/client/factory/ClientFactory.java index cfdd4ee6d739976c8ef678ef0ca8c09470575669..55af02f9afbe5daa0a203463f756157c3f7fe896 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/factory/ClientFactory.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/factory/ClientFactory.java @@ -25,6 +25,7 @@ import dev.fitko.fitconnect.api.services.validation.ValidationService; import dev.fitko.fitconnect.client.RoutingClient; import dev.fitko.fitconnect.client.SenderClient; import dev.fitko.fitconnect.client.SubscriberClient; +import dev.fitko.fitconnect.client.subscriber.SubmissionReceiver; import dev.fitko.fitconnect.core.SubmissionSender; import dev.fitko.fitconnect.core.SubmissionSubscriber; import dev.fitko.fitconnect.core.auth.DefaultOAuthService; @@ -39,12 +40,15 @@ import dev.fitko.fitconnect.core.routing.RouteVerifier; import dev.fitko.fitconnect.core.routing.RoutingApiService; import dev.fitko.fitconnect.core.schema.SchemaResourceProvider; import dev.fitko.fitconnect.core.submission.SubmissionApiService; +import dev.fitko.fitconnect.core.util.FileUtil; import dev.fitko.fitconnect.core.validation.DefaultValidationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.client.RestTemplate; +import org.yaml.snakeyaml.Yaml; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.text.ParseException; @@ -57,22 +61,14 @@ public final class ClientFactory { private static final Logger LOGGER = LoggerFactory.getLogger(ClientFactory.class); - private static final String CONFIG_ENV_KEY_NAME = "FIT_CONNECT_CONFIG"; private static final String SET_SCHEMA_DIR = "/set-schema"; - private static final String DESTINATION_SCHEMA_DIR = "/destination-schema"; private static final String METADATA_SCHEMA_DIR = "/metadata-schema"; + private static final String PATH_TO_TRUSTED_ROOT_CERTIFICATES = "trusted-root-certificates"; + private static final String BUILD_INFO_PATH = "buildinfo.yaml"; - private ClientFactory() { - } - /** - * Create a new {@link SenderClient} to send submissions that is automatically configured via the config.yml file. - * - * @return the sender client - */ - public static SenderClient senderClient() { - return senderClient(loadConfig()); + private ClientFactory() { } /** @@ -80,19 +76,10 @@ public final class ClientFactory { * * @return the sender client */ - public static SenderClient senderClient(final ApplicationConfig config) { + public static SenderClient getSenderClient(final ApplicationConfig config) { + checkNotNull(config); LOGGER.info("Initializing sender client ..."); - return new SenderClient(getSender(config, ApplicationConfigLoader.loadBuildInfo())); - } - - - /** - * Create a new {@link SubscriberClient} to receive submissions that is automatically configured via config.yml file. - * - * @return the subscriber client - */ - public static SubscriberClient subscriberClient() { - return subscriberClient(loadConfig()); + return new SenderClient(getSender(config, loadBuildInfo())); } /** @@ -100,35 +87,33 @@ public final class ClientFactory { * * @return the subscriber client */ - public static SubscriberClient subscriberClient(final ApplicationConfig config) { + public static SubscriberClient getSubscriberClient(final ApplicationConfig config) { + + checkNotNull(config); + LOGGER.info("Initializing subscriber client ..."); - final Subscriber subscriber = getSubscriber(config, ApplicationConfigLoader.loadBuildInfo()); + final Subscriber subscriber = getSubscriber(config, loadBuildInfo()); final SubscriberConfig subscriberConfig = config.getSubscriberConfig(); - LOGGER.info("Reading private decryption key from {} ", subscriberConfig.getPrivateDecryptionKeyPath()); - final String privateKeyPath = readPath(subscriberConfig.getPrivateDecryptionKeyPath(), "Decryption Key"); + LOGGER.info("Reading private decryption key"); + final String privateKeyPath = readPath(getPrivateDecryptionKeyPathFromSubscriber(subscriberConfig), "Decryption Key"); final RSAKey privateKey = readRSAKeyFromString(privateKeyPath); - return new SubscriberClient(subscriber, privateKey); - } + final SubmissionReceiver submissionReceiver = new SubmissionReceiver(subscriber, privateKey, config); - /** - * Create a new {@link RoutingClient} to find destinations and services that is automatically configured via a provided {@link ApplicationConfig}. - * - * @return the routing client - */ - public static RoutingClient routingClient() { - return routingClient(loadConfig()); + return new SubscriberClient(subscriber, submissionReceiver); } - /** * Create a new {@link RoutingClient} to find destinations and services that is automatically configured via a provided {@link ApplicationConfig}. * * @return the routing client */ - public static RoutingClient routingClient(final ApplicationConfig config) { + public static RoutingClient getRoutingClient(final ApplicationConfig config) { + + checkNotNull(config); + LOGGER.info("Initializing routing client ..."); - final RestTemplate restTemplate = getRestTemplate(config, ApplicationConfigLoader.loadBuildInfo()); + final RestTemplate restTemplate = getRestTemplate(config, loadBuildInfo()); final SchemaProvider schemaProvider = getSchemaProvider(); final OAuthService authService = getSenderConfiguredAuthService(config, restTemplate); @@ -178,7 +163,6 @@ public final class ClientFactory { return new SubmissionSubscriber(submissionService, eventLogService, cryptoService, validator, setService); } - private static OAuthService getSenderConfiguredAuthService(final ApplicationConfig config, final RestTemplate restTemplate) { final String clientId = config.getSenderConfig().getClientId(); final String clientSecret = config.getSenderConfig().getClientSecret(); @@ -200,7 +184,7 @@ public final class ClientFactory { } private static ValidationService getValidationService(final ApplicationConfig config, final SchemaProvider schemaProvider, final MessageDigestService messageDigestService) { - return new DefaultValidationService(config, messageDigestService, schemaProvider); + return new DefaultValidationService(config, messageDigestService, schemaProvider, FileUtil.loadContentOfFilesInDirectory(PATH_TO_TRUSTED_ROOT_CERTIFICATES)); } private static CryptoService getCryptoService(final MessageDigestService messageDigestService) { @@ -265,24 +249,23 @@ public final class ClientFactory { } } - private static Path readConfigFromEnvironment() { - try { - return Path.of(System.getenv(CONFIG_ENV_KEY_NAME)); - } catch (final NullPointerException e) { - LOGGER.error("Environment variable {} could not be loaded", CONFIG_ENV_KEY_NAME); - throw new InitializationException(e.getMessage(), e); + private static BuildInfo loadBuildInfo() { + final InputStream resource = ClientFactory.class.getClassLoader().getResourceAsStream(BUILD_INFO_PATH); + return new Yaml().loadAs(resource, BuildInfo.class); + } + + private static void checkNotNull(final ApplicationConfig config) { + if(config == null){ + throw new InitializationException("application config must not be null"); } } - private static ApplicationConfig loadConfig() { - try { - final Path configPath = readConfigFromEnvironment(); - final ApplicationConfig applicationConfig = ApplicationConfigLoader.loadConfig(configPath); - LOGGER.info("Using sdk environment config `{}` ", applicationConfig.getActiveEnvironment().getName()); - return applicationConfig; - } catch (final InitializationException e) { - LOGGER.error("Config could not be loaded, please provide a 'config.yml' by setting the environment variable 'FIT_CONNECT_CONFIG'"); - throw new InitializationException(e.getMessage(), e); + private static String getPrivateDecryptionKeyPathFromSubscriber(final SubscriberConfig subscriberConfig) { + if(subscriberConfig.getPrivateDecryptionKeyPaths().size() != 1){ + throw new InitializationException("Currently only one configured private key per subscriber is allowed !"); } + final String keyPath = subscriberConfig.getPrivateDecryptionKeyPaths().get(0); + LOGGER.info("Reading private decryption key from {}", keyPath); + return keyPath; } } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/router/DestinationSearch.java b/client/src/main/java/dev/fitko/fitconnect/client/router/DestinationSearch.java index fc9e37521e8a93ea0d7653291792988cb1b36818..eab9b403648844cbf232e5d7b68ae78acfc177ef 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/router/DestinationSearch.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/router/DestinationSearch.java @@ -3,9 +3,10 @@ package dev.fitko.fitconnect.client.router; import lombok.AllArgsConstructor; import lombok.Value; +import java.util.Arrays; +import java.util.Objects; import java.util.regex.Pattern; - /** * Builds a new search request for a service identifier and AT MAX. ONE OTHER search criterion (ars | ags | areaId). */ @@ -21,11 +22,118 @@ public class DestinationSearch { int offset; int limit; - public static Builder Builder() { + public static MandatoryProperties Builder() { return new Builder(); } - public static class Builder { + public interface MandatoryProperties { + + /** + * Leika Key of the requested service. + * + * @param leikaKey service identifier + * @return Builder + * @throws IllegalArgumentException if the leika key pattern is not matching + */ + OptionalProperties withLeikaKey(final String leikaKey) throws IllegalArgumentException; + } + + public interface OptionalProperties { + + /** + * Official municipal code of the place. + * + * @param ars amtlicher regionalschlüssel + * @return Builder + * @throws IllegalArgumentException if the ars key pattern is not matching + */ + Pagination withArs(final String ars) throws IllegalArgumentException; + + /** + * Official regional key of the area. + * + * @param ags amtlicher gemeindeschlüssel + * @return Builder + * @throws IllegalArgumentException if the ags key pattern is not matching + */ + Pagination withAgs(final String ags) throws IllegalArgumentException; + + /** + * ID of the area. This ID can be determined via the routing clients <code>findAreas</code> search. + * + * @param areaId id of the area + * @return Builder + * @throws IllegalArgumentException if the area id pattern is not matching + * @see dev.fitko.fitconnect.client.RoutingClient#findAreas(String, int, int) + */ + Pagination withAreaId(final String areaId) throws IllegalArgumentException; + + /** + * Start position of the subset of the result set. Default is 0. + * + * @param offset start of the subset + * @return Builder + * @throws IllegalArgumentException if the offset is a negative number + */ + Builder withOffset(final int offset) throws IllegalArgumentException; + + /** + * Max. size of the subset of the result set. Maximum is 500. Default is 100. + * + * @param limit max. entries in the subset + * @return Builder + * @throws IllegalArgumentException if the limit is > 500 + */ + Builder withLimit(final int limit) throws IllegalArgumentException; + + /** + * Construct the search request. + * + * @return DestinationSearch + */ + DestinationSearch build() throws IllegalArgumentException; + + } + + public interface Pagination { + + /** + * Start position of the subset of the result set. Default is 0. + * + * @param offset start of the subset + * @return Builder + * @throws IllegalArgumentException if the offset is a negative number + */ + Pagination withOffset(final int offset) throws IllegalArgumentException; + + /** + * Max. size of the subset of the result set. Maximum is 500. Default is 100. + * + * @param limit max. entries in the subset + * @return Builder + * @throws IllegalArgumentException if the limit is > 500 + */ + Pagination withLimit(final int limit) throws IllegalArgumentException; + + /** + * Construct the search request. + * + * @return DestinationSearch + */ + DestinationSearch build() throws IllegalArgumentException; + + } + + public interface BuildSearch { + /** + * Construct the search request. + * + * @return DestinationSearch + */ + DestinationSearch build() throws IllegalArgumentException; + } + + private static class Builder implements MandatoryProperties, OptionalProperties, Pagination, BuildSearch { private static final Pattern AGS_PATTERN = Pattern.compile("^(\\d{2}|\\d{3}|\\d{5}|\\d{8})$"); private static final Pattern ARS_PATTERN = Pattern.compile("^(\\d{2}|\\d{3}|\\d{5}|\\d{9}|\\d{12})$"); @@ -39,14 +147,9 @@ public class DestinationSearch { private int offset = 0; private int limit = 100; - /** - * Leika Key of the requested service. - * - * @param leikaKey service identifier - * @return Builder - * @throws IllegalArgumentException if the leika key pattern is not matching - */ - public Builder withLeikaKey(final String leikaKey) throws IllegalArgumentException { + + @Override + public OptionalProperties withLeikaKey(final String leikaKey) throws IllegalArgumentException { if (!LEIKA_KEY_PATTERN.matcher(leikaKey).matches()) { throw new IllegalArgumentException("Leika key does not match allowed pattern " + LEIKA_KEY_PATTERN); } @@ -54,14 +157,8 @@ public class DestinationSearch { return this; } - /** - * Official municipal code of the place. - * - * @param ars amtlicher regionalschlüssel - * @return Builder - * @throws IllegalArgumentException if the ars key pattern is not matching - */ - public Builder withArs(final String ars) throws IllegalArgumentException{ + @Override + public Pagination withArs(final String ars) throws IllegalArgumentException { if (!ARS_PATTERN.matcher(ars).matches()) { throw new IllegalArgumentException("ARS key does not match allowed pattern " + ARS_PATTERN); } @@ -70,14 +167,8 @@ public class DestinationSearch { return this; } - /** - * Official regional key of the area. - * - * @param ags amtlicher gemeindeschlüssel - * @return Builder - * @throws IllegalArgumentException if the ags key pattern is not matching - */ - public Builder withAgs(final String ags) throws IllegalArgumentException { + @Override + public Pagination withAgs(final String ags) throws IllegalArgumentException { if (!AGS_PATTERN.matcher(ags).matches()) { throw new IllegalArgumentException("AGS key does not match allowed pattern " + AGS_PATTERN); } @@ -90,10 +181,10 @@ public class DestinationSearch { * * @param areaId id of the area * @return Builder - * @see dev.fitko.fitconnect.client.RoutingClient#findAreas(String, int, int) * @throws IllegalArgumentException if the area id pattern is not matching + * @see dev.fitko.fitconnect.client.RoutingClient#findAreas(String, int, int) */ - public Builder withAreaId(final String areaId) throws IllegalArgumentException { + public Pagination withAreaId(final String areaId) throws IllegalArgumentException { if (!AREA_ID_PATTERN.matcher(areaId).matches()) { throw new IllegalArgumentException("AreaId key does not match allowed pattern " + AREA_ID_PATTERN); } @@ -101,14 +192,8 @@ public class DestinationSearch { return this; } - /** - * Start position of the subset of the result set. Default is 0. - * - * @param offset start of the subset - * @return Builder - * @throws IllegalArgumentException if the offset is a negative number - */ - public Builder withOffset(final int offset) throws IllegalArgumentException{ + @Override + public Builder withOffset(final int offset) throws IllegalArgumentException { if (limit < 0) { throw new IllegalArgumentException("offset must be positive"); } @@ -116,13 +201,7 @@ public class DestinationSearch { return this; } - /** - * Max. size of the subset of the result set. Maximum is 500. Default is 100. - * - * @param limit max. entries in the subset - * @return Builder - * @throws IllegalArgumentException if the limit is > 500 - */ + @Override public Builder withLimit(final int limit) throws IllegalArgumentException { if (limit > 500) { throw new IllegalArgumentException("limit must no be > 500"); @@ -131,15 +210,14 @@ public class DestinationSearch { return this; } - /** - * Construct the search request. - * - * @return DestinationSearch - */ + @Override public DestinationSearch build() throws IllegalArgumentException { - if(leikaKey == null){ + if (leikaKey == null) { throw new IllegalArgumentException("leikaKey is mandatory"); } + if(Arrays.asList(areaId, ags, ars).stream().allMatch(Objects::isNull)){ + throw new IllegalArgumentException("at least one regional search criterion (areaId, ars or ags) is mandatory"); + } return new DestinationSearch(leikaKey, ars, ags, areaId, offset, limit); } } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/EncryptedSubmissionBuilder.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/EncryptedSubmissionBuilder.java deleted file mode 100644 index 6845ec400ff212d216b92d5a6f4068e6057c1eba..0000000000000000000000000000000000000000 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/EncryptedSubmissionBuilder.java +++ /dev/null @@ -1,97 +0,0 @@ -package dev.fitko.fitconnect.client.sender; - -import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; -import dev.fitko.fitconnect.client.sender.model.EncryptedSubmissionPayload; -import dev.fitko.fitconnect.client.sender.steps.EncryptedBuildStep; -import dev.fitko.fitconnect.client.sender.steps.EncryptedBuilderStartStep; -import dev.fitko.fitconnect.client.sender.steps.EncryptedDataStep; -import dev.fitko.fitconnect.client.sender.steps.EncryptedDestinationStep; -import dev.fitko.fitconnect.client.sender.steps.EncryptedMetadataStep; -import dev.fitko.fitconnect.client.sender.steps.EncryptedServiceTypeStep; -import lombok.Getter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.Map; -import java.util.UUID; - -@Getter -public final class EncryptedSubmissionBuilder implements EncryptedBuilderStartStep, - EncryptedDataStep, - EncryptedMetadataStep, - EncryptedDestinationStep, - EncryptedServiceTypeStep, - EncryptedBuildStep { - - private static final Logger LOGGER = LoggerFactory.getLogger(EncryptedSubmissionBuilder.class); - - private UUID destinationId; - private Map<UUID, String> encryptedAttachments = Collections.emptyMap(); - private ServiceType serviceType; - private String encryptedData; - private String encryptedMetadata; - - private EncryptedSubmissionBuilder() { - } - - public static EncryptedBuilderStartStep Builder() { - return new EncryptedSubmissionBuilder(); - } - - @Override - public EncryptedServiceTypeStep withDestination(final UUID destinationId) { - this.destinationId = destinationId; - return this; - } - - @Override - public EncryptedDataStep withEncryptedAttachments(final Map<UUID, String> encryptedAttachments) { - this.encryptedAttachments = encryptedAttachments == null ? Collections.emptyMap() : encryptedAttachments; - return this; - } - - @Override - public EncryptedDataStep withEncryptedAttachment(final UUID attachmentId, final String encryptedAttachment) { - if (attachmentId == null || encryptedAttachment == null) { - return withEncryptedAttachments(Collections.emptyMap()); - } - return withEncryptedAttachments(Map.of(attachmentId, encryptedAttachment)); - } - - @Override - public EncryptedMetadataStep withEncryptedData(final String encryptedData) { - this.encryptedData = encryptedData; - return this; - } - - @Override - public EncryptedDestinationStep withEncryptedMetadata(final String encryptedMetadata) { - this.encryptedMetadata = encryptedMetadata; - return this; - } - - @Override - public EncryptedBuildStep withServiceType(final String serviceTypeName, final String leikaKey) { - serviceType = ServiceType.builder() - .identifier(leikaKey) - .name(serviceTypeName) - .build(); - return this; - } - - @Override - public EncryptedBuildStep withServiceType(final String serviceTypeName, final String description, final String leikaKey) { - serviceType = ServiceType.builder() - .identifier(leikaKey) - .description(description) - .name(serviceTypeName) - .build(); - return this; - } - - @Override - public EncryptedSubmissionPayload build() { - return new EncryptedSubmissionPayload(this); - } -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/SubmissionBuilder.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/SubmissionBuilder.java deleted file mode 100644 index 89a55fd239a0adeacc5dfba66fa45a9ee5b4fa20..0000000000000000000000000000000000000000 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/SubmissionBuilder.java +++ /dev/null @@ -1,101 +0,0 @@ -package dev.fitko.fitconnect.client.sender; - -import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; -import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; -import dev.fitko.fitconnect.client.sender.model.SubmissionPayload; -import dev.fitko.fitconnect.client.sender.steps.*; -import lombok.Getter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.net.URI; -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -import static dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType.APPLICATION_JSON; -import static dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType.APPLICATION_XML; - -@Getter -public final class SubmissionBuilder implements BuilderStartStep, - DataStep, - DestinationStep, - ServiceTypeStep, - BuildStep { - - private static final Logger LOGGER = LoggerFactory.getLogger(SubmissionBuilder.class); - - private UUID destinationId; - private List<File> attachments = Collections.emptyList(); - private String data; - private MimeType dataMimeType; - private ServiceType serviceType; - private URI schemaUri; - - private SubmissionBuilder() { - } - - public static BuilderStartStep Builder() { - return new SubmissionBuilder(); - } - - @Override - public ServiceTypeStep withDestination(final UUID destinationId) { - this.destinationId = destinationId; - return this; - } - - @Override - public DataStep withAttachments(final List<File> attachments) { - this.attachments = attachments == null ? Collections.emptyList() : attachments; - return this; - } - - @Override - public DataStep withAttachment(final File attachment) { - withAttachments(attachment == null ? Collections.emptyList() : List.of(attachment)); - return this; - } - - @Override - public DestinationStep withJsonData(final String jsonData, final URI schemaUri) { - data = jsonData; - dataMimeType = APPLICATION_JSON; - this.schemaUri = schemaUri; - return this; - } - - - @Override - public DestinationStep withXmlData(final String xmlData, final URI schemaUri) { - data = xmlData; - dataMimeType = APPLICATION_XML; - this.schemaUri = schemaUri; - return this; - } - - @Override - public BuildStep withServiceType(final String serviceTypeName, final String leikaKey) { - serviceType = ServiceType.builder() - .identifier(leikaKey) - .name(serviceTypeName) - .build(); - return this; - } - - @Override - public BuildStep withServiceType(final String serviceTypeName, final String description, final String leikaKey) { - serviceType = ServiceType.builder() - .identifier(leikaKey) - .description(description) - .name(serviceTypeName) - .build(); - return this; - } - - @Override - public SubmissionPayload build() { - return new SubmissionPayload(this); - } -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/model/Attachment.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/model/Attachment.java new file mode 100644 index 0000000000000000000000000000000000000000..932fe4ae7c3dc4e673997642dc045eeca63cbe91 --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/model/Attachment.java @@ -0,0 +1,196 @@ +package dev.fitko.fitconnect.client.sender.model; + +import dev.fitko.fitconnect.api.exceptions.AttachmentCreationException; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; + +public class Attachment { + private final byte[] data; + private final String fileName; + private final String description; + private final String mimeType; + + /** + * Create an attachment and read the content from a given path. + * + * @param filePath path of the attachment file + * @param mimeType mime-type of the attachment + */ + public static Attachment fromPath(final Path filePath, final String mimeType) throws AttachmentCreationException { + return fromPath(filePath, mimeType, null, null); + } + + /** + * Create an attachment and read the content from a given path. + * + * @param filePath path of the attachment file + * @param mimeType mime-type of the attachment + * @param fileName name of the attachment file + * @param description description of the attachment file + * @throws AttachmentCreationException if the file path could not be read + */ + public static Attachment fromPath(final Path filePath, final String mimeType, final String fileName, final String description) throws AttachmentCreationException { + return new Attachment(readBytesFromPath(filePath), mimeType, fileName, description); + } + + /** + * Create an attachment and read the content from an input stream. + * + * @param inputStream stream of the attachment data + * @param mimeType mime type of the provided attachment data + * @throws AttachmentCreationException if the input-stream could not be read + */ + public static Attachment fromInputStream(final InputStream inputStream, final String mimeType) throws AttachmentCreationException { + return fromInputStream(inputStream, mimeType, null, null); + } + + /** + * Create an attachment and read the content from an input stream. + * + * @param inputStream stream of the attachment data + * @param mimeType mime type of the provided attachment data + * @param fileName name of the attachment file + * @param description description of the attachment file + * @throws AttachmentCreationException if the input-stream could not be read + */ + public static Attachment fromInputStream(final InputStream inputStream, final String mimeType, final String fileName, final String description) throws AttachmentCreationException { + return new Attachment(readBytesFromInputStream(inputStream), mimeType, fileName, description); + } + + /** + * Create an attachment and read the content from a byte array. + * + * @param content data of the attachment as byte[] + * @param mimeType mime type of the provided attachment data + */ + public static Attachment fromByteArray(final byte[] content, final String mimeType) { + return fromByteArray(content, mimeType, null, null); + } + + /** + * Create an attachment and read the content from a byte array. + * + * @param content data of the attachment as byte[] + * @param fileName name of the attachment file + * @param mimeType mime type of the provided attachment data + * @param description description of the attachment file + */ + public static Attachment fromByteArray(final byte[] content, final String mimeType, final String fileName, final String description) { + return new Attachment(content, mimeType, fileName, description); + } + + /** + * Create an attachment and read the content from a given string. The content will be read with UTF-8 encoding. + * <p>Note: If you don't use an SDK to retrieve the submission you may have to decode the string with UTF-8</p> + * + * @param content data of the attachment as byte[] + * @param mimeType mime type of the provided attachment data + */ + public static Attachment fromString(final String content, final String mimeType) { + return fromString(content, mimeType, null, null); + } + + /** + * Create an attachment and read the content from a given string. The content will be read with UTF-8 encoding. + * <p>Note: If you don't use an SDK to retrieve the submission you may have to decode the string with UTF-8</p> + * + * @param content data of the attachment as string + * @param fileName name of the attachment file + * @param mimeType mime type of the provided attachment data + * @param description description of the attachment file + */ + public static Attachment fromString(final String content, final String mimeType, final String fileName, final String description) { + return new Attachment(content.getBytes(StandardCharsets.UTF_8), mimeType, fileName, description); + } + + /** + * Get the attachment content as byte[]. + * + * @return byte array of the attachments content + */ + public byte[] getDataAsBytes() { + return data; + } + + /** + * Get the attachment content as string, e.g. in case the mime-type is json or xml. + * + * @param encoding charset the string should be decoded with + * @return string of the attachments content. + */ + public String getDataAString(final Charset encoding) { + return new String(data, encoding); + } + + /** + * Get the attachment content as string with a default UTF-8 encoding, e.g. in case the mime-type is json or xml. + * + * @return utf-8 encoded string of the attachments content. + */ + public String getDataAString() { + return new String(data, StandardCharsets.UTF_8); + } + + /** + * Filename of the attachment. This filed is optional so it might be null. + * @return filename as string, null if not present + */ + public String getFileName() { + return fileName; + } + + /** + * Description of the attachment. This filed is optional so it might be null. + * @return description as string, null if not present + */ + public String getDescription() { + return description; + } + + /** + * Mimetype of the attachments content. + * @return mimetype as string. + */ + public String getMimeType() { + return mimeType; + } + + private Attachment(final byte[] data, final String mimeType, final String fileName, final String description) { + this.data = data; + this.fileName = fileName != null ? getBaseNameFromPath(fileName) : null; // prevent maliciously injected filePaths + this.mimeType = mimeType; + this.description = description; + } + + private static byte[] readBytesFromInputStream(final InputStream inputStream) { + try (final BufferedInputStream bis = new BufferedInputStream(inputStream)) { + return bis.readAllBytes(); + } catch (final IOException e) { + throw new AttachmentCreationException("Attachment could not be read from input-stream ", e); + } + } + + private static byte[] readBytesFromPath(final Path path) { + try { + return Files.readAllBytes(path); + } catch (final IOException e) { + throw new AttachmentCreationException("Reading attachment from path '" + path + "' failed ", e); + } + } + + private static String getBaseNameFromPath(final String fileName) { + try { + return Path.of(fileName).getFileName().toString(); + } catch (final InvalidPathException e) { + throw new AttachmentCreationException("Reading filename '" + fileName + "' failed ", e); + } + } + +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/model/AttachmentPayload.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/model/AttachmentPayload.java index 59b523eee03ffd0500541ca668ba906334e6044c..a3b1fc8503231575c41c6c0f1ffb059f620e89ca 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/model/AttachmentPayload.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/model/AttachmentPayload.java @@ -4,16 +4,16 @@ import lombok.Builder; import lombok.Getter; import lombok.With; -import java.io.File; import java.util.UUID; @With @Getter @Builder public class AttachmentPayload { - private File file; + private UUID attachmentId; private String hashedData; private String encryptedData; private String mimeType; - private UUID attachmentId; + private String fileName; + private String description; } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/model/EncryptedAttachment.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/model/EncryptedAttachment.java new file mode 100644 index 0000000000000000000000000000000000000000..0648c6d2dd91e83154728a85d95285daeb6f39ff --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/model/EncryptedAttachment.java @@ -0,0 +1,25 @@ +package dev.fitko.fitconnect.client.sender.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.UUID; + +@Getter +@EqualsAndHashCode +public class EncryptedAttachment { + + private final UUID attachmentId; + private final String content; + + /** + * Create a new encrypted attachment. + * + * @param attachmentId the attachmentId that matches with the announced attachmentId in the submissions metadata + * @param content encrypted content of the attachment + */ + public EncryptedAttachment(final UUID attachmentId, final String content) { + this.attachmentId = attachmentId; + this.content = content; + } +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/model/EncryptedSubmissionPayload.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/model/EncryptedSubmissionPayload.java deleted file mode 100644 index 9194448bd82ad851ff1872c37d422ce6afa7a815..0000000000000000000000000000000000000000 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/model/EncryptedSubmissionPayload.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.fitko.fitconnect.client.sender.model; - -import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; -import dev.fitko.fitconnect.client.sender.EncryptedSubmissionBuilder; -import lombok.Getter; - -import java.util.Map; -import java.util.UUID; - -@Getter -public class EncryptedSubmissionPayload { - - private final UUID destinationId; - private final Map<UUID, String> encryptedAttachments; - private final String encryptedData; - private final String encryptedMetadata; - private final ServiceType serviceType; - - public EncryptedSubmissionPayload(final EncryptedSubmissionBuilder builder) { - destinationId = builder.getDestinationId(); - encryptedAttachments = builder.getEncryptedAttachments(); - encryptedData = builder.getEncryptedData(); - encryptedMetadata = builder.getEncryptedMetadata(); - serviceType = builder.getServiceType(); - } - -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/model/SendableEncryptedSubmission.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/model/SendableEncryptedSubmission.java new file mode 100644 index 0000000000000000000000000000000000000000..d4182086220c0be2fbba45cd5682031cde9a00f4 --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/model/SendableEncryptedSubmission.java @@ -0,0 +1,107 @@ +package dev.fitko.fitconnect.client.sender.model; + +import dev.fitko.fitconnect.client.sender.steps.encrypted.EncryptedAttachmentsStep; +import dev.fitko.fitconnect.client.sender.steps.encrypted.EncryptedBuildStep; +import dev.fitko.fitconnect.client.sender.steps.encrypted.EncryptedDataStep; +import dev.fitko.fitconnect.client.sender.steps.encrypted.EncryptedDestinationStep; +import dev.fitko.fitconnect.client.sender.steps.encrypted.EncryptedMetadataStep; +import dev.fitko.fitconnect.client.sender.steps.encrypted.EncryptedServiceTypeStep; +import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +@Getter +public class SendableEncryptedSubmission { + + private final UUID destinationId; + private final List<EncryptedAttachment> attachments; + private final String data; + private final String metadata; + private final String serviceName; + private final String serviceIdentifier; + + private SendableEncryptedSubmission(final Builder builder) { + destinationId = builder.getDestinationId(); + attachments = Collections.unmodifiableList(builder.getEncryptedAttachments()); + data = builder.getEncryptedData(); + metadata = builder.getEncryptedMetadata(); + serviceName = builder.getServiceName(); + serviceIdentifier = builder.getServiceIdentifier(); + } + + public static EncryptedDestinationStep Builder() { + return new Builder(); + } + + @Getter + private static final class Builder implements EncryptedDestinationStep, + EncryptedServiceTypeStep, + EncryptedMetadataStep, + EncryptedDataStep, + EncryptedAttachmentsStep, + EncryptedBuildStep { + + private static final Logger LOGGER = LoggerFactory.getLogger(Builder.class); + + private UUID destinationId; + private String serviceName; + private String serviceIdentifier; + private String encryptedData; + private String encryptedMetadata; + private final List<EncryptedAttachment> encryptedAttachments = new ArrayList<>(); + + private Builder() { + } + + @Override + public EncryptedServiceTypeStep setDestination(final UUID destinationId) { + this.destinationId = destinationId; + return this; + } + + @Override + public EncryptedAttachmentsStep addEncryptedAttachments(final List<EncryptedAttachment> attachments) { + if (attachments != null && !attachments.isEmpty()) { + encryptedAttachments.addAll(attachments); + } + return this; + } + + @Override + public EncryptedAttachmentsStep addEncryptedAttachment(final EncryptedAttachment attachment) { + if(attachment != null){ + encryptedAttachments.add(attachment); + } + return this; + } + + @Override + public EncryptedAttachmentsStep setEncryptedData(final String data) { + encryptedData = data; + return this; + } + + @Override + public EncryptedDataStep setEncryptedMetadata(final String encryptedMetadata) { + this.encryptedMetadata = encryptedMetadata; + return this; + } + + @Override + public EncryptedMetadataStep setServiceType(final String serviceIdentifier, final String serviceName) { + this.serviceIdentifier = serviceIdentifier; + this.serviceName = serviceName; + return this; + } + + @Override + public SendableEncryptedSubmission build() { + return new SendableEncryptedSubmission(this); + } + } +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/model/SendableSubmission.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/model/SendableSubmission.java new file mode 100644 index 0000000000000000000000000000000000000000..49b1dd1cda4d96ec64c4967a5cdc5f246ef3a626 --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/model/SendableSubmission.java @@ -0,0 +1,146 @@ +package dev.fitko.fitconnect.client.sender.model; + +import dev.fitko.fitconnect.api.domain.model.metadata.AuthenticationInformation; +import dev.fitko.fitconnect.api.domain.model.metadata.payment.PaymentInformation; +import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel; +import dev.fitko.fitconnect.client.sender.steps.unencrypted.BuildStep; +import dev.fitko.fitconnect.client.sender.steps.unencrypted.DataStep; +import dev.fitko.fitconnect.client.sender.steps.unencrypted.DestinationStep; +import dev.fitko.fitconnect.client.sender.steps.unencrypted.OptionalPropertiesStep; +import dev.fitko.fitconnect.client.sender.steps.unencrypted.ServiceTypeStep; +import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType.APPLICATION_JSON; +import static dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType.APPLICATION_XML; + +@Getter +public class SendableSubmission { + + private final UUID destinationId; + private final String data; + private final String dataMimeType; + private final List<Attachment> attachments; + private final String serviceName; + private final String serviceIdentifier; + private final List<AuthenticationInformation> authenticationInformation; + private final PaymentInformation paymentInformation; + private final ReplyChannel replyChannel; + + private SendableSubmission(final Builder builder) { + destinationId = builder.getDestinationId(); + data = builder.getData(); + dataMimeType = builder.getDataMimeType(); + attachments = Collections.unmodifiableList(builder.getAttachments()); + serviceName = builder.getServiceName(); + serviceIdentifier = builder.getServiceIdentifier(); + authenticationInformation = builder.getAuthenticationInformation(); + replyChannel = builder.getReplyChannel(); + paymentInformation = builder.getPaymentInformation(); + } + + public static DestinationStep Builder() { + return new Builder(); + } + + @Getter + private static final class Builder implements DestinationStep, + ServiceTypeStep, + DataStep, + OptionalPropertiesStep, + BuildStep { + + private static final Logger LOGGER = LoggerFactory.getLogger(Builder.class); + + private UUID destinationId; + private final List<Attachment> attachments = new ArrayList<>(); + private String data; + private String dataMimeType; + + private String serviceName; + + private String serviceIdentifier; + + private List<AuthenticationInformation> authenticationInformation; + + private PaymentInformation paymentInformation; + + private ReplyChannel replyChannel; + + private Builder() { + } + + @Override + public ServiceTypeStep setDestination(final UUID destinationId) { + this.destinationId = destinationId; + return this; + } + + @Override + public OptionalPropertiesStep addAttachments(final List<Attachment> attachments) { + if (attachments != null && !attachments.isEmpty()) { + this.attachments.addAll(attachments); + } + return this; + } + + @Override + public OptionalPropertiesStep addAttachment(final Attachment attachment) { + if (attachment != null) { + attachments.add(attachment); + } + return this; + } + + @Override + public OptionalPropertiesStep setReplyChannel(final ReplyChannel replyChannel) { + this.replyChannel = replyChannel; + return this; + } + + @Override + public OptionalPropertiesStep setJsonData(final String jsonData) { + data = jsonData; + dataMimeType = APPLICATION_JSON.value(); + return this; + } + + + @Override + public OptionalPropertiesStep setXmlData(final String xmlData) { + data = xmlData; + dataMimeType = APPLICATION_XML.value(); + return this; + } + + @Override + public DataStep setServiceType(final String serviceIdentifier, final String serviceName) { + this.serviceIdentifier = serviceIdentifier; + this.serviceName = serviceName; + return this; + } + + @Override + public OptionalPropertiesStep setAuthenticationInformation(final List<AuthenticationInformation> authenticationInformation) { + this.authenticationInformation = authenticationInformation; + return this; + } + + @Override + public OptionalPropertiesStep setPaymentInformation(final PaymentInformation paymentInformation) { + this.paymentInformation = paymentInformation; + return this; + } + + @Override + public SendableSubmission build() { + return new SendableSubmission(this); + } + } +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/model/SubmissionPayload.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/model/SubmissionPayload.java deleted file mode 100644 index fc7956049bd99f805db90ea84834cf161cf16337..0000000000000000000000000000000000000000 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/model/SubmissionPayload.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.fitko.fitconnect.client.sender.model; - -import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; -import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; -import dev.fitko.fitconnect.client.sender.SubmissionBuilder; -import lombok.Getter; - -import java.io.File; -import java.net.URI; -import java.util.List; -import java.util.UUID; - -@Getter -public class SubmissionPayload { - - private final UUID destinationId; - private final String data; - private final MimeType dataMimeType; - private final List<File> attachments; - private final ServiceType serviceType; - private final URI schemaUri; - - public SubmissionPayload(final SubmissionBuilder builder) { - destinationId = builder.getDestinationId(); - data = builder.getData(); - dataMimeType = builder.getDataMimeType(); - attachments = builder.getAttachments(); - serviceType = builder.getServiceType(); - schemaUri = builder.getSchemaUri(); - } -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/BuildStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/BuildStep.java deleted file mode 100644 index 2ecd116d6e5616c808a692fbddb98fd894ea178b..0000000000000000000000000000000000000000 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/BuildStep.java +++ /dev/null @@ -1,8 +0,0 @@ -package dev.fitko.fitconnect.client.sender.steps; - -import dev.fitko.fitconnect.client.sender.model.SubmissionPayload; - -public interface BuildStep { - - SubmissionPayload build(); -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/BuilderStartStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/BuilderStartStep.java index 2f62d688771242f858f11cbd0d9d787285464f0c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/BuilderStartStep.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/BuilderStartStep.java @@ -1,44 +0,0 @@ -package dev.fitko.fitconnect.client.sender.steps; - -import java.io.File; -import java.net.URI; -import java.util.List; - -public interface BuilderStartStep { - - /** - * Sends the submission with a list of attachments - * - * @param attachments that are sent with the submission - * @return the next step where additional data can be added to the submission - */ - DataStep withAttachments(List<File> attachments); - - - /** - * Sends the submission with an attachments - * - * @param attachment that is sent with the submission - * @return the next step where additional data can be added to the submission - */ - DataStep withAttachment(File attachment); - - - /** - * JSON data as string. - * - * @param data json string - * @param schemaUri schema URI indicating which schema the subscriber should use when parsing the data - * @return next step to submit the data - */ - DestinationStep withJsonData(String data, URI schemaUri); - - /** - * XML data as string. - * - * @param data xml string - * @param schemaUri schema URI indicating which schema the subscriber should use when parsing the data - * @return next step to submit the data - */ - DestinationStep withXmlData(String data, URI schemaUri); -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedBuildStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedBuildStep.java deleted file mode 100644 index 9f1c76796f8c58e0a9d174b09aeff070249ebb87..0000000000000000000000000000000000000000 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedBuildStep.java +++ /dev/null @@ -1,8 +0,0 @@ -package dev.fitko.fitconnect.client.sender.steps; - -import dev.fitko.fitconnect.client.sender.model.EncryptedSubmissionPayload; - -public interface EncryptedBuildStep { - - EncryptedSubmissionPayload build(); -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedBuilderStartStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedBuilderStartStep.java deleted file mode 100644 index 478e9574ddef6a86ca90359042e1d3f6b38d1df8..0000000000000000000000000000000000000000 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedBuilderStartStep.java +++ /dev/null @@ -1,33 +0,0 @@ -package dev.fitko.fitconnect.client.sender.steps; - -import java.util.Map; -import java.util.UUID; - -public interface EncryptedBuilderStartStep { - - /** - * Sends the submission with a list of already encrypted attachments - * - * @param encryptedAttachments map of encrypted attachments on attachment UUIDs - * - * @return the next step where additional encrypted data can be added to the submission - */ - EncryptedDataStep withEncryptedAttachments(Map<UUID, String> encryptedAttachments); - - /** - * Sends the submission with an attachments - * - * @param attachmentId unique identifier attachment that is uploaded - * @param attachment encrypted attachment that is sent with the submission - * @return the next step where additional data can be added to the submission - */ - EncryptedDataStep withEncryptedAttachment(UUID attachmentId, String attachment); - - /** - * Data as encrypted JWE string. - * - * @param encryptedData encrypted data as string - * @return next step to submit the data - */ - EncryptedMetadataStep withEncryptedData(String encryptedData); -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedDataStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedDataStep.java deleted file mode 100644 index ed7072728a0f0472609d71491435f9045be65653..0000000000000000000000000000000000000000 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedDataStep.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.fitko.fitconnect.client.sender.steps; - -public interface EncryptedDataStep { - - /** - * Data as encrypted JWE string. - * - * @param encryptedData encrypted data as string - * @return next step to add encrypted metadata - */ - EncryptedMetadataStep withEncryptedData(String encryptedData); -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedMetadataStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedMetadataStep.java deleted file mode 100644 index c60a0ef598d54037ecd66767692f011b21d2a873..0000000000000000000000000000000000000000 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedMetadataStep.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.fitko.fitconnect.client.sender.steps; - -public interface EncryptedMetadataStep { - - /** - * JSON data as encrypted JWE string. - * - * @param encryptedMetadata encrypted JWE string of the metadata - * @return next step to add a destination - */ - EncryptedDestinationStep withEncryptedMetadata(String encryptedMetadata); -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedServiceTypeStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedServiceTypeStep.java deleted file mode 100644 index 486f5b7a68cba766d21ccd0125a74df908749ab5..0000000000000000000000000000000000000000 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedServiceTypeStep.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.fitko.fitconnect.client.sender.steps; - -import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; - -public interface EncryptedServiceTypeStep { - - /** - * Sets a {@link ServiceType} for a submission - * - * @param name a name of the service - * @param leikaKey unique identifier of the service in the form <b>urn:de:fim:leika:leistung:</b> - * @return next step to add attachments - * @see <a href="https://fimportal.de/">Search For Service Types</a> - */ - EncryptedBuildStep withServiceType(String name, String leikaKey); - - /** - * Sets a {@link ServiceType} for a submission - * - * @param name a name of the service - * @param description description of the service - * @param leikaKey unique identifier of the service in the form <b>urn:de:fim:leika:leistung:</b> - * @return next step to add attachments - * @see <a href="https://fimportal.de/">Search For Service Types</a> - */ - EncryptedBuildStep withServiceType(String name, String description, String leikaKey); -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/ServiceTypeStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/ServiceTypeStep.java deleted file mode 100644 index 3ae77ef43749ae413fe7b2ee38b513f0e2004d15..0000000000000000000000000000000000000000 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/ServiceTypeStep.java +++ /dev/null @@ -1,27 +0,0 @@ -package dev.fitko.fitconnect.client.sender.steps; - -import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; - -public interface ServiceTypeStep { - - /** - * Sets a {@link ServiceType} for a submission - * - * @param name a name of the service - * @param leikaKey unique identifier of the service in the form <b>urn:de:fim:leika:leistung:</b> - * @return next step to finish the build - * @see <a href="https://fimportal.de/">Search For Service Types</a> - */ - BuildStep withServiceType(String name, String leikaKey); - - /** - * Sets a {@link ServiceType} for a submission - * - * @param name a name of the service - * @param description description of the service - * @param leikaKey unique identifier of the service in the form <b>urn:de:fim:leika:leistung:</b> - * @return next step to finish the build - * @see <a href="https://fimportal.de/">Search For Service Types</a> - */ - BuildStep withServiceType(String name, String description, String leikaKey); -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedAttachmentsStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedAttachmentsStep.java new file mode 100644 index 0000000000000000000000000000000000000000..9da1be1b847678f4c327c97a7b5ff5141b722158 --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedAttachmentsStep.java @@ -0,0 +1,30 @@ +package dev.fitko.fitconnect.client.sender.steps.encrypted; + +import dev.fitko.fitconnect.client.sender.model.EncryptedAttachment; +import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public interface EncryptedAttachmentsStep { + + /** + * Add a list of encrypted attachments to the submission to send. + * + * @param attachments list of encrypted attachments + * + * @return the next step where additional encrypted data can be added to the submission + */ + EncryptedAttachmentsStep addEncryptedAttachments(List<EncryptedAttachment> attachments); + + /** + * Add an encrypted attachment to the submission to send. + * + * @param attachment encrypted attachment to add + * @return the next step where additional data can be added to the submission + */ + EncryptedAttachmentsStep addEncryptedAttachment(EncryptedAttachment attachment); + + SendableEncryptedSubmission build(); +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedBuildStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedBuildStep.java new file mode 100644 index 0000000000000000000000000000000000000000..81cf0ffb415443be0f4e2ad1e09471507e060ca4 --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedBuildStep.java @@ -0,0 +1,8 @@ +package dev.fitko.fitconnect.client.sender.steps.encrypted; + +import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission; + +public interface EncryptedBuildStep { + + SendableEncryptedSubmission build(); +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedDataStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedDataStep.java new file mode 100644 index 0000000000000000000000000000000000000000..4001e3bf51159b0090f0ab891367b074ad41930e --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedDataStep.java @@ -0,0 +1,12 @@ +package dev.fitko.fitconnect.client.sender.steps.encrypted; + +public interface EncryptedDataStep { + + /** + * Data as encrypted JWE string. + * + * @param data encrypted data as string + * @return next step to add encrypted attachments + */ + EncryptedAttachmentsStep setEncryptedData(String data); +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedDestinationStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedDestinationStep.java similarity index 70% rename from client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedDestinationStep.java rename to client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedDestinationStep.java index 11f1eb1023868a181728e436947eab30ffdd8727..f936b294b16e0e35b68af3777e25bbb857928d6b 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/EncryptedDestinationStep.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedDestinationStep.java @@ -1,4 +1,4 @@ -package dev.fitko.fitconnect.client.sender.steps; +package dev.fitko.fitconnect.client.sender.steps.encrypted; import java.util.UUID; @@ -10,5 +10,5 @@ public interface EncryptedDestinationStep { * @param destinationId unique identifier of the clients destination * @return the upload step for attachments */ - EncryptedServiceTypeStep withDestination(UUID destinationId); + EncryptedServiceTypeStep setDestination(UUID destinationId); } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedMetadataStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedMetadataStep.java new file mode 100644 index 0000000000000000000000000000000000000000..ce2c52494d6949895856a9d65a81cc3a71114f1a --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedMetadataStep.java @@ -0,0 +1,12 @@ +package dev.fitko.fitconnect.client.sender.steps.encrypted; + +public interface EncryptedMetadataStep { + + /** + * JSON data as encrypted JWE string. + * + * @param metadata encrypted JWE string of the metadata + * @return next step to add encrypted data + */ + EncryptedDataStep setEncryptedMetadata(String metadata); +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedServiceTypeStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedServiceTypeStep.java new file mode 100644 index 0000000000000000000000000000000000000000..c58f3e1598721ec6a7b9f558067b6481b392a621 --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/encrypted/EncryptedServiceTypeStep.java @@ -0,0 +1,16 @@ +package dev.fitko.fitconnect.client.sender.steps.encrypted; + +import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; + +public interface EncryptedServiceTypeStep { + + /** + * Sets a {@link ServiceType} for a submission + * + * @param leikaKey unique identifier of the service in the form <b>urn:de:fim:leika:leistung:</b> + * @param name a name of the service + * @return next step to add encrypted metadata + * @see <a href="https://fimportal.de/">Search For Service Types</a> + */ + EncryptedMetadataStep setServiceType(String leikaKey, String name); +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/BuildStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/BuildStep.java new file mode 100644 index 0000000000000000000000000000000000000000..5ac27408c87c9970947ede5e74333e378dd5614f --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/BuildStep.java @@ -0,0 +1,8 @@ +package dev.fitko.fitconnect.client.sender.steps.unencrypted; + +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; + +public interface BuildStep { + + SendableSubmission build(); +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/DataStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/DataStep.java similarity index 72% rename from client/src/main/java/dev/fitko/fitconnect/client/sender/steps/DataStep.java rename to client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/DataStep.java index 533a07c9bf084d1b873bc84e04f22013e172ed16..872b202baebd44fa6e3bcfab629b6deba695e7bb 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/DataStep.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/DataStep.java @@ -1,4 +1,4 @@ -package dev.fitko.fitconnect.client.sender.steps; +package dev.fitko.fitconnect.client.sender.steps.unencrypted; import java.net.URI; @@ -11,7 +11,7 @@ public interface DataStep { * @param schemaUri schema URI indicating which schema the subscriber should use when parsing the data * @return next step to submit the data */ - DestinationStep withJsonData(String data, URI schemaUri); + OptionalPropertiesStep setJsonData(String data, URI schemaUri); /** * XML data as string. @@ -20,5 +20,5 @@ public interface DataStep { * @param schemaUri schema URI indicating which schema the subscriber should use when parsing the data * @return next step to submit the data */ - DestinationStep withXmlData(String data, URI schemaUri); + OptionalPropertiesStep setXmlData(String data, URI schemaUri); } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/DestinationStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/DestinationStep.java similarity index 70% rename from client/src/main/java/dev/fitko/fitconnect/client/sender/steps/DestinationStep.java rename to client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/DestinationStep.java index 37874139f3dd0f887e93f4d41257d203fb610857..aeffe872265beb74427902dc49b2c380d2d787a7 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/DestinationStep.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/DestinationStep.java @@ -1,4 +1,4 @@ -package dev.fitko.fitconnect.client.sender.steps; +package dev.fitko.fitconnect.client.sender.steps.unencrypted; import java.util.UUID; @@ -10,5 +10,5 @@ public interface DestinationStep { * @param destinationId unique identifier of the clients destination * @return the upload step for attachments */ - ServiceTypeStep withDestination(UUID destinationId); + ServiceTypeStep setDestination(UUID destinationId); } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/OptionalPropertiesStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/OptionalPropertiesStep.java new file mode 100644 index 0000000000000000000000000000000000000000..014d8e47bff914d8a17465bd96f2522d9332cd42 --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/OptionalPropertiesStep.java @@ -0,0 +1,48 @@ +package dev.fitko.fitconnect.client.sender.steps.unencrypted; + +import dev.fitko.fitconnect.api.domain.model.metadata.AuthenticationInformation; +import dev.fitko.fitconnect.api.domain.model.metadata.payment.PaymentInformation; +import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel; +import dev.fitko.fitconnect.client.sender.model.Attachment; +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; + +import java.util.List; + +public interface OptionalPropertiesStep { + + /** + * Sends the submission with a list of attachments + * + * @param attachments that are sent with the submission + * @return the next step where additional data can be added to the submission + */ + OptionalPropertiesStep addAttachments(List<Attachment> attachments); + + /** + * Sends the submission with an attachments + * + * @param attachment that is sent with the submission + * @return the next step where additional data can be added to the submission + */ + OptionalPropertiesStep addAttachment(Attachment attachment); + + /** + * Add a reply channel. The {@link ReplyChannel} object provides static factories for all available options: + * <ul> + * <li>ReplyChannel.fromEmail(...)</li> + * <li>ReplyChannel.fromDeMail(...)</li> + * <li>ReplyChannel.fromFink(...)</li> + * <li>ReplyChannel.fromElster(...)</li> + *</ul> + * @see <a href="https://docs.fitko.de/fit-connect/docs/metadata/replyChannel#r%C3%BCckkanal-replychannel">FIT-Connect documentation on reply channels</a> + * @param replyChannel a configured reply channel + * @return next step to set more additional properties + */ + OptionalPropertiesStep setReplyChannel(ReplyChannel replyChannel); + + OptionalPropertiesStep setAuthenticationInformation(List<AuthenticationInformation> authenticationInformation); + + OptionalPropertiesStep setPaymentInformation(PaymentInformation paymentInformation); + + SendableSubmission build(); +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/ServiceTypeStep.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/ServiceTypeStep.java new file mode 100644 index 0000000000000000000000000000000000000000..a628f6d7de89f23a3134a2826e620845cdf11294 --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/steps/unencrypted/ServiceTypeStep.java @@ -0,0 +1,16 @@ +package dev.fitko.fitconnect.client.sender.steps.unencrypted; + +import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; + +public interface ServiceTypeStep { + + /** + * Sets a {@link ServiceType} for a submission + * + * @param serviceIdentifier unique identifier of the service in the form <b>urn:de:fim:leika:leistung:</b> + * @param serviceName a name of the service + * @return next step to add attachments + * @see <a href="https://fimportal.de/">Search For Service Types</a> + */ + DataStep setServiceType(String serviceIdentifier, String serviceName); +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/strategies/SendEncryptedSubmissionStrategy.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/strategies/SendEncryptedSubmissionStrategy.java index b1dec39424d25c1c48a554f60c66a3c7df176caa..1fbd05c104c17820739fe0319ddbbeeb8603e097 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/strategies/SendEncryptedSubmissionStrategy.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/strategies/SendEncryptedSubmissionStrategy.java @@ -1,24 +1,24 @@ package dev.fitko.fitconnect.client.sender.strategies; +import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.submission.CreateSubmission; import dev.fitko.fitconnect.api.domain.model.submission.SentSubmission; -import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.SubmitSubmission; import dev.fitko.fitconnect.api.exceptions.RestApiException; import dev.fitko.fitconnect.api.exceptions.SubmissionNotCreatedException; import dev.fitko.fitconnect.api.services.Sender; -import dev.fitko.fitconnect.client.sender.model.AttachmentPayload; -import dev.fitko.fitconnect.client.sender.model.EncryptedSubmissionPayload; +import dev.fitko.fitconnect.client.sender.model.EncryptedAttachment; +import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission; import dev.fitko.fitconnect.core.util.StopWatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; -import java.util.Map; import java.util.UUID; -import java.util.stream.Collectors; +import static dev.fitko.fitconnect.client.util.SubmissionUtil.buildAuthenticationTags; +import static dev.fitko.fitconnect.client.util.SubmissionUtil.buildAuthenticationTagsForEncryptedSubmission; import static dev.fitko.fitconnect.client.util.SubmissionUtil.buildCreateSubmission; import static dev.fitko.fitconnect.client.util.SubmissionUtil.buildSentSubmission; import static dev.fitko.fitconnect.client.util.SubmissionUtil.buildSubmitSubmission; @@ -33,15 +33,19 @@ public class SendEncryptedSubmissionStrategy { this.sender = sender; } - public SentSubmission send(final EncryptedSubmissionPayload submissionPayload) { + public SentSubmission send(final SendableEncryptedSubmission submissionPayload) { try { - final List<AttachmentPayload> attachmentPayloads = toAttachmentPayloads(submissionPayload.getEncryptedAttachments()); - final UUID submissionId = announceNewSubmission(submissionPayload, attachmentPayloads); - final SubmitSubmission submitSubmission = buildSubmitSubmission(submissionId, submissionPayload.getEncryptedData(), submissionPayload.getEncryptedMetadata()); + + final List<EncryptedAttachment> encryptedAttachments = submissionPayload.getAttachments(); + final String encryptedData = submissionPayload.getData(); + final String encryptedMetadata = submissionPayload.getMetadata(); + + final UUID submissionId = announceNewSubmission(submissionPayload, encryptedAttachments); + final SubmitSubmission submitSubmission = buildSubmitSubmission(submissionId, encryptedData, encryptedMetadata); final var startTimeAttachmentUpload = StopWatch.start(); - uploadAttachments(attachmentPayloads, submissionId); + uploadAttachments(encryptedAttachments, submissionId); LOGGER.info("Uploading attachments took {}", StopWatch.stopWithFormattedTime(startTimeAttachmentUpload)); final var startTimeSubmissionUpload = StopWatch.start(); @@ -49,7 +53,8 @@ public class SendEncryptedSubmissionStrategy { LOGGER.info("Uploading submission took {}", StopWatch.stopWithFormattedTime(startTimeSubmissionUpload)); LOGGER.info("SUCCESSFULLY HANDED IN SUBMISSION !"); - return buildSentSubmission(attachmentPayloads, submissionPayload.getEncryptedData(), submissionPayload.getEncryptedData(), submission); + final AuthenticationTags authenticationTags = buildAuthenticationTagsForEncryptedSubmission(encryptedAttachments, encryptedData, encryptedMetadata); + return buildSentSubmission(authenticationTags, submission); } catch (final RestApiException e) { LOGGER.error("Sending submission failed", e); @@ -60,29 +65,17 @@ public class SendEncryptedSubmissionStrategy { return null; } - private UUID announceNewSubmission(final EncryptedSubmissionPayload submissionPayload, final List<AttachmentPayload> attachmentPayloads) { - final UUID destinationId = submissionPayload.getDestinationId(); - final ServiceType serviceType = submissionPayload.getServiceType(); - final CreateSubmission submissionToAnnounce = buildCreateSubmission(destinationId, attachmentPayloads, serviceType); + private UUID announceNewSubmission(final SendableEncryptedSubmission submissionPayload, final List<EncryptedAttachment> encryptedAttachments) { + final CreateSubmission submissionToAnnounce = buildCreateSubmission(submissionPayload, encryptedAttachments); return sender.createSubmission(submissionToAnnounce).getSubmissionId(); } - private void uploadAttachments(final List<AttachmentPayload> attachmentPayloads, final UUID submissionId) { - if (attachmentPayloads.isEmpty()) { + private void uploadAttachments(final List<EncryptedAttachment> encryptedAttachments, final UUID submissionId) { + if (encryptedAttachments.isEmpty()) { LOGGER.info("No attachments to upload"); } else { - LOGGER.info("Uploading {} attachment(s)", attachmentPayloads.size()); - attachmentPayloads.forEach(a -> sender.uploadAttachment(submissionId, a.getAttachmentId(), a.getEncryptedData())); + LOGGER.info("Uploading {} attachment(s)", encryptedAttachments.size()); + encryptedAttachments.forEach(a -> sender.uploadAttachment(submissionId, a.getAttachmentId(), a.getContent())); } } - - private List<AttachmentPayload> toAttachmentPayloads(final Map<UUID, String> encryptedAttachments) { - return encryptedAttachments.entrySet() - .stream() - .map(attachment -> AttachmentPayload.builder() - .encryptedData(attachment.getValue()) - .attachmentId(attachment.getKey()) - .build()) - .collect(Collectors.toList()); - } } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/sender/strategies/SendNewSubmissionStrategy.java b/client/src/main/java/dev/fitko/fitconnect/client/sender/strategies/SendNewSubmissionStrategy.java index 664109153eb409b19e28c53657b6bb46f6cad3e0..bf94bdb6f2dbe8955d85fd6cd97c36c6e4f2c9ee 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/sender/strategies/SendNewSubmissionStrategy.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/sender/strategies/SendNewSubmissionStrategy.java @@ -3,12 +3,13 @@ package dev.fitko.fitconnect.client.sender.strategies; import com.nimbusds.jose.jwk.RSAKey; import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.domain.model.destination.DestinationService; +import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.metadata.ContentStructure; import dev.fitko.fitconnect.api.domain.model.metadata.Hash; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; import dev.fitko.fitconnect.api.domain.model.metadata.PublicServiceType; import dev.fitko.fitconnect.api.domain.model.metadata.SignatureType; -import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Purpose; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; @@ -27,28 +28,23 @@ import dev.fitko.fitconnect.api.exceptions.RestApiException; import dev.fitko.fitconnect.api.exceptions.SchemaNotFoundException; import dev.fitko.fitconnect.api.exceptions.SubmissionNotCreatedException; import dev.fitko.fitconnect.api.services.Sender; +import dev.fitko.fitconnect.client.sender.model.Attachment; import dev.fitko.fitconnect.client.sender.model.AttachmentPayload; -import dev.fitko.fitconnect.client.sender.model.SubmissionPayload; +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; import dev.fitko.fitconnect.core.util.StopWatch; -import org.apache.tika.Tika; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; import java.net.URI; -import java.net.URLConnection; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.UUID; - import java.util.stream.Collectors; import static dev.fitko.fitconnect.api.config.SchemaConfig.METADATA_V_1_0_0; +import static dev.fitko.fitconnect.client.util.SubmissionUtil.buildAuthenticationTags; import static dev.fitko.fitconnect.client.util.SubmissionUtil.buildCreateSubmission; import static dev.fitko.fitconnect.client.util.SubmissionUtil.buildSentSubmission; import static dev.fitko.fitconnect.client.util.SubmissionUtil.buildSubmitSubmission; @@ -64,18 +60,17 @@ public class SendNewSubmissionStrategy { this.sender = sender; } - public SentSubmission send(final SubmissionPayload submissionPayload) { + public SentSubmission send(final SendableSubmission sendableSubmission) { - final UUID destinationId = submissionPayload.getDestinationId(); - final ServiceType serviceType = submissionPayload.getServiceType(); + final UUID destinationId = sendableSubmission.getDestinationId(); try { final RSAKey encryptionKey = sender.getEncryptionKeyForDestination(destinationId); final Destination destination = sender.getDestination(destinationId); - final List<AttachmentPayload> encryptedAttachments = encryptAndHashAttachments(encryptionKey, submissionPayload.getAttachments()); - final CreateSubmission newSubmission = buildCreateSubmission(destinationId, encryptedAttachments, serviceType); + final List<AttachmentPayload> encryptedAttachments = encryptAndHashAttachments(encryptionKey, sendableSubmission.getAttachments()); + final CreateSubmission newSubmission = buildCreateSubmission(sendableSubmission, encryptedAttachments); final SubmissionForPickup announcedSubmission = sender.createSubmission(newSubmission); final UUID announcedSubmissionId = announcedSubmission.getSubmissionId(); @@ -85,7 +80,7 @@ public class SendNewSubmissionStrategy { LOGGER.info("Uploading attachments took {}", StopWatch.stopWithFormattedTime(startTimeAttachmentUpload)); LOGGER.info("Creating metadata"); - final Metadata metadata = buildMetadata(submissionPayload, destination, encryptedAttachments); + final Metadata metadata = buildMetadata(sendableSubmission, destination, encryptedAttachments); final ValidationResult validatedMetadata = sender.validateMetadata(metadata); if (validatedMetadata.hasError()) { @@ -93,7 +88,7 @@ public class SendNewSubmissionStrategy { return null; } - final String encryptedData = sender.encryptBytes(encryptionKey, submissionPayload.getData().getBytes(StandardCharsets.UTF_8)); + final String encryptedData = sender.encryptBytes(encryptionKey, sendableSubmission.getData().getBytes(StandardCharsets.UTF_8)); final String encryptedMetadata = sender.encryptObject(encryptionKey, metadata); final var startTimeSubmissionUpload = StopWatch.start(); @@ -101,7 +96,8 @@ public class SendNewSubmissionStrategy { LOGGER.info("Uploading submission took {}", StopWatch.stopWithFormattedTime(startTimeSubmissionUpload)); LOGGER.info("SUCCESSFULLY HANDED IN SUBMISSION !"); - return buildSentSubmission(encryptedAttachments, encryptedData, encryptedMetadata, submission); + final AuthenticationTags authenticationTags = buildAuthenticationTags(encryptedAttachments, encryptedData, encryptedMetadata); + return buildSentSubmission(authenticationTags, submission); } catch (final EncryptionException e) { LOGGER.error("Encrypting submission failed", e); @@ -122,13 +118,13 @@ public class SendNewSubmissionStrategy { return null; } - private Metadata buildMetadata(final SubmissionPayload submissionPayload, final Destination destination, final List<AttachmentPayload> encryptedAttachments) { - final String hashedData = sender.createHash(submissionPayload.getData().getBytes(StandardCharsets.UTF_8)); + private Metadata buildMetadata(final SendableSubmission sendableSubmission, final Destination destination, final List<AttachmentPayload> encryptedAttachments) { + final String hashedData = sender.createHash(sendableSubmission.getData().getBytes(StandardCharsets.UTF_8)); - final MimeType dataMimeType = submissionPayload.getDataMimeType(); - final Data data = createData(dataMimeType, hashedData, submissionPayload.getSchemaUri()); - final PublicServiceType publicServiceType = buildPublicServiceType(submissionPayload.getServiceType()); - final List<Attachment> attachmentMetadata = encryptedAttachments.stream().map(this::toHashedAttachment).collect(Collectors.toList()); + final String dataMimeType = sendableSubmission.getDataMimeType(); + final URI schemaUri = getSubmissionSchemaFromDestination(destination, dataMimeType); + final Data data = createData(dataMimeType, hashedData, , submissionPayload.getSchemaUri()); + final List<ApiAttachment> attachmentMetadata = encryptedAttachments.stream().map(this::toHashedAttachment).collect(Collectors.toList()); final var contentStructure = new ContentStructure(); contentStructure.setAttachments(attachmentMetadata); @@ -137,18 +133,31 @@ public class SendNewSubmissionStrategy { final var metadata = new Metadata(); metadata.setSchema(METADATA_V_1_0_0.toString()); metadata.setContentStructure(contentStructure); - metadata.setPublicServiceType(publicServiceType); + + setOptionalProperties(sendableSubmission, metadata); return metadata; } - private Data createData(final MimeType mimeType, final String hashedData, final URI schemaUri) { + private void setOptionalProperties(final SendableSubmission sendableSubmission, final Metadata metadata) { + if(sendableSubmission.getAuthenticationInformation() != null){ + metadata.setAuthenticationInformation(sendableSubmission.getAuthenticationInformation()); + } + if(sendableSubmission.getReplyChannel() != null){ + metadata.setReplyChannel(sendableSubmission.getReplyChannel()); + } + if(sendableSubmission.getPaymentInformation() != null){ + metadata.setPaymentInformation(sendableSubmission.getPaymentInformation()); + } + } + + private Data createData(final String mimeType, final String hashedData, final URI schemaUri) { final var hash = new Hash(); hash.setContent(hashedData); hash.setSignatureType(SignatureType.SHA_512); final var submissionSchema = new SubmissionSchema(); - submissionSchema.setMimeType(mimeType); + submissionSchema.setMimeType(MimeType.fromValue(mimeType)); submissionSchema.setSchemaUri(schemaUri); final var data = new Data(); @@ -157,24 +166,13 @@ public class SendNewSubmissionStrategy { return data; } - private PublicServiceType buildPublicServiceType(final ServiceType serviceType) { - final var publicServiceType = new PublicServiceType(); - publicServiceType.setIdentifier(serviceType.getIdentifier()); - if (serviceType.getName() != null) { - publicServiceType.setName(serviceType.getName()); - } - if (serviceType.getDescription() != null) { - publicServiceType.setDescription(serviceType.getDescription()); - } - return publicServiceType; - } - - private Attachment toHashedAttachment(final AttachmentPayload attachmentPayload) { - final var attachment = new Attachment(); + private ApiAttachment toHashedAttachment(final AttachmentPayload attachmentPayload) { + final var attachment = new ApiAttachment(); attachment.setAttachmentId(attachmentPayload.getAttachmentId()); attachment.setPurpose(Purpose.ATTACHMENT); - attachment.setFilename(attachmentPayload.getFile().getName()); + attachment.setFilename(attachmentPayload.getFileName()); attachment.setMimeType(attachmentPayload.getMimeType()); + attachment.setDescription(attachmentPayload.getDescription()); final var hash = new Hash(); hash.setContent(attachmentPayload.getHashedData()); @@ -192,38 +190,27 @@ public class SendNewSubmissionStrategy { } } - private List<AttachmentPayload> encryptAndHashAttachments(final RSAKey encryptionKey, final List<File> attachments) { + private List<AttachmentPayload> encryptAndHashAttachments(final RSAKey encryptionKey, final List<Attachment> attachments) { return attachments.stream() - .map(file -> AttachmentPayload.builder().file(file).attachmentId(UUID.randomUUID()).build()) .filter(Objects::nonNull) - .map(payload -> encryptAndHashAttachment(encryptionKey, payload)) + .map(attachment -> encryptAndHashAttachment(encryptionKey, attachment)) .collect(Collectors.toList()); } - private AttachmentPayload encryptAndHashAttachment(final RSAKey encryptionKey, final AttachmentPayload attachmentPayload) { - final File file = attachmentPayload.getFile(); - try { - final byte[] rawData = Files.readAllBytes(Paths.get(file.getPath())); - final String encryptedAttachment = sender.encryptBytes(encryptionKey, rawData); - final String hashedBytes = sender.createHash(rawData); - return attachmentPayload.withEncryptedData(encryptedAttachment) - .withHashedData(hashedBytes) - .withMimeType(detectMimeTypeFromFile(attachmentPayload)); - } catch (final Exception e) { - throw new AttachmentCreationException("Attachment '" + file.getAbsolutePath() + "' could not be created ", e); - } - } - - private String detectMimeTypeFromFile(final AttachmentPayload attachmentPayload) { - final File file = attachmentPayload.getFile(); - String mimeType; + private AttachmentPayload encryptAndHashAttachment(final RSAKey encryptionKey, final Attachment attachment) { try { - final Tika mimeTypeGuesser = new Tika(); - mimeType = mimeTypeGuesser.detect(file); - } catch (final IOException e) { - mimeType = URLConnection.guessContentTypeFromName(file.getName()); + final String encryptedAttachment = sender.encryptBytes(encryptionKey, attachment.getDataAsBytes()); + final String hashedBytes = sender.createHash(attachment.getDataAsBytes()); + return AttachmentPayload.builder() + .attachmentId(UUID.randomUUID()) + .hashedData(hashedBytes) + .encryptedData(encryptedAttachment) + .mimeType(attachment.getMimeType()) + .fileName(attachment.getFileName()) + .description(attachment.getDescription()) + .build(); + } catch (final EncryptionException e) { + throw new AttachmentCreationException("Attachment '" + attachment.getFileName() + "' could not be encrypted ", e); } - LOGGER.info("Detected attachment mime-type {}", mimeType); - return mimeType; } } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/subscriber/ReceivedSubmission.java b/client/src/main/java/dev/fitko/fitconnect/client/subscriber/ReceivedSubmission.java index 2af508e16012e5ee526cd895edb758ec353da83d..af264014b0c38f64960aba24113d38c44c09cf19 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/subscriber/ReceivedSubmission.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/subscriber/ReceivedSubmission.java @@ -4,14 +4,14 @@ import dev.fitko.fitconnect.api.domain.model.event.Event; import dev.fitko.fitconnect.api.domain.model.event.EventPayload; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; -import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.services.Subscriber; -import dev.fitko.fitconnect.client.subscriber.model.ReceivedAttachment; +import dev.fitko.fitconnect.client.sender.model.Attachment; import dev.fitko.fitconnect.client.subscriber.model.ReceivedData; import java.net.URI; import java.util.List; +import java.util.Map; import java.util.UUID; public class ReceivedSubmission { @@ -20,23 +20,27 @@ public class ReceivedSubmission { private final Submission submission; private final Metadata metadata; private final ReceivedData receivedData; - private final List<ReceivedAttachment> receivedAttachments; + private final List<Attachment> attachments; + private final Map<UUID, String> encryptedAttachments; - public ReceivedSubmission(final Subscriber subscriber, final Submission submission, final Metadata metadata, final ReceivedData receivedData, final List<ReceivedAttachment> receivedAttachments) { + public ReceivedSubmission(final Subscriber subscriber, final Submission submission, final Metadata metadata, final ReceivedData receivedData, final List<Attachment> attachments, final Map<UUID, String> encryptedAttachments) { this.subscriber = subscriber; this.submission = submission; this.metadata = metadata; this.receivedData = receivedData; - this.receivedAttachments = receivedAttachments; + this.attachments = attachments; + this.encryptedAttachments = encryptedAttachments; + } /** * Send an accept-submission {@link Event} to confirm that the handed in submission was accepted. * + * @param problems optional {@link Problem}s * @see <a href="https://docs.fitko.de/fit-connect/docs/getting-started/event-log/events">FIT-Connect Events</a> */ - public void acceptSubmission() { - subscriber.acceptSubmission(new EventPayload(submission)); + public void acceptSubmission(final Problem... problems) { + subscriber.acceptSubmission(EventPayload.forAcceptEventWithAttachments(submission, encryptedAttachments, problems)); } /** @@ -46,7 +50,7 @@ public class ReceivedSubmission { * @see <a href="https://docs.fitko.de/fit-connect/docs/getting-started/event-log/events">FIT-Connect Events</a> */ public void rejectSubmission(final List<Problem> rejectionProblems) { - subscriber.rejectSubmission(new EventPayload(submission, rejectionProblems)); + subscriber.rejectSubmission(EventPayload.forRejectEvent(submission, rejectionProblems)); } /** @@ -54,17 +58,17 @@ public class ReceivedSubmission { * * @return data as string */ - public String getData() { + public String getDataAsString() { return receivedData.getData(); } /** * Get data's mime-type. * - * @return {@link MimeType} + * @return mimetype string */ - public MimeType getDataMimeType() { - return receivedData.getMimeType(); + public String getDataMimeType() { + return receivedData.getMimeType().value(); } /** @@ -79,10 +83,10 @@ public class ReceivedSubmission { /** * Access list of decrypted attachments. * - * @return list of {@link ReceivedAttachment} + * @return list of {@link Attachment} */ - public List<ReceivedAttachment> getAttachments() { - return receivedAttachments; + public List<Attachment> getAttachments() { + return attachments; } /** @@ -90,7 +94,7 @@ public class ReceivedSubmission { * * @return metadata */ - public Metadata getSubmissionMetadata() { + public Metadata getMetadata() { return metadata; } @@ -120,4 +124,5 @@ public class ReceivedSubmission { public UUID getSubmissionId() { return submission.getSubmissionId(); } + } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/subscriber/SubmissionReceiver.java b/client/src/main/java/dev/fitko/fitconnect/client/subscriber/SubmissionReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..050f5c8d1186ac1759759f789fa2eb6570d2135e --- /dev/null +++ b/client/src/main/java/dev/fitko/fitconnect/client/subscriber/SubmissionReceiver.java @@ -0,0 +1,252 @@ +package dev.fitko.fitconnect.client.subscriber; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.RSAKey; +import dev.fitko.fitconnect.api.config.ApplicationConfig; +import dev.fitko.fitconnect.api.domain.model.event.Event; +import dev.fitko.fitconnect.api.domain.model.event.EventPayload; +import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; +import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; +import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.AttachmentEncryptionIssue; +import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.MissingAttachment; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataEncryptionIssue; +import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MetadataEncryptionIssue; +import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MetadataJsonSyntaxViolation; +import dev.fitko.fitconnect.api.domain.model.event.problems.submission.InvalidEventLog; +import dev.fitko.fitconnect.api.domain.model.event.problems.submission.MissingAuthenticationTags; +import dev.fitko.fitconnect.api.domain.model.event.problems.submission.NotExactlyOneSubmitEvent; +import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentForValidation; +import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; +import dev.fitko.fitconnect.api.domain.model.submission.Submission; +import dev.fitko.fitconnect.api.domain.validation.ValidationResult; +import dev.fitko.fitconnect.api.exceptions.AuthenticationTagsEmptyException; +import dev.fitko.fitconnect.api.exceptions.DecryptionException; +import dev.fitko.fitconnect.api.exceptions.EventLogException; +import dev.fitko.fitconnect.api.exceptions.RestApiException; +import dev.fitko.fitconnect.api.exceptions.SubmissionRequestException; +import dev.fitko.fitconnect.api.exceptions.SubmitEventNotFoundException; +import dev.fitko.fitconnect.api.services.Subscriber; +import dev.fitko.fitconnect.client.sender.model.Attachment; +import dev.fitko.fitconnect.client.subscriber.model.ReceivedData; +import dev.fitko.fitconnect.core.util.StopWatch; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; +import static com.nimbusds.jose.jwk.KeyOperation.UNWRAP_KEY; + +public class SubmissionReceiver { + + private static final Logger LOGGER = LoggerFactory.getLogger(SubmissionReceiver.class); + private static final ObjectMapper MAPPER = new ObjectMapper().configure(FAIL_ON_UNKNOWN_PROPERTIES, false); + + private final Subscriber subscriber; + private final ApplicationConfig config; + private final RSAKey privateKey; + + public SubmissionReceiver(final Subscriber subscriber, final RSAKey privateKey, final ApplicationConfig config) { + this.subscriber = subscriber; + this.privateKey = privateKey; + this.config = config; + } + + public ReceivedSubmission receiveSubmission(final UUID submissionId) { + + LOGGER.info("Requesting submission ..."); + final Submission submission = loadSubmission(submissionId); + + LOGGER.info("Loading authentication tags from event log ..."); + final AuthenticationTags authenticationTags = loadAuthTagsForSubmitEvent(submission); + + LOGGER.info("Decrypting metadata ..."); + final Metadata metadata = decryptMetadata(submission); + validateMetadata(metadata, submission, authenticationTags); + + LOGGER.info("Decrypting data ..."); + final byte[] decryptedData = decryptData(submission); + validateData(submission, metadata, decryptedData, authenticationTags); + + LOGGER.info("Loading and decrypting attachments ..."); + final List<AttachmentForValidation> attachments = loadAttachments(submission, metadata); + validateAttachments(attachments, submission, authenticationTags); + + return buildReceivedSubmission(submission, metadata, decryptedData, attachments); + } + + private Submission loadSubmission(final UUID submissionId) { + try { + final var startTimeDownloadSubmission = StopWatch.start(); + final Submission submission = subscriber.getSubmission(submissionId); + LOGGER.info("Downloading submission took {}", StopWatch.stopWithFormattedTime(startTimeDownloadSubmission)); + return submission; + } catch (final RestApiException e) { + throw new SubmissionRequestException(e.getMessage(), e); + } + } + + private AuthenticationTags loadAuthTagsForSubmitEvent(final Submission submission) { + try { + return subscriber.getAuthenticationTagsForEvent(Event.SUBMIT, submission); + } catch (final EventLogException e) { + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#struktur--und-signaturpr%C3%BCfung-der-security-event-tokens + final InvalidEventLog problem = new InvalidEventLog(); + rejectSubmissionWithProblem(submission, problem); + throw new SubmissionRequestException(problem.getDetail(), e); + } catch (final SubmitEventNotFoundException e) { + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#genau-ein-submit-event + final NotExactlyOneSubmitEvent problem = new NotExactlyOneSubmitEvent(); + rejectSubmissionWithProblem(submission, problem); + throw new SubmissionRequestException(problem.getDetail()); + } catch (final AuthenticationTagsEmptyException e) { + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tags-im-submit-event + final MissingAuthenticationTags problem = new MissingAuthenticationTags(); + rejectSubmissionWithProblem(submission, problem); + throw new SubmissionRequestException(problem.getDetail()); + } + } + + private void validateMetadata(final Metadata metadata, final Submission submission, final AuthenticationTags authenticationTags) { + final ValidationResult validationResult = subscriber.validateMetadata(metadata, submission, authenticationTags); + evaluateValidationResult(submission, validationResult, "Metadata is invalid"); + } + + private void validateAttachments(final List<AttachmentForValidation> attachmentForValidation, final Submission submission, final AuthenticationTags authenticationTags) { + final ValidationResult validationResult = subscriber.validateAttachments(attachmentForValidation, authenticationTags); + evaluateValidationResult(submission, validationResult, "Attachment validation failed"); + } + + private void validateData(final Submission submission, final Metadata metadata, final byte[] decryptedData, final AuthenticationTags authenticationTags) { + final ValidationResult validationResult = subscriber.validateData(decryptedData, submission, metadata, authenticationTags); + evaluateValidationResult(submission, validationResult, "Data is invalid"); + } + + private void evaluateValidationResult(final Submission submission, final ValidationResult validationResult, final String errorMessage) throws SubmissionRequestException { + if (validationResult.hasProblems()) { + rejectSubmissionWithProblem(submission, validationResult.getProblems().toArray(new Problem[0])); + throw new SubmissionRequestException(validationResult.hasError() ? validationResult.getError().getMessage() : errorMessage); + } else if (validationResult.hasError()) { + LOGGER.error(validationResult.getError().getMessage(), validationResult.getError()); + throw new SubmissionRequestException(validationResult.getError().getMessage(), validationResult.getError()); + } + } + + private ReceivedSubmission buildReceivedSubmission(final Submission submission, final Metadata metadata, final byte[] decryptedData, final List<AttachmentForValidation> attachments) { + final MimeType mimeType = metadata.getContentStructure().getData().getSubmissionSchema().getMimeType(); + final ReceivedData receivedData = new ReceivedData(new String(decryptedData, StandardCharsets.UTF_8), mimeType); + final List<Attachment> receivedAttachments = attachments.stream().map(this::toAttachment).collect(Collectors.toList()); + final Map<UUID, String> encryptedAttachments = attachments.stream().collect(Collectors.toMap(AttachmentForValidation::getAttachmentId, AttachmentForValidation::getEncryptedData)); + return new ReceivedSubmission(subscriber, submission, metadata, receivedData, receivedAttachments, encryptedAttachments); + } + + private Attachment toAttachment(final AttachmentForValidation attachment) { + final ApiAttachment metadata = attachment.getAttachmentMetadata(); + return Attachment.fromByteArray(attachment.getDecryptedData(), metadata.getMimeType(), metadata.getFilename(), metadata.getDescription()); + } + + private List<AttachmentForValidation> loadAttachments(final Submission submission, final Metadata metadata) { + final List<ApiAttachment> attachments = metadata.getContentStructure().getAttachments(); + if (attachments == null || attachments.isEmpty()) { + LOGGER.info("Submission contains no attachments"); + return Collections.emptyList(); + } + final List<AttachmentForValidation> receivedAttachments = new ArrayList<>(); + for (final ApiAttachment attachmentMetadata : attachments) { + final String encryptedAttachment = downloadAttachment(submission, attachmentMetadata); + final byte[] decryptedAttachment = decryptAttachment(attachmentMetadata, encryptedAttachment, submission); + receivedAttachments.add(new AttachmentForValidation(attachmentMetadata, encryptedAttachment, decryptedAttachment)); + } + return receivedAttachments; + } + + private byte[] decryptAttachment(final ApiAttachment metadata, final String encryptedAttachment, final Submission submission) { + checkPrivateKey(); + try { + final var startDecryption = StopWatch.start(); + final byte[] decryptedAttachment = subscriber.decryptStringContent(privateKey, encryptedAttachment); + LOGGER.info("Decrypting attachment {} took {}", metadata.getAttachmentId(), StopWatch.stopWithFormattedTime(startDecryption)); + return decryptedAttachment; + } catch (final DecryptionException e) { + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#entschl%C3%BCsselung-2 + rejectSubmissionWithProblem(submission, new AttachmentEncryptionIssue(metadata.getAttachmentId())); + throw new SubmissionRequestException(e.getMessage(), e); + } + } + + private String downloadAttachment(final Submission submission, final ApiAttachment metadata) { + try { + final var startDownload = StopWatch.start(); + final String encryptedAttachment = subscriber.fetchAttachment(submission.getSubmissionId(), metadata.getAttachmentId()); + LOGGER.info("Downloading attachment {} took {}", metadata.getAttachmentId(), StopWatch.stopWithFormattedTime(startDownload)); + return encryptedAttachment; + } catch (final RestApiException e) { + if (e.getHttpStatus() != null && e.getHttpStatus().equals(HttpStatus.NOT_FOUND)) { + rejectSubmissionWithProblem(submission, new MissingAttachment(metadata.getAttachmentId())); + } + throw new SubmissionRequestException(e.getMessage(), e); + } + } + + private byte[] decryptData(final Submission submission) { + checkPrivateKey(); + try { + return subscriber.decryptStringContent(privateKey, submission.getEncryptedData()); + } catch (final DecryptionException e) { + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#entschl%C3%BCsselung-1 + rejectSubmissionWithProblem(submission, new DataEncryptionIssue()); + throw new SubmissionRequestException(e.getMessage(), e); + } + } + + private Metadata decryptMetadata(final Submission submission) { + checkPrivateKey(); + try { + final byte[] metadataBytes = subscriber.decryptStringContent(privateKey, submission.getEncryptedMetadata()); + return MAPPER.readValue(metadataBytes, Metadata.class); + } catch (final IOException e) { + rejectSubmissionWithProblem(submission, new MetadataJsonSyntaxViolation()); + throw new SubmissionRequestException(e.getMessage(), e); + } catch (final DecryptionException e) { + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#entschl%C3%BCsselung + rejectSubmissionWithProblem(submission, new MetadataEncryptionIssue()); + throw new SubmissionRequestException(e.getMessage(), e); + } + } + + private void checkPrivateKey() { + if (!isUnwrapKey(privateKey) && !isEncryptionKeyUse(privateKey)) { + throw new SubmissionRequestException("Private key is not suitable for decryption, could not find key_operation unwrapKey or key_use encryption"); + } + } + + private static boolean isEncryptionKeyUse(final RSAKey privateKey) { + return privateKey.getKeyUse() != null && privateKey.getKeyUse().equals(KeyUse.ENCRYPTION); + } + + private static boolean isUnwrapKey(final RSAKey privateKey) { + return privateKey.getKeyOperations() != null && privateKey.getKeyOperations().stream() + .anyMatch(keyOp -> keyOp.identifier().equals(UNWRAP_KEY.identifier())); + } + + private void rejectSubmissionWithProblem(final Submission submission, final Problem... problem) { + reject(submission, List.of(problem)); + } + + private void reject(final Submission submission, final List<Problem> problems) { + if (config.isEnableAutoReject()) { + subscriber.rejectSubmission(EventPayload.forRejectEvent(submission, problems)); + } + } +} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/subscriber/model/DecryptedAttachmentPayload.java b/client/src/main/java/dev/fitko/fitconnect/client/subscriber/model/DecryptedAttachmentPayload.java index 3162e2d24743fc5cf893dd38c2e21c22ea133853..f1fdf6b1627ef6b4d1d22ae43d7ec32a0ae53ed4 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/subscriber/model/DecryptedAttachmentPayload.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/subscriber/model/DecryptedAttachmentPayload.java @@ -1,6 +1,6 @@ package dev.fitko.fitconnect.client.subscriber.model; -import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; import lombok.Builder; import lombok.Value; @@ -8,5 +8,5 @@ import lombok.Value; @Builder public class DecryptedAttachmentPayload { byte[] decryptedContent; - Attachment attachmentMetadata; + ApiAttachment attachmentMetadata; } diff --git a/client/src/main/java/dev/fitko/fitconnect/client/subscriber/model/ReceivedAttachment.java b/client/src/main/java/dev/fitko/fitconnect/client/subscriber/model/ReceivedAttachment.java index 8f1a7e599679d4831cf3ae4b076002b13ac56286..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/subscriber/model/ReceivedAttachment.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/subscriber/model/ReceivedAttachment.java @@ -1,16 +0,0 @@ -package dev.fitko.fitconnect.client.subscriber.model; - -import lombok.Builder; -import lombok.Getter; - -import java.util.UUID; - -@Getter -@Builder -public class ReceivedAttachment { - private byte[] data; - private UUID attachmentId; - private String filename; - private String description; - private String mimeType; -} diff --git a/client/src/main/java/dev/fitko/fitconnect/client/util/SubmissionUtil.java b/client/src/main/java/dev/fitko/fitconnect/client/util/SubmissionUtil.java index da952802fbdf83007896c337b2f52538fe4e2036..0d8e37ad5a485ace7c8563904897ac0e08580bde 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/util/SubmissionUtil.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/util/SubmissionUtil.java @@ -8,6 +8,9 @@ import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.SubmitSubmission; import dev.fitko.fitconnect.client.sender.model.AttachmentPayload; +import dev.fitko.fitconnect.client.sender.model.EncryptedAttachment; +import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission; +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; import java.text.ParseException; import java.util.List; @@ -23,11 +26,26 @@ public final class SubmissionUtil { private SubmissionUtil() { } - public static CreateSubmission buildCreateSubmission(final UUID destinationId, final List<AttachmentPayload> attachments, final ServiceType serviceType) { + public static CreateSubmission buildCreateSubmission(final SendableSubmission sendableSubmission, final List<AttachmentPayload> attachments) { return CreateSubmission.builder() - .destinationId(destinationId) + .destinationId(sendableSubmission.getDestinationId()) .announcedAttachments(attachments.stream().map(AttachmentPayload::getAttachmentId).collect(Collectors.toList())) - .serviceType(serviceType) + .serviceType(getServiceTypeFromSendableSubmission(sendableSubmission.getServiceName(), sendableSubmission.getServiceIdentifier())) + .build(); + } + + public static CreateSubmission buildCreateSubmission(final SendableEncryptedSubmission sendableSubmission, final List<EncryptedAttachment> attachments) { + return CreateSubmission.builder() + .destinationId(sendableSubmission.getDestinationId()) + .announcedAttachments(attachments.stream().map(EncryptedAttachment::getAttachmentId).collect(Collectors.toList())) + .serviceType(getServiceTypeFromSendableSubmission(sendableSubmission.getServiceName(), sendableSubmission.getServiceIdentifier())) + .build(); + } + + private static ServiceType getServiceTypeFromSendableSubmission(final String serviceName, final String serviceIdentifier) { + return ServiceType.builder() + .name(serviceName) + .identifier(serviceIdentifier) .build(); } @@ -39,13 +57,7 @@ public final class SubmissionUtil { return submission; } - public static SentSubmission buildSentSubmission(final List<AttachmentPayload> encryptedAttachments, final String encryptedData, final String encryptedMetadata, final Submission submission) { - - final AuthenticationTags authenticationTags = new AuthenticationTags(); - authenticationTags.setData(getAuthTag(encryptedData)); - authenticationTags.setMetadata(getAuthTag(encryptedMetadata)); - authenticationTags.setAttachments(getAuthTagsFromAttachments(encryptedAttachments)); - + public static SentSubmission buildSentSubmission(final AuthenticationTags authenticationTags, final Submission submission) { return SentSubmission.builder() .submissionId(submission.getSubmissionId()) .caseId(submission.getCaseId()) @@ -54,14 +66,30 @@ public final class SubmissionUtil { .build(); } - private static Map<UUID, String> getAuthTagsFromAttachments(final List<AttachmentPayload> encryptedAttachments) { - return encryptedAttachments.stream().collect(Collectors.toMap(AttachmentPayload::getAttachmentId, SubmissionUtil::getAuthTag)); + public static AuthenticationTags buildAuthenticationTags(final List<AttachmentPayload> encryptedAttachments, final String encryptedData, final String encryptedMetadata) { + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setData(getAuthTag(encryptedData)); + authenticationTags.setMetadata(getAuthTag(encryptedMetadata)); + authenticationTags.setAttachments(encryptedAttachments.stream().collect(Collectors.toMap(AttachmentPayload::getAttachmentId, SubmissionUtil::getAuthTag))); + return authenticationTags; + } + + public static AuthenticationTags buildAuthenticationTagsForEncryptedSubmission(final List<EncryptedAttachment> encryptedAttachments, final String encryptedData, final String encryptedMetadata) { + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setData(getAuthTag(encryptedData)); + authenticationTags.setMetadata(getAuthTag(encryptedMetadata)); + authenticationTags.setAttachments(encryptedAttachments.stream().collect(Collectors.toMap(EncryptedAttachment::getAttachmentId, SubmissionUtil::getAuthTag))); + return authenticationTags; } private static String getAuthTag(final AttachmentPayload attachmentPayload) { return getAuthTag(attachmentPayload.getEncryptedData()); } + private static String getAuthTag(final EncryptedAttachment encryptedAttachment) { + return getAuthTag(encryptedAttachment.getContent()); + } + private static String getAuthTag(final String encryptedJWTData) { try { return JWEObject.parse(encryptedJWTData).getAuthTag().toString(); diff --git a/client/src/main/java/dev/fitko/fitconnect/client/util/ValidDataGuard.java b/client/src/main/java/dev/fitko/fitconnect/client/util/ValidDataGuard.java index 3fc9bc6334f5f53f7e8968d94e7dba19f4b2fa8c..2ac59ddb29f28bc9b73e435a0b0eb67f25787ce8 100644 --- a/client/src/main/java/dev/fitko/fitconnect/client/util/ValidDataGuard.java +++ b/client/src/main/java/dev/fitko/fitconnect/client/util/ValidDataGuard.java @@ -3,11 +3,10 @@ package dev.fitko.fitconnect.client.util; import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.domain.model.destination.DestinationService; import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; -import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; import dev.fitko.fitconnect.api.services.Sender; -import dev.fitko.fitconnect.client.sender.model.EncryptedSubmissionPayload; -import dev.fitko.fitconnect.client.sender.model.SubmissionPayload; +import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission; +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; import java.net.URI; import java.util.Objects; @@ -26,55 +25,52 @@ public class ValidDataGuard { /** * Checks if the encrypted submission payload is in a valid state for sending. * - * @param encryptedSubmissionPayload payload to be checked + * @param sendableEncryptedSubmission payload to be checked * @throws IllegalArgumentException if one of the checks fails * @throws IllegalStateException if the destination does not have a declared service type */ - public void ensureValidDataPayload(final EncryptedSubmissionPayload encryptedSubmissionPayload) { - if (encryptedSubmissionPayload == null) { + public void ensureValidDataPayload(final SendableEncryptedSubmission sendableEncryptedSubmission) { + if (sendableEncryptedSubmission == null) { throw new IllegalArgumentException("Encrypted payload must not be null."); } - if (encryptedSubmissionPayload.getEncryptedData() == null) { + if (sendableEncryptedSubmission.getData() == null) { throw new IllegalArgumentException("Encrypted data is mandatory, but was null."); } - if (encryptedSubmissionPayload.getEncryptedMetadata() == null) { + if (sendableEncryptedSubmission.getMetadata() == null) { throw new IllegalArgumentException("Encrypted metadata must not be null."); } - testDefaults(encryptedSubmissionPayload.getDestinationId(), encryptedSubmissionPayload.getServiceType()); + testDefaults(sendableEncryptedSubmission.getDestinationId(), sendableEncryptedSubmission.getServiceName(), sendableEncryptedSubmission.getServiceIdentifier()); } /** * Checks if the unencrypted submission payload is in a valid state for sending. * - * @param submissionPayload payload to be checked + * @param sendableSubmission payload to be checked * @throws IllegalArgumentException if one of the checks fails */ - public void ensureValidDataPayload(final SubmissionPayload submissionPayload) { - if (submissionPayload == null) { + public void ensureValidDataPayload(final SendableSubmission sendableSubmission) { + if (sendableSubmission == null) { throw new IllegalArgumentException("Payload must not be null."); } - if (submissionPayload.getData() == null) { + if (sendableSubmission.getData() == null) { throw new IllegalArgumentException("Data is mandatory, but was null."); } - testOnValidDataFormat(submissionPayload); - testDefaults(submissionPayload); + testOnValidDataFormat(sendableSubmission); + testDefaults(sendableSubmission); } - private void testDefaults(final UUID destinationId, final ServiceType serviceType) { - + private void testDefaults(final UUID destinationId, final String serviceName, final String serviceIdentifier) { if (destinationId == null) { throw new IllegalArgumentException("DestinationId is mandatory, but was null."); - } else if (serviceType == null) { - throw new IllegalArgumentException("ServiceType is mandatory, but was null."); - } else if (serviceType.getIdentifier() == null) { + } else if (serviceIdentifier == null) { throw new IllegalArgumentException("Leika key is mandatory, but was null."); - } else if (noValidLeikaKeyPattern(serviceType.getIdentifier())) { + } else if (noValidLeikaKeyPattern(serviceIdentifier)) { throw new IllegalArgumentException("LeikaKey has invalid format, please follow: ^urn:[a-z0-9][a-z0-9-]{0,31}:[a-z0-9()+,.:=@;$_!*'%/?#-]+$."); } Destination destination = sender.getDestination(destinationId); - if (serviceTypeDoesNotMatchDestination(destination, serviceType)) { - throw new IllegalArgumentException("Provided service type '" + serviceType.getIdentifier() + "' is not allowed by the destination "); + if (serviceTypeDoesNotMatchDestination(destination, serviceIdentifier)) { + throw new IllegalArgumentException("Provided service type '" + serviceIdentifier + "' is not allowed by the destination "); } } @@ -100,11 +96,11 @@ public class ValidDataGuard { } } - private boolean serviceTypeDoesNotMatchDestination(final Destination destination, final ServiceType serviceType) { + private boolean serviceTypeDoesNotMatchDestination(final Destination destination, final String serviceIdentifier) { return destination.getServices().stream() .map(DestinationService::getIdentifier) .filter(Objects::nonNull) - .filter(serviceIdentifier -> serviceIdentifier.equals(serviceType.getIdentifier())) + .filter(destinationServiceIdentifier -> destinationServiceIdentifier.equals(serviceIdentifier)) .findFirst() .isEmpty(); } @@ -119,24 +115,24 @@ public class ValidDataGuard { .isEmpty(); } - private void testOnValidDataFormat(final SubmissionPayload submissionPayload) { - final MimeType dataMimeType = submissionPayload.getDataMimeType(); + private void testOnValidDataFormat(final SendableSubmission sendableSubmission) { + final MimeType dataMimeType = MimeType.fromValue(sendableSubmission.getDataMimeType()); if (dataMimeType.equals(MimeType.APPLICATION_JSON)) { - checkJsonFormat(submissionPayload); + checkJsonFormat(sendableSubmission); } else if (dataMimeType.equals(MimeType.APPLICATION_XML)) { - checkXmlFormat(submissionPayload); + checkXmlFormat(sendableSubmission); } } - private void checkXmlFormat(final SubmissionPayload submissionPayload) { - final ValidationResult validationResult = sender.validateXmlFormat(submissionPayload.getData()); + private void checkXmlFormat(final SendableSubmission sendableSubmission) { + final ValidationResult validationResult = sender.validateXmlFormat(sendableSubmission.getData()); if (validationResult.hasError()) { throw new IllegalArgumentException("Data is not in expected xml format, please provide valid xml: " + validationResult.getError().getMessage()); } } - private void checkJsonFormat(final SubmissionPayload submissionPayload) { - final ValidationResult validationResult = sender.validateJsonFormat(submissionPayload.getData()); + private void checkJsonFormat(final SendableSubmission sendableSubmission) { + final ValidationResult validationResult = sender.validateJsonFormat(sendableSubmission.getData()); if (validationResult.hasError()) { throw new IllegalArgumentException("Data is not in expected json format, please provide valid json: " + validationResult.getError().getMessage()); } diff --git a/client/src/test/java/dev/fitko/fitconnect/client/ClientIntegrationTest.java b/client/src/test/java/dev/fitko/fitconnect/client/ClientIntegrationTest.java deleted file mode 100644 index 997a573d75293fb498da81f9d9df6e93ee67036b..0000000000000000000000000000000000000000 --- a/client/src/test/java/dev/fitko/fitconnect/client/ClientIntegrationTest.java +++ /dev/null @@ -1,566 +0,0 @@ -package dev.fitko.fitconnect.client; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.nimbusds.jose.jwk.RSAKey; -import dev.fitko.fitconnect.api.config.*; -import dev.fitko.fitconnect.api.domain.auth.OAuthToken; -import dev.fitko.fitconnect.api.domain.model.event.Event; -import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; -import dev.fitko.fitconnect.api.domain.model.event.EventStatus; -import dev.fitko.fitconnect.api.domain.model.event.problems.submission.InvalidEventLog; -import dev.fitko.fitconnect.api.domain.model.metadata.*; -import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; -import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Purpose; -import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; -import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; -import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema; -import dev.fitko.fitconnect.api.domain.model.route.Area; -import dev.fitko.fitconnect.api.domain.model.route.Route; -import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; -import dev.fitko.fitconnect.api.exceptions.RestApiException; -import dev.fitko.fitconnect.api.services.crypto.CryptoService; -import dev.fitko.fitconnect.client.factory.ClientFactory; -import dev.fitko.fitconnect.client.router.DestinationSearch; -import dev.fitko.fitconnect.client.sender.EncryptedSubmissionBuilder; -import dev.fitko.fitconnect.client.sender.SubmissionBuilder; -import dev.fitko.fitconnect.client.sender.model.AttachmentPayload; -import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission; -import dev.fitko.fitconnect.core.auth.DefaultOAuthService; -import dev.fitko.fitconnect.core.crypto.HashService; -import dev.fitko.fitconnect.core.crypto.JWECryptoService; -import dev.fitko.fitconnect.core.http.RestService; -import org.apache.tika.mime.MimeTypes; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.text.ParseException; -import java.time.Duration; -import java.util.*; -import java.util.stream.Collectors; - -import static dev.fitko.fitconnect.api.config.SchemaConfig.METADATA_V_1_0_0; -import static org.awaitility.Awaitility.await; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.*; - -/** - * This test uses real credentials and endpoints stored in gitlab ci variables to test the sdk against a real system - */ -@EnabledIfEnvironmentVariable(named="TEST_DESTINATION_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named="SENDER_CLIENT_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named="SENDER_CLIENT_SECRET", matches = ".*") -@EnabledIfEnvironmentVariable(named="SUBSCRIBER_CLIENT_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named="SUBSCRIBER_CLIENT_SECRET", matches = ".*") -class ClientIntegrationTest { - - @Nested - class SendSubmissionTests { - - @Test - void testSendAndConfirmCycle() { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("TESTING", true); - - final var submission = SubmissionBuilder.Builder() - .withAttachment(new File("src/test/resources/attachment.txt")) - .withJsonData("{ \"data\": \"Beispiel Fachdaten\" }", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) - .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000") - .build(); - - final var sentSubmission = ClientFactory.senderClient(config).submit(submission); - - assertNotNull(sentSubmission); - - // When - final ReceivedSubmission receivedSubmission = - ClientFactory.subscriberClient(config) - .requestSubmission(sentSubmission.getSubmissionId()); - - // Then - assertNotNull(receivedSubmission); - assertThat(receivedSubmission.getData(), is("{ \"data\": \"Beispiel Fachdaten\" }")); - assertThat(receivedSubmission.getDataSchemaUri(), is(URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json"))); - assertThat(receivedSubmission.getDataMimeType().toString(), is("application/json")); - assertThat(new String(receivedSubmission.getAttachments().get(0).getData()), is("Test attachment")); - } - - @Test - void testSendAndConfirmCycleWithEncryptedData() throws ParseException, IOException { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("TESTING", true); - - final UUID destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID")); - - final CryptoService cryptoService = new JWECryptoService(new HashService()); - final SenderClient client = ClientFactory.senderClient(config); - - final Optional<String> publicKey = client.getPublicKey(destinationId); - assertTrue(publicKey.isPresent()); - - final RSAKey encryptionKey = RSAKey.parse(publicKey.get()); - - final String jsonData = "{ \"data\": \"Beispiel Fachdaten\" }"; - final File attachmentFile = new File("src/test/resources/attachment.txt"); - final String attachmentData = Files.readString(attachmentFile.toPath()); - - final String encryptedData = cryptoService.encryptString(encryptionKey, jsonData); - final String encryptedAttachment = cryptoService.encryptString(encryptionKey, attachmentData); - - final var submissionSchema = new SubmissionSchema(); - submissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")); - submissionSchema.setMimeType(MimeType.APPLICATION_JSON); - - final var dataHash = new Hash(); - dataHash.setSignatureType(SignatureType.SHA_512); - dataHash.setContent(cryptoService.hashBytes(jsonData.getBytes(StandardCharsets.UTF_8))); - - final var data = new Data(); - data.setHash(dataHash); - data.setSubmissionSchema(submissionSchema); - - final var attachmentPayload = AttachmentPayload.builder() - .encryptedData(encryptedAttachment) - .file(attachmentFile) - .mimeType(MimeTypes.PLAIN_TEXT) - .attachmentId(UUID.randomUUID()) - .hashedData(cryptoService.hashBytes(attachmentData.getBytes(StandardCharsets.UTF_8))) - .build(); - - final var publicServiceType = new PublicServiceType(); - publicServiceType.setName("Test Service"); - publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); - - final var attachment = new Attachment(); - attachment.setAttachmentId(attachmentPayload.getAttachmentId()); - attachment.setPurpose(Purpose.ATTACHMENT); - attachment.setFilename(attachmentPayload.getFile().getName()); - attachment.setMimeType(attachmentPayload.getMimeType()); - - final var attachmentHash = new Hash(); - attachmentHash.setContent(attachmentPayload.getHashedData()); - attachmentHash.setSignatureType(SignatureType.SHA_512); - attachment.setHash(attachmentHash); - - final var contentStructure = new ContentStructure(); - contentStructure.setAttachments(List.of(attachment)); - contentStructure.setData(data); - - final var metadata = new Metadata(); - metadata.setSchema(METADATA_V_1_0_0.toString()); - metadata.setContentStructure(contentStructure); - metadata.setPublicServiceType(publicServiceType); - - final String encryptedMetadata = cryptoService.encryptBytes(encryptionKey, new ObjectMapper().writeValueAsBytes(metadata)); - - // When - final var submission = EncryptedSubmissionBuilder.Builder() - .withEncryptedAttachment(attachmentPayload.getAttachmentId(), attachmentPayload.getEncryptedData()) - .withEncryptedData(encryptedData) - .withEncryptedMetadata(encryptedMetadata) - .withDestination(destinationId) - .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000") - .build(); - - final var sentSubmission = ClientFactory.senderClient(config).submit(submission); - - assertNotNull(sentSubmission); - - final ReceivedSubmission receivedSubmission = ClientFactory.subscriberClient(config).requestSubmission(sentSubmission.getSubmissionId()); - - // Then - assertNotNull(receivedSubmission); - assertThat(receivedSubmission.getData(), is(jsonData)); - assertThat(receivedSubmission.getDataMimeType().toString(), is("application/json")); - assertThat(new String(receivedSubmission.getAttachments().get(0).getData()), is("Test attachment")); - } - - @Test - void testAbortedSendSubmissionWithKeyValidationNotSilent() { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("PROD", false); - - // When - final var submission = SubmissionBuilder.Builder() - .withJsonData("{ \"data\": \"Beispiel Fachdaten\" }", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) - .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000") - .build(); - - final var sentSubmission = ClientFactory.senderClient(config).submit(submission); - - // Then - assertNull(sentSubmission); - } - } - - @Nested - class ReceiveSubmissionTests { - - @Test - void testListSubmissions() { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("TESTING", true); - - final var senderClient = ClientFactory.senderClient(config); - final var subscriberClient = ClientFactory.subscriberClient(config); - - final UUID destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID")); - final String leikaKey = "urn:de:fim:leika:leistung:99400048079000"; - final String serviceName = "Test Service"; - - final var submissionOne = SubmissionBuilder.Builder() - .withJsonData("{ \"data\": \"Beispiel Fachdaten 1\" }", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(destinationId) - .withServiceType(serviceName, leikaKey) - .build(); - - final var submissionTwo = SubmissionBuilder.Builder() - .withJsonData("{ \"data\": \"Beispiel Fachdaten 2\" }", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(destinationId) - .withServiceType(serviceName, leikaKey) - .build(); - - final var sentSubmissionOne = senderClient.submit(submissionOne); - final var sentSubmissionTwo = senderClient.submit(submissionTwo); - - assertNotNull(sentSubmissionOne); - assertNotNull(sentSubmissionTwo); - - // When - final Set<SubmissionForPickup> submissions = subscriberClient.getAvailableSubmissions(destinationId); - - // Then - assertFalse(submissions.isEmpty()); - - final List<UUID> submissionIds = submissions.stream().map(SubmissionForPickup::getSubmissionId).collect(Collectors.toList()); - - assertThat(submissionIds, hasItems(sentSubmissionOne.getSubmissionId(), sentSubmissionTwo.getSubmissionId())); - - // remove by confirming - subscriberClient.requestSubmission(sentSubmissionOne.getSubmissionId()).acceptSubmission(); - subscriberClient.requestSubmission(sentSubmissionTwo.getSubmissionId()).acceptSubmission(); - } - } - - @Nested - class EventTests { - - @Test - void testRejectEvent() { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("TESTING", true); - - final var submission = SubmissionBuilder.Builder() - .withAttachment(new File("src/test/resources/attachment.txt")) - .withJsonData("{ \"data\": \"Beispiel Fachdaten\" }", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) - .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000") - .build(); - - final var sentSubmission = ClientFactory.senderClient(config).submit(submission); - - assertNotNull(sentSubmission); - - // When - final var subscriberClient = ClientFactory.subscriberClient(config); - - final var sentSubmissionId = sentSubmission.getSubmissionId(); - - // reject and remove - subscriberClient.requestSubmission(sentSubmissionId).rejectSubmission(List.of(new InvalidEventLog())); - - // second attempt to receive and reject the submission should return an empty result - // since the submission is gone after being rejected - assertNull(subscriberClient.requestSubmission(sentSubmissionId)); - } - - @Test - void testAcceptEvent() { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("TESTING", true); - - final var submission = SubmissionBuilder.Builder() - .withAttachment(new File("src/test/resources/attachment.txt")) - .withJsonData("{ \"data\": \"Beispiel Fachdaten\" }", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) - .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000") - .build(); - - final var sentSubmission = ClientFactory.senderClient(config).submit(submission); - - assertNotNull(sentSubmission); - - // When - final var subscriberClient = ClientFactory.subscriberClient(config); - - final var sentSubmissionId = sentSubmission.getSubmissionId(); - - // accept and remove - subscriberClient.requestSubmission(sentSubmissionId).acceptSubmission(); - - // second attempt to receive the submission should return an empty result - // since the submission is gone after being accepted - assertNull(subscriberClient.requestSubmission(sentSubmissionId)); - } - - @Test - void testReadEventLogFromSender() { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("TESTING", true); - - final var submission = SubmissionBuilder.Builder() - .withJsonData("{ \"data\": \"Beispiel Fachdaten\" }", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) - .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000") - .build(); - - final var sentSubmission = ClientFactory.senderClient(config).submit(submission); - - assertNotNull(sentSubmission); - - // When - await().atMost(Duration.ofSeconds(30)).until(() -> - { - final UUID destinationId = sentSubmission.getDestinationId(); - final UUID caseId = sentSubmission.getCaseId(); - - final List<EventLogEntry> senderEventLog = ClientFactory.senderClient(config).getEventLog(caseId, destinationId); - final List<Event> senderEvents = senderEventLog.stream().map(EventLogEntry::getEvent).collect(Collectors.toList()); - - // Then - return senderEvents.equals(List.of(Event.CREATE, Event.SUBMIT)); - }); - } - - @Test - void testReadEventLogFromSubscriber() { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("TESTING", true); - - final var submission = SubmissionBuilder.Builder() - .withAttachment(new File("src/test/resources/attachment.txt")) - .withJsonData("{ \"data\": \"Beispiel Fachdaten\" }", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) - .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000") - .build(); - - final var sentSubmission = ClientFactory.senderClient(config).submit(submission); - - assertNotNull(sentSubmission); - - final var receivedSubmission = ClientFactory.subscriberClient(config) - .requestSubmission(sentSubmission.getSubmissionId()); - - assertNotNull(receivedSubmission); - - // When - await().atMost(Duration.ofSeconds(30)).until(() -> - { - final UUID destinationId = receivedSubmission.getDestinationId(); - final UUID caseId = receivedSubmission.getCaseId(); - - final List<EventLogEntry> subscriberEventLog = ClientFactory.subscriberClient(config).getEventLog(caseId, destinationId); - final List<Event> subscriberEvents = subscriberEventLog.stream().map(EventLogEntry::getEvent).collect(Collectors.toList()); - - // Then - return subscriberEvents.equals(List.of(Event.CREATE, Event.SUBMIT)); - }); - } - - @Test - void testReadSubmissionStatusWithAuthTagEventValidationFromSender() { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("TESTING", true); - - final SenderClient senderClient = ClientFactory.senderClient(config); - - final var submission = SubmissionBuilder.Builder() - .withAttachment(new File("src/test/resources/attachment.txt")) - .withJsonData("{ \"data\": \"Beispiel Fachdaten\" }", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) - .withServiceType("Test Service", "urn:de:fim:leika:leistung:99400048079000") - .build(); - - final var sentSubmission = ClientFactory.senderClient(config).submit(submission); - - assertNotNull(sentSubmission); - - await().atMost(Duration.ofSeconds(30)).until(() -> - { - // When - final EventStatus statusForSubmission = senderClient.getStatusForSubmission(sentSubmission); - - // Then - return statusForSubmission.getStatus().equals(Event.SUBMIT.getState()); - }); - } - - } - - @Nested - class RoutingTests { - - @Test - void testFindDestinationsWithRegionalKey() { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("TESTING", true); - - final RoutingClient routingClient = ClientFactory.routingClient(config); - - final DestinationSearch search = DestinationSearch.Builder() - .withLeikaKey("99123456760610") - .withArs("064350014014") - .build(); - - // When - final List<Route> routes = routingClient.findDestinations(search); - - // Then - assertThat(routes, hasSize(1)); - assertThat(routes.get(0).getDestinationId(), is(UUID.fromString("d40e7b13-da98-4b09-9e16-bbd61ca81510"))); - } - - @Test - void testFindDestinationsWithAreaId() { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("TESTING", true); - - final RoutingClient routingClient = ClientFactory.routingClient(config); - - final DestinationSearch search = DestinationSearch.Builder() - .withLeikaKey("99123456760610") - .withAreaId("931") - .build(); - - // When - final List<Route> routes = routingClient.findDestinations(search); - - // Then - assertThat(routes, hasSize(1)); - assertThat(routes.get(0).getDestinationId(), is(UUID.fromString("d40e7b13-da98-4b09-9e16-bbd61ca81510"))); - } - - @Test - void testFindDestinationsWithMultipleAreaSearchCriteria() { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("TESTING", true); - - final RoutingClient routingClient = ClientFactory.routingClient(config); - - final DestinationSearch search = DestinationSearch.Builder() - .withLeikaKey("99123456760610") - .withArs("064350014014") - .withAreaId("1234") - .build(); - - // When - final RestApiException exception = assertThrows(RestApiException.class, () -> routingClient.findDestinations(search)); - - // Then - assertThat(exception.getMessage(), containsString("Only one of ars, ags or areaId must be specified")); - } - - @Test - void testFindAreaWithMultipleSearchCriteria() { - - // Given - final ApplicationConfig config = getConfigWithCredentialsFromGitlab("TESTING", true); - - final RoutingClient routingClient = ClientFactory.routingClient(config); - - // When - final List<Area> areas = routingClient.findAreas(List.of("Leip*", "04229"), 0, 10); - - // Then - assertThat(areas, is(not(empty()))); - assertThat(areas.size(), is(lessThanOrEqualTo(10))); - assertTrue(areas.stream().anyMatch(area -> area.getName().equals("Leipzig"))); - } - - } - - @Nested - class AuthenticationTests { - @Test - void retrieveAuthenticationToken() { - - // Given - final var tokenUrl = "https://auth-testing.fit-connect.fitko.dev/token"; - final var clientId = System.getenv("SUBSCRIBER_CLIENT_ID"); - final var secret = System.getenv("SUBSCRIBER_CLIENT_SECRET"); - - final RestService restService = new RestService(new BuildInfo()); - - final var authService = new DefaultOAuthService(restService.getRestTemplate(), clientId, secret, tokenUrl); - - // When - final OAuthToken token = authService.getCurrentToken(); - - // Then - assertNotNull(token); - assertNull(token.getError()); - assertNotNull(token.getAccessToken()); - assertEquals(1800, token.getExpiresIn()); - } - } - - - private ApplicationConfig getConfigWithCredentialsFromGitlab(final String environmentName, final boolean allowInsecurePublicKey) { - - final var sender = new SenderConfig(System.getenv("SENDER_CLIENT_ID"), System.getenv("SENDER_CLIENT_SECRET")); - - final var subscriber = SubscriberConfig.builder() - .clientId(System.getenv("SUBSCRIBER_CLIENT_ID")) - .clientSecret(System.getenv("SUBSCRIBER_CLIENT_SECRET")) - .privateDecryptionKeyPath("src/test/resources/private_decryption_test_key.json") - .privateSigningKeyPath("src/test/resources/private_test_signing_key.json") - .build(); - - final var envName = new EnvironmentName(environmentName); - final Environment env = getEnvironment(allowInsecurePublicKey); - - return ApplicationConfig.builder() - .senderConfig(sender) - .subscriberConfig(subscriber) - .environments(Map.of(envName, env)) - .activeEnvironment(envName) - .build(); - } - - private static Environment getEnvironment(final boolean allowInsecurePublicKey) { - final String authBaseUrl = "https://auth-testing.fit-connect.fitko.dev"; - final String routingBaseUrl = "https://routing-api-testing.fit-connect.fitko.dev"; - final String selfServicePortalUrl = "https://portal.auth-testing.fit-connect.fitko.dev"; - final String submissionBaseUrl = "https://submission-api-testing.fit-connect.fitko.dev"; - return new Environment(authBaseUrl, routingBaseUrl, submissionBaseUrl, selfServicePortalUrl, allowInsecurePublicKey); - } -} diff --git a/client/src/test/java/dev/fitko/fitconnect/client/SenderClientTest.java b/client/src/test/java/dev/fitko/fitconnect/client/SenderClientTest.java index 723b1e2603662cff41d6b311c25026dae29f5d9d..378013b3022c50210ed00ff964f77fc3b4fa02bc 100644 --- a/client/src/test/java/dev/fitko/fitconnect/client/SenderClientTest.java +++ b/client/src/test/java/dev/fitko/fitconnect/client/SenderClientTest.java @@ -8,7 +8,7 @@ import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.domain.model.destination.DestinationService; import dev.fitko.fitconnect.api.domain.model.event.Event; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; -import dev.fitko.fitconnect.api.domain.model.event.EventStatus; +import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataEncryptionIssue; import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataSchemaViolation; @@ -18,48 +18,76 @@ import dev.fitko.fitconnect.api.domain.model.submission.SentSubmission; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; -import dev.fitko.fitconnect.api.exceptions.*; +import dev.fitko.fitconnect.api.exceptions.AttachmentCreationException; +import dev.fitko.fitconnect.api.exceptions.EncryptionException; +import dev.fitko.fitconnect.api.exceptions.EventLogException; +import dev.fitko.fitconnect.api.exceptions.KeyNotRetrievedException; +import dev.fitko.fitconnect.api.exceptions.RestApiException; +import dev.fitko.fitconnect.api.exceptions.SubmissionNotCreatedException; +import dev.fitko.fitconnect.api.exceptions.ValidationException; import dev.fitko.fitconnect.api.services.Sender; -import dev.fitko.fitconnect.client.sender.EncryptedSubmissionBuilder; -import dev.fitko.fitconnect.client.sender.SubmissionBuilder; -import dev.fitko.fitconnect.client.sender.model.EncryptedSubmissionPayload; -import dev.fitko.fitconnect.client.sender.model.SubmissionPayload; +import dev.fitko.fitconnect.client.sender.model.Attachment; +import dev.fitko.fitconnect.client.sender.model.EncryptedAttachment; +import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission; +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; import dev.fitko.fitconnect.client.testutil.LogCaptor; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.net.URI; -import java.util.*; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.core.IsEqual.equalTo; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class SenderClientTest { - private Sender senderMock; - private SenderClient senderClient; + private final Sender senderMock; + private final SenderClient senderClient; private static final LogCaptor logs = new LogCaptor(); + private final UUID destinationId = UUID.randomUUID(); + + private final RSAKey publicKey = generateRsaKey(2048).toPublicJWK(); + @BeforeEach - public void setup() { + public void clearLogs(){ + logs.clearEvents(); + } + + public SenderClientTest() throws JOSEException { senderMock = mock(Sender.class); senderClient = new SenderClient(senderMock); - logs.clearEvents(); + setupTestMocks(destinationId); } @Test - void testSendSubmissionWithJsonData() throws Exception { + void testSendSubmissionWithJsonData() { // Given - final RSAKey publicKey = generateRsaKey(4096).toPublicJWK(); + final RSAKey publicKey = this.publicKey; final var destinationId = UUID.randomUUID(); final var announcedSubmission = getAnnouncedSubmission(destinationId); @@ -75,21 +103,20 @@ public class SenderClientTest { when(senderMock.getDestination(any())).thenReturn(destination); when(senderMock.createSubmission(any())).thenReturn(announcedSubmission); when(senderMock.sendSubmission(any())).thenReturn(expectedSubmission); - when(senderMock.validatePublicKey(any())).thenReturn(ValidationResult.ok()); when(senderMock.getEncryptionKeyForDestination(any())).thenReturn(publicKey); when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); when(senderMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); // When - final var submission = SubmissionBuilder.Builder() - .withAttachment(new File("src/test/resources/attachment.txt")) - .withJsonData("{}", URI.create("https://json.mimetype")) - .withDestination(destinationId) - .withServiceType("Service", "urn:de:fim:leika:leistung:99400048079000") + final var submission = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "TestService") + .setJsonData("{}", URI.create("https://json.mimetype")) + .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "text/plain")) .build(); - final var sentSubmission = senderClient.submit(submission); + final var sentSubmission = senderClient.send(submission); // Then assertNotNull(sentSubmission); @@ -98,10 +125,10 @@ public class SenderClientTest { } @Test - void testSendSubmissionWithXmlData() throws Exception { + void testSendSubmissionWithXmlData() { // Given - final RSAKey publicKey = generateRsaKey(4096).toPublicJWK(); + final RSAKey publicKey = this.publicKey; final var destinationId = UUID.randomUUID(); final var announcedSubmission = getAnnouncedSubmission(destinationId); @@ -117,20 +144,19 @@ public class SenderClientTest { when(senderMock.getDestination(any())).thenReturn(destination); when(senderMock.createSubmission(any())).thenReturn(announcedSubmission); when(senderMock.sendSubmission(any())).thenReturn(expectedSubmission); - when(senderMock.validatePublicKey(any())).thenReturn(ValidationResult.ok()); when(senderMock.getEncryptionKeyForDestination(any())).thenReturn(publicKey); when(senderMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); when(senderMock.validateXmlFormat(any())).thenReturn(ValidationResult.ok()); // When - final var submission = SubmissionBuilder.Builder() - .withAttachment(new File("src/test/resources/attachment.txt")) - .withXmlData("<xml><test>data</test></xml>", URI.create("https://xml.mimetype")) - .withDestination(destinationId) - .withServiceType("Service", "urn:de:fim:leika:leistung:99400048079000") + final var submission = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "TestService") + .setXmlData("<xml><test>data</test></xml>", URI.create("https://xml.mimetype")) + .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "text/plain")) .build(); - final var sentSubmission = senderClient.submit(submission); + final var sentSubmission = senderClient.send(submission); // Then assertNotNull(sentSubmission); @@ -163,7 +189,7 @@ public class SenderClientTest { } @Test - void testMissingDataMimeTypeSchema() throws Exception { + void testMissingDataMimeTypeSchema() { // Given final var destinationId = UUID.randomUUID(); @@ -177,22 +203,21 @@ public class SenderClientTest { final var announcedSubmission = getAnnouncedSubmission(destinationId); - final RSAKey publicKey = generateRsaKey(4096).toPublicJWK(); + final RSAKey publicKey = this.publicKey; when(senderMock.getDestination(any())).thenReturn(destination); when(senderMock.createSubmission(any())).thenReturn(announcedSubmission); - when(senderMock.validatePublicKey(any())).thenReturn(ValidationResult.ok()); when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); when(senderMock.getEncryptionKeyForDestination(any())).thenReturn(publicKey); // When - final var submission = SubmissionBuilder.Builder() - .withJsonData("{}", URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(destinationId) - .withServiceType("Service", "urn:de:fim:leika:leistung:99400048079000") + final var submission = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "TestService") + .setJsonData("{}", URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) .build(); - final var exception = Assertions.assertThrows(IllegalArgumentException.class, () -> senderClient.submit(submission)); + final var sentSubmission = senderClient.send(submission); // Then assertThat(exception.getMessage(), equalTo("Combination of provided MIME type 'application/json' and schema URI " + @@ -200,20 +225,18 @@ public class SenderClientTest { } @Test - void testWithEmptyAttachments() throws Exception { + void testWithEmptyAttachments() { // Given - final UUID destinationId = setupTestMocks(); - - // When - final var submission = SubmissionBuilder.Builder() - .withAttachments(List.of()) - .withJsonData("{}", URI.create("https://mimetype.test.de")) - .withDestination(destinationId) - .withServiceType("name", "urn:de:fim:leika:leistung:99400048079000") + final var submission = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "TestService") + .setJsonData("{}", URI.create("https://mimetype.test.de")) + .addAttachments(List.of()) .build(); - final var sentSubmission = senderClient.submit(submission); + // When + final var sentSubmission = senderClient.send(submission); // Then assertNotNull(sentSubmission); @@ -221,20 +244,18 @@ public class SenderClientTest { } @Test - void testWithAttachmentFileNull() throws Exception { + void testWithAttachmentFileNull() { // Given - final UUID destinationId = setupTestMocks(); - - // When - final var submission = SubmissionBuilder.Builder() - .withAttachment(null) - .withJsonData("{}", URI.create("https://mimetype.test.de")) - .withDestination(destinationId) - .withServiceType("name", "urn:de:fim:leika:leistung:99400048079000") + final var submission = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "TestService") + .setJsonData("{}", URI.create("https://mimetype.test.de")) + .addAttachment(null) .build(); - final var sentSubmission = senderClient.submit(submission); + // When + final var sentSubmission = senderClient.send(submission); // Then assertNotNull(sentSubmission); @@ -242,41 +263,50 @@ public class SenderClientTest { } @Test - void testWithAttachmentPathNotPresent() throws Exception { + void testWithAttachmentPathNotPresent() { // Given - final UUID destinationId = setupTestMocks(); + final var path = Path.of("/non/existing/path"); // When - final var submission = SubmissionBuilder.Builder() - .withAttachment(new File("/non/existing/path")) - .withJsonData("{}", URI.create("https://mimetype.test.de")) - .withDestination(destinationId) - .withServiceType("name", "urn:de:fim:leika:leistung:99400048079000") - .build(); + final AttachmentCreationException exception = assertThrows(AttachmentCreationException.class, () -> Attachment.fromPath(path, "plain/text")); - final var sentSubmission = senderClient.submit(submission); + // Then + assertThat(exception.getMessage(), containsString("Reading attachment from path '" + path + "' failed")); + } + + @Test + void testWithAttachmentInputStreamNotReadable() { + + // Given + final var inputStream = new InputStream() { + @Override + public int read() throws IOException { + throw new IOException("reading from stream failed"); + } + }; + + // When + final AttachmentCreationException exception = assertThrows(AttachmentCreationException.class, () -> Attachment.fromInputStream(inputStream, "plain/text", "non-existing-file", "test")); // Then - assertNull(sentSubmission); - logs.assertContains("Reading file failed. Attachment will not be created."); + assertThat(exception.getMessage(), containsString("Attachment could not be read from input-stream")); } @Test - void testEncryptionError() throws Exception { + void testEncryptionError() { // Given - final var destinationId = setupTestMocks(); when(senderMock.encryptBytes(any(), any())).thenThrow(new EncryptionException("Encryption failed")); - // When - final var submission = SubmissionBuilder.Builder() - .withJsonData("{}", URI.create("https://mimetype.test.de")) - .withDestination(destinationId) - .withServiceType("name", "urn:de:fim:leika:leistung:99400048079000") + final var submission = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "TestService") + .setJsonData("{}", URI.create("https://mimetype.test.de")) .build(); - final var sentSubmission = senderClient.submit(submission); + // When + final var sentSubmission = senderClient.send(submission); // Then assertNull(sentSubmission); @@ -284,20 +314,19 @@ public class SenderClientTest { } @Test - void testAnnouncingSubmissionFailed() throws Exception { + void testAnnouncingSubmissionFailed() { // Given - final var destinationId = setupTestMocks(); when(senderMock.createSubmission(any())).thenThrow(new SubmissionNotCreatedException("Announcing submission failed")); - // When - final var submission = SubmissionBuilder.Builder() - .withJsonData("{}", URI.create("https://mimetype.test.de")) - .withDestination(destinationId) - .withServiceType("name", "urn:de:fim:leika:leistung:99400048079000") + final var submission = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "TestService") + .setJsonData("{}", URI.create("https://mimetype.test.de")) .build(); - final var sentSubmission = senderClient.submit(submission); + // When + final var sentSubmission = senderClient.send(submission); // Then assertNull(sentSubmission); @@ -306,20 +335,19 @@ public class SenderClientTest { } @Test - void testRestApiCallFailed() throws Exception { + void testRestApiCallFailed() { // Given - final var destinationId = setupTestMocks(); when(senderMock.createSubmission(any())).thenThrow(new RestApiException("Announcing submission failed")); - // When - final SubmissionPayload submission = SubmissionBuilder.Builder() - .withJsonData("{}", URI.create("https://mimetype.test.de")) - .withDestination(destinationId) - .withServiceType("name", "urn:de:fim:leika:leistung:99400048079000") + final SendableSubmission submission = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "TestService") + .setJsonData("{}", URI.create("https://mimetype.test.de")) .build(); - final var sentSubmission = senderClient.submit(submission); + // When + final var sentSubmission = senderClient.send(submission); assertNull(sentSubmission); logs.assertContains("Sending submission failed"); @@ -327,81 +355,75 @@ public class SenderClientTest { } @Test - void testGettingEncryptionKeyFailed() throws Exception { + void testGettingEncryptionKeyFailed() { // Given - final var destinationId = setupTestMocks(); + when(senderMock.getEncryptionKeyForDestination(any())).thenThrow(new KeyNotRetrievedException("Getting encryption key failed")); // When - final SubmissionPayload submission = SubmissionBuilder.Builder() - .withJsonData("{}", URI.create("https://mimetype.test.de")) - .withDestination(destinationId) - .withServiceType("name", "urn:de:fim:leika:leistung:99400048079000") + final SendableSubmission submission = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "TestService") + .setJsonData("{}", URI.create("https://mimetype.test.de")) .build(); - final var sentSubmission = senderClient.submit(submission); + final var sentSubmission = senderClient.send(submission); assertNull(sentSubmission); logs.assertContains("Getting encryption key for destination " + destinationId + " failed"); } @Test - void testSendSubmissionWithEncryptedData() throws Exception { + void testSendSubmissionWithEncryptedData() { // Given - final var destinationId = setupTestMocks(); - - // When - final EncryptedSubmissionPayload submission = EncryptedSubmissionBuilder.Builder() - .withEncryptedAttachments(Map.of(UUID.randomUUID(), "@tt@chm$nt")) - .withEncryptedData("$ncrypt$d d@t@") - .withEncryptedMetadata("$ncrypt$d m$t@d@t@") - .withDestination(destinationId) - .withServiceType("name", "urn:de:fim:leika:leistung:99400048079000") + final SendableEncryptedSubmission submission = SendableEncryptedSubmission.Builder() + .setDestination(destinationId) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "TestService") + .setEncryptedMetadata("$ncrypt$d m$t@d@t@") + .setEncryptedData("$ncrypt$d d@t@") + .addEncryptedAttachment(new EncryptedAttachment(UUID.randomUUID(), "@tt@chm$nt")) .build(); - final var sentSubmission = senderClient.submit(submission); + // When + final var sentSubmission = senderClient.send(submission); assertNotNull(sentSubmission); logs.assertContains("SUCCESSFULLY HANDED IN SUBMISSION"); } @Test - void testPublicEncryptionKeyRetrieval() throws Exception { + void testPublicEncryptionKeyRetrieval() { // Given final var destinationId = UUID.randomUUID(); final var destination = getDestination(destinationId); - final RSAKey publicKey = generateRsaKey(4096).toPublicJWK(); - when(senderMock.getDestination(any())).thenReturn(destination); - when(senderMock.validatePublicKey(any())).thenReturn(ValidationResult.ok()); when(senderMock.getEncryptionKeyForDestination(any())).thenReturn(publicKey); // When - final Optional<String> encryptionKey = senderClient.getPublicKey(destinationId); + final String encryptionKey = senderClient.getPublicKeyForDestination(destinationId); - assertTrue(encryptionKey.isPresent()); - assertThat(encryptionKey.get(), equalTo(publicKey.toJSONString())); + assertNotNull(encryptionKey); + assertThat(encryptionKey, equalTo(publicKey.toJSONString())); } @Test - void testFailOnInvalidMetadata() throws Exception { + void testFailOnInvalidMetadata() { // Given - final var destinationId = setupTestMocks(); when(senderMock.validateMetadata(any())).thenReturn(ValidationResult.error(new ValidationException("invalid metadata"))); - // When - final SubmissionPayload submission = SubmissionBuilder.Builder() - .withJsonData("{}", URI.create("https://mimetype.test.de")) - .withDestination(destinationId) - .withServiceType("name", "urn:de:fim:leika:leistung:99400048079000") + final SendableSubmission submission = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "TestService") + .setJsonData("{}", URI.create("https://mimetype.test.de")) .build(); - final var sentSubmission = senderClient.submit(submission); + // When + final var sentSubmission = senderClient.send(submission); assertNull(sentSubmission); logs.assertContains("Metadata does not match schema"); @@ -451,12 +473,12 @@ public class SenderClientTest { .authenticationTags(new AuthenticationTags()) .build(); - final EventStatus eventStatus = new EventStatus(Event.ACCEPT.getState(), Collections.emptyList()); + final SubmissionStatus submissionStatus = new SubmissionStatus(Event.ACCEPT.getState(), Collections.emptyList()); - when(senderMock.getLastedEvent(any(), any(), any(), any())).thenReturn(eventStatus); + when(senderMock.getLastedEvent(any(), any(), any(), any())).thenReturn(submissionStatus); // When - final EventStatus statusForSubmission = senderClient.getStatusForSubmission(sentSubmission); + final SubmissionStatus statusForSubmission = senderClient.getStatusForSubmission(sentSubmission); // Then assertThat(statusForSubmission.getStatus(), is(Event.ACCEPT.getState())); @@ -474,12 +496,12 @@ public class SenderClientTest { .authenticationTags(new AuthenticationTags()) .build(); - final EventStatus eventStatus = new EventStatus(Event.REJECT.getState(), List.of(new DataEncryptionIssue(), new DataSchemaViolation())); + final SubmissionStatus submissionStatus = new SubmissionStatus(Event.REJECT.getState(), List.of(new DataEncryptionIssue(), new DataSchemaViolation())); - when(senderMock.getLastedEvent(any(), any(), any(), any())).thenReturn(eventStatus); + when(senderMock.getLastedEvent(any(), any(), any(), any())).thenReturn(submissionStatus); // When - final EventStatus statusForSubmission = senderClient.getStatusForSubmission(sentSubmission); + final SubmissionStatus statusForSubmission = senderClient.getStatusForSubmission(sentSubmission); // Then assertThat(statusForSubmission.getStatus(), is(Event.REJECT.getState())); @@ -490,33 +512,30 @@ public class SenderClientTest { @Test void validateCallback() { - when(this.senderMock.validateCallback(anyString(), anyLong(), anyString(), anyString())).thenReturn(ValidationResult.ok()); + when(senderMock.validateCallback(anyString(), anyLong(), anyString(), anyString())).thenReturn(ValidationResult.ok()); - ValidationResult validationResult = this.senderClient.validateCallback("hmac", 0L, "body", "secret"); + final ValidationResult validationResult = senderClient.validateCallback("hmac", 0L, "body", "secret"); - verify(this.senderMock, times(1)).validateCallback(anyString(), anyLong(), anyString(), anyString()); + verify(senderMock, times(1)).validateCallback(anyString(), anyLong(), anyString(), anyString()); assertTrue(validationResult.isValid()); assertFalse(validationResult.hasError()); } - private UUID setupTestMocks() throws JOSEException { + private void setupTestMocks(final UUID destinationId) { - final var destinationId = UUID.randomUUID(); final var destination = getDestination(destinationId); final var announcedSubmission = getAnnouncedSubmission(destinationId); - final RSAKey publicKey = generateRsaKey(4096).toPublicJWK(); + final RSAKey publicKey = this.publicKey; when(senderMock.getDestination(any())).thenReturn(destination); when(senderMock.createSubmission(any())).thenReturn(announcedSubmission); - when(senderMock.validatePublicKey(any())).thenReturn(ValidationResult.ok()); when(senderMock.getEncryptionKeyForDestination(any())).thenReturn(publicKey); when(senderMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); when(senderMock.validateXmlFormat(any())).thenReturn(ValidationResult.ok()); when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); when(senderMock.sendSubmission(any())).thenReturn(new Submission()); - return destinationId; } private SubmissionForPickup getAnnouncedSubmission(final UUID destinationId) { 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..00e535a6f06020080d7a0b2f6111e8416d7a5166 100644 --- a/client/src/test/java/dev/fitko/fitconnect/client/SubscriberClientTest.java +++ b/client/src/test/java/dev/fitko/fitconnect/client/SubscriberClientTest.java @@ -3,51 +3,79 @@ package dev.fitko.fitconnect.client; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.jwk.RSAKey; +import dev.fitko.fitconnect.api.config.ApplicationConfig; +import dev.fitko.fitconnect.api.domain.model.event.Event; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; import dev.fitko.fitconnect.api.domain.model.event.EventPayload; +import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; +import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.AttachmentEncryptionIssue; +import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.IncorrectAttachmentAuthenticationTag; +import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.MissingAttachment; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataEncryptionIssue; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataHashMismatch; +import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.IncorrectMetadataAuthenticationTag; import dev.fitko.fitconnect.api.domain.model.event.problems.submission.InvalidEventLog; +import dev.fitko.fitconnect.api.domain.model.event.problems.submission.MissingAuthenticationTags; +import dev.fitko.fitconnect.api.domain.model.event.problems.submission.NotExactlyOneSubmitEvent; import dev.fitko.fitconnect.api.domain.model.metadata.ContentStructure; import dev.fitko.fitconnect.api.domain.model.metadata.Hash; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; import dev.fitko.fitconnect.api.domain.model.metadata.SignatureType; -import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; +import dev.fitko.fitconnect.api.exceptions.AuthenticationTagsEmptyException; import dev.fitko.fitconnect.api.exceptions.DecryptionException; import dev.fitko.fitconnect.api.exceptions.EventLogException; import dev.fitko.fitconnect.api.exceptions.RestApiException; +import dev.fitko.fitconnect.api.exceptions.SubmissionRequestException; +import dev.fitko.fitconnect.api.exceptions.SubmitEventNotFoundException; import dev.fitko.fitconnect.api.exceptions.ValidationException; import dev.fitko.fitconnect.api.services.Subscriber; import dev.fitko.fitconnect.api.services.crypto.CryptoService; -import dev.fitko.fitconnect.client.testutil.LogCaptor; +import dev.fitko.fitconnect.client.subscriber.SubmissionReceiver; import dev.fitko.fitconnect.core.crypto.HashService; import dev.fitko.fitconnect.core.crypto.JWECryptoService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.springframework.http.HttpStatus; import java.io.File; import java.io.IOException; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.text.ParseException; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; class SubscriberClientTest { private static final ObjectMapper mapper = new ObjectMapper(); - private final LogCaptor logs = new LogCaptor(); private Subscriber subscriberMock; private RSAKey privateKey; @@ -55,19 +83,20 @@ class SubscriberClientTest { @BeforeEach public void setup() throws IOException, ParseException { + final ApplicationConfig config = new ApplicationConfig(); privateKey = RSAKey.parse(getResourceAsString("private_decryption_test_key.json")); subscriberMock = Mockito.mock(Subscriber.class); - underTest = new SubscriberClient(subscriberMock, privateKey); + underTest = new SubscriberClient(subscriberMock, new SubmissionReceiver(subscriberMock, privateKey, config)); } @Test void testNoSubmissionsFound() { - assertThat(underTest.getAvailableSubmissions(null).isEmpty(), is(true)); - assertThat(underTest.getAvailableSubmissions(UUID.randomUUID()).isEmpty(), is(true)); + assertThat(underTest.getAvailableSubmissionsForDestination(null).isEmpty(), is(true)); + assertThat(underTest.getAvailableSubmissionsForDestination(UUID.randomUUID()).isEmpty(), is(true)); final var destinationId = UUID.randomUUID(); - when(subscriberMock.pollAvailableSubmissions(destinationId, 0, 5)).thenReturn(Collections.EMPTY_SET); - assertThat(underTest.getAvailableSubmissions(destinationId).isEmpty(), is(true)); + when(subscriberMock.pollAvailableSubmissionsForDestination(destinationId, 0, 5)).thenReturn(Collections.emptySet()); + assertThat(underTest.getAvailableSubmissionsForDestination(destinationId).isEmpty(), is(true)); } @Test @@ -76,31 +105,35 @@ class SubscriberClientTest { when(subscriberMock.getSubmission(any())).thenThrow(new RestApiException("Submission not found")); // When - final var receivedSubmission = underTest.requestSubmission(UUID.randomUUID()); + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> underTest.requestSubmission(UUID.randomUUID())); // Then - assertNull(receivedSubmission); - logs.assertContains("API request failed"); + assertThat(exception.getMessage(), containsString("Submission not found")); } @Test void testDecryptionFailed() { // Given final var submissionId = UUID.randomUUID(); + final var submission = new Submission(); submission.setSubmissionId(submissionId); submission.setEncryptedData("encryptedData"); submission.setEncryptedMetadata("encryptedMetadata"); + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("metadataAuthTags"); + authenticationTags.setData("dataAuthTags"); + when(subscriberMock.getSubmission(any())).thenReturn(submission); + when(subscriberMock.getAuthenticationTagsForEvent(Event.SUBMIT, submission)).thenReturn(authenticationTags); when(subscriberMock.decryptStringContent(any(), any())).thenThrow(new DecryptionException("Decryption failed")); // When - final var receivedSubmission = underTest.requestSubmission(submissionId); + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> underTest.requestSubmission(submissionId)); // Then - assertNull(receivedSubmission); - logs.assertContains("Decrypting payload failed"); + assertThat(exception.getMessage(), containsString("Decryption failed")); } @Test @@ -112,6 +145,7 @@ class SubscriberClientTest { submission.setSubmissionId(submissionId); submission.setCaseId(UUID.randomUUID()); submission.setDestinationId(UUID.randomUUID()); + submission.setEncryptedMetadata("abc"); final var hash = new Hash(); hash.setContent("hashedTestContent"); @@ -130,16 +164,24 @@ class SubscriberClientTest { final var metadata = new Metadata(); metadata.setContentStructure(contentStructure); + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("abc"); + authenticationTags.setData("def"); + when(subscriberMock.getSubmission(any())).thenReturn(submission); when(subscriberMock.decryptStringContent(any(), any())).thenReturn(mapper.writeValueAsBytes(metadata)); - when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateMetadata(any(), any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateData(any(), any(), any(), any())).thenReturn(ValidationResult.ok()); when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); + when(subscriberMock.validateAttachments(any(), any())).thenReturn(ValidationResult.ok()); + // When underTest.requestSubmission(submissionId).acceptSubmission(); // Then - verify(subscriberMock, times(1)).acceptSubmission(new EventPayload(submission)); + verify(subscriberMock, times(1)).acceptSubmission(EventPayload.forAcceptEvent(submission)); } @Test @@ -151,6 +193,7 @@ class SubscriberClientTest { submission.setSubmissionId(submissionId); submission.setCaseId(UUID.randomUUID()); submission.setDestinationId(UUID.randomUUID()); + submission.setEncryptedMetadata("abc"); final var hash = new Hash(); hash.setContent("hashedTestContent"); @@ -168,16 +211,24 @@ class SubscriberClientTest { final var metadata = new Metadata(); metadata.setContentStructure(contentStructure); + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("abc"); + authenticationTags.setData("def"); + when(subscriberMock.getSubmission(any())).thenReturn(submission); when(subscriberMock.decryptStringContent(any(), any())).thenReturn(mapper.writeValueAsBytes(metadata)); - when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateMetadata(any(), any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateData(any(), any(), any(), any())).thenReturn(ValidationResult.ok()); when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); + when(subscriberMock.validateAttachments(any(), any())).thenReturn(ValidationResult.ok()); + // When - underTest.requestSubmission(submissionId).rejectSubmission(List.of(new InvalidEventLog())); + underTest.requestSubmission(submissionId).rejectSubmission(List.of(new DataEncryptionIssue())); // Then - final var expectedEventPayload = new EventPayload(submission, List.of(new InvalidEventLog())); + final var expectedEventPayload = EventPayload.forRejectEvent(submission, List.of(new DataEncryptionIssue())); verify(subscriberMock, times(1)).rejectSubmission(expectedEventPayload); } @@ -185,20 +236,25 @@ class SubscriberClientTest { void testReadingMetadataFailed() { // Given final var submissionId = UUID.randomUUID(); + final var submission = new Submission(); submission.setSubmissionId(submissionId); submission.setEncryptedData("encryptedData"); submission.setEncryptedMetadata("encryptedMetadata"); + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("metadataAuthTags"); + authenticationTags.setData("dataAuthTags"); + when(subscriberMock.getSubmission(any())).thenReturn(submission); + when(subscriberMock.getAuthenticationTagsForEvent(Event.SUBMIT, submission)).thenReturn(authenticationTags); when(subscriberMock.decryptStringContent(any(), any())).thenReturn("encryptedMetadata".getBytes()); // When - final var receivedSubmission = underTest.requestSubmission(submissionId); + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> underTest.requestSubmission(submissionId)); // Then - assertNull(receivedSubmission); - logs.assertContains("Reading metadata failed"); + assertThat(exception.getMessage(), containsString("Unrecognized token 'encryptedMetadata'")); } @Test @@ -210,10 +266,10 @@ class SubscriberClientTest { submission.setSubmissionId(UUID.randomUUID()); submission.setDestinationId(destinationId); - when(subscriberMock.pollAvailableSubmissions(destinationId, 0, 1)).thenReturn(Set.of(submission)); + when(subscriberMock.pollAvailableSubmissionsForDestination(destinationId, 0, 1)).thenReturn(Set.of(submission)); // When - final Set<SubmissionForPickup> availableSubmissions = underTest.getAvailableSubmissions(destinationId, 0, 1); + final Set<SubmissionForPickup> availableSubmissions = underTest.getAvailableSubmissionsForDestination(destinationId, 0, 1); // Then assertThat(availableSubmissions.size(), is(1)); @@ -252,7 +308,7 @@ class SubscriberClientTest { final RSAKey encryptionKey = privateKey; final RSAKey decryptionKey = encryptionKey.toPublicJWK(); - final String encryptedData = cryptoService.encryptString(decryptionKey, dataPayload); + final String encryptedData = cryptoService.encryptObject(decryptionKey, dataPayload); final String encryptedMetadata = cryptoService.encryptBytes(decryptionKey, metadataBytes); final var submission = new Submission(); @@ -260,19 +316,26 @@ class SubscriberClientTest { submission.setEncryptedData(encryptedData); submission.setEncryptedMetadata(encryptedMetadata); + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setData(encryptedData.split(ApplicationConfig.AUTH_TAG_SPLIT_TOKEN)[4]); + authenticationTags.setMetadata(encryptedMetadata.split(ApplicationConfig.AUTH_TAG_SPLIT_TOKEN)[4]); + when(subscriberMock.getSubmission(any())).thenReturn(submission); - when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateMetadata(any(), any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateData(any(), any(), any(), any())).thenReturn(ValidationResult.ok()); when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateAttachments(any(), any())).thenReturn(ValidationResult.ok()); when(subscriberMock.decryptStringContent(encryptionKey, submission.getEncryptedData())).thenReturn(dataPayload.getBytes()); when(subscriberMock.decryptStringContent(encryptionKey, submission.getEncryptedMetadata())).thenReturn(metadataBytes); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); // When final var receivedSubmission = underTest.requestSubmission(submissionId); // Then assertNotNull(receivedSubmission); - assertThat(receivedSubmission.getData(), is(dataPayload)); - assertThat(receivedSubmission.getDataMimeType(), is(MimeType.APPLICATION_JSON)); + assertThat(receivedSubmission.getDataAsString(), is(dataPayload)); + assertThat(receivedSubmission.getDataMimeType(), is(MimeType.APPLICATION_JSON.value())); assertThat(receivedSubmission.getAttachments(), is(Collections.EMPTY_LIST)); } @@ -282,14 +345,14 @@ class SubscriberClientTest { when(subscriberMock.getSubmission(any())).thenThrow(new RestApiException("Submission not found")); // When - final var receivedSubmission = underTest.requestSubmission(UUID.randomUUID()); + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> underTest.requestSubmission(UUID.randomUUID())); // Then - assertNull(receivedSubmission); + assertThat(exception.getMessage(), containsString("Submission not found")); } @Test - void testInvalidMetadata() throws JsonProcessingException { + void testMetadataValidationFailed() throws JsonProcessingException { // Given final var submissionId = UUID.randomUUID(); final var destinationId = UUID.randomUUID(); @@ -299,19 +362,27 @@ class SubscriberClientTest { submission.setSubmissionId(submissionId); submission.setDestinationId(destinationId); submission.setCaseId(caseId); + submission.setEncryptedMetadata("abc"); + + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("abc"); final var invalidMetadata = mapper.writeValueAsBytes(new Metadata()); when(subscriberMock.getSubmission(any())).thenReturn(submission); when(subscriberMock.decryptStringContent(any(), any())).thenReturn(invalidMetadata); - when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.error(new ValidationException("Validation failed"))); + when(subscriberMock.validateMetadata(any(), any(), any())).thenReturn(ValidationResult.error(new ValidationException("Metadata does not match schema"))); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); + + final IncorrectMetadataAuthenticationTag problem = new IncorrectMetadataAuthenticationTag(); + when(subscriberMock.validateMetadata(any(), any(), any())).thenReturn(ValidationResult.problem(problem)); // When - final var receivedSubmission = underTest.requestSubmission(submissionId); + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> underTest.requestSubmission(submissionId)); // Then - assertNull(receivedSubmission); - logs.assertContains("Metadata does not match schema"); + assertThat(exception.getMessage(), containsString("Metadata is invalid")); + verify(subscriberMock, times(1)).rejectSubmission(EventPayload.forRejectEvent(submission, List.of(problem))); } @Test @@ -325,6 +396,7 @@ class SubscriberClientTest { submission.setSubmissionId(submissionId); submission.setDestinationId(destinationId); submission.setCaseId(caseId); + submission.setEncryptedMetadata("abc"); final Map<String, Map<?, ?>> metadata = Map.of( "$schemaFoo", Collections.emptyMap(), @@ -332,7 +404,7 @@ class SubscriberClientTest { "data", Map.of( "hash", Map.of( "type", SignatureType.SHA_512, - "content", "bla" + "content", "foo" ), "submissionSchema", Map.of( "schemaUri", URI.create("https://dummy.schema.url"), @@ -344,17 +416,24 @@ class SubscriberClientTest { final var invalidMetadata = mapper.writeValueAsBytes(metadata); + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("abc"); + authenticationTags.setData("def"); + when(subscriberMock.getSubmission(any())).thenReturn(submission); when(subscriberMock.decryptStringContent(any(), any())).thenReturn(invalidMetadata); - when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateMetadata(any(), any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateData(any(), any(), any(), any())).thenReturn(ValidationResult.ok()); when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); + when(subscriberMock.validateAttachments(any(), any())).thenReturn(ValidationResult.ok()); + // When final var receivedSubmission = underTest.requestSubmission(submissionId); // Then - assertNotNull(receivedSubmission); - assertThat(receivedSubmission.getSubmissionMetadata().getContentStructure().getData().getHash().getContent(), is("bla")); + assertThat(receivedSubmission.getMetadata().getContentStructure().getData().getHash().getContent(), is("foo")); } @Test @@ -387,7 +466,7 @@ class SubscriberClientTest { final RSAKey encryptionKey = privateKey; final RSAKey decryptionKey = encryptionKey.toPublicJWK(); - final String encryptedData = cryptoService.encryptString(decryptionKey, dataPayload); + final String encryptedData = cryptoService.encryptObject(decryptionKey, dataPayload); final String encryptedMetadata = cryptoService.encryptBytes(decryptionKey, metadataBytes); final var submission = new Submission(); @@ -395,22 +474,29 @@ class SubscriberClientTest { submission.setEncryptedData(encryptedData); submission.setEncryptedMetadata(encryptedMetadata); + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setData(encryptedData.split(ApplicationConfig.AUTH_TAG_SPLIT_TOKEN)[4]); + authenticationTags.setMetadata(encryptedMetadata.split(ApplicationConfig.AUTH_TAG_SPLIT_TOKEN)[4]); + when(subscriberMock.getSubmission(any())).thenReturn(submission); - when(subscriberMock.validateMetadata(any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateMetadata(any(), any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateData(any(), any(), any(), any())).thenReturn(ValidationResult.ok()); when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); when(subscriberMock.decryptStringContent(encryptionKey, submission.getEncryptedData())).thenReturn(dataPayload.getBytes()); when(subscriberMock.decryptStringContent(encryptionKey, submission.getEncryptedMetadata())).thenReturn(metadataBytes); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); + when(subscriberMock.validateAttachments(any(), any())).thenReturn(ValidationResult.ok()); // When final var receivedSubmission = underTest.requestSubmission(submissionId); // Then - assertNotNull(receivedSubmission.getSubmissionMetadata()); - assertThat(receivedSubmission.getSubmissionMetadata().getAdditionalReferenceInfo().getSenderReference(), is("sender_123")); + assertNotNull(receivedSubmission.getMetadata()); + assertThat(receivedSubmission.getMetadata().getAdditionalReferenceInfo().getSenderReference(), is("sender_123")); } @Test - void testCorruptedData() throws JsonProcessingException { + void testDataValidationFailed() throws JsonProcessingException { // Given final RSAKey decryptionKey = privateKey; @@ -428,8 +514,8 @@ class SubscriberClientTest { 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 String encryptedData = cryptoService.encryptObject(encryptionKey, mapper.writeValueAsString(data)); + final String encryptedMetadata = cryptoService.encryptObject(encryptionKey, mapper.writeValueAsString(metadata)); final var metadataBytes = mapper.writeValueAsBytes(metadata); final var dataBytes = mapper.writeValueAsBytes(data); @@ -444,22 +530,32 @@ class SubscriberClientTest { submission.setEncryptedMetadata(encryptedMetadata); submission.setEncryptedData(encryptedData); + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setData(encryptedData.split(ApplicationConfig.AUTH_TAG_SPLIT_TOKEN)[4]); + authenticationTags.setMetadata(encryptedMetadata.split(ApplicationConfig.AUTH_TAG_SPLIT_TOKEN)[4]); + 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.error(new ValidationException("Corrupt data hash"))); + when(subscriberMock.validateMetadata(any(), any(), any())).thenReturn(ValidationResult.ok()); + + final DataHashMismatch problem = new DataHashMismatch(); + when(subscriberMock.validateData(any(), any(), any(), any())).thenReturn(ValidationResult.problem(problem)); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); // When - final var receivedSubmission = underTest.requestSubmission(submissionId); + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> underTest.requestSubmission(submissionId)); // Then - assertNull(receivedSubmission); - logs.assertContains("Data might be corrupted, hash-sum does not match"); + assertThat(exception.getMessage(), containsString("Data is invalid")); + verify(subscriberMock, times(1)).rejectSubmission(EventPayload.forRejectEvent(submission, List.of(problem))); + } @Test - void testCorruptedAttachment() throws JsonProcessingException { + void testAttachmentValidationFailed() throws JsonProcessingException { // Given final RSAKey decryptionKey = privateKey; final RSAKey encryptionKey = decryptionKey.toPublicJWK(); @@ -472,7 +568,8 @@ class SubscriberClientTest { final var attachmentHash = new Hash(); attachmentHash.setContent(""); - final var attachment = new Attachment(); + final var attachment = new ApiAttachment(); + attachment.setAttachmentId(UUID.randomUUID()); attachment.setFilename("src/test/resources/attachment.txt"); attachment.setHash(attachmentHash); @@ -482,8 +579,8 @@ class SubscriberClientTest { 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 String encryptedData = cryptoService.encryptBytes(encryptionKey, mapper.writeValueAsString(data).getBytes()); + final String encryptedMetadata = cryptoService.encryptBytes(encryptionKey, mapper.writeValueAsString(metadata).getBytes()); final var metadataBytes = mapper.writeValueAsBytes(metadata); final var dataBytes = mapper.writeValueAsBytes(data); @@ -498,21 +595,154 @@ class SubscriberClientTest { submission.setEncryptedMetadata(encryptedMetadata); submission.setEncryptedData(encryptedData); + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setData(encryptedData.split(ApplicationConfig.AUTH_TAG_SPLIT_TOKEN)[4]); + authenticationTags.setMetadata(encryptedMetadata.split(ApplicationConfig.AUTH_TAG_SPLIT_TOKEN)[4]); + 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.validateMetadata(any(), any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); + when(subscriberMock.validateData(any(), any(), any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); - final var decryptedDataBytes = cryptoService.decryptBytes(decryptionKey, submission.getEncryptedData()); - when(subscriberMock.validateHashIntegrity(dataHash.getContent(), decryptedDataBytes)).thenReturn(ValidationResult.ok()); - when(subscriberMock.validateHashIntegrity(attachmentHash.getContent(), null)).thenReturn(ValidationResult.error(new ValidationException("Corrupt attachment"))); + final IncorrectAttachmentAuthenticationTag problem = new IncorrectAttachmentAuthenticationTag(attachment.getAttachmentId()); + when(subscriberMock.validateAttachments(anyList(), any())).thenReturn(ValidationResult.problem(problem)); // When - final var receivedSubmission = underTest.requestSubmission(submissionId); + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> underTest.requestSubmission(submissionId)); + + // Then + assertThat(exception.getMessage(), containsString("Attachment validation failed")); + verify(subscriberMock, times(1)).rejectSubmission(EventPayload.forRejectEvent(submission, List.of(problem))); + } + + @Test + void testMissingAttachment() throws Exception { + + // Given + final var dataPayload = "{ some : 'data foo' }"; + final var submissionId = UUID.randomUUID(); + final CryptoService cryptoService = new JWECryptoService(new HashService()); + + final Hash hash = new Hash(); + hash.setContent(cryptoService.hashBytes(dataPayload.getBytes())); + hash.setSignatureType(SignatureType.SHA_512); + + final SubmissionSchema schema = new SubmissionSchema(); + schema.setSchemaUri(URI.create("https://dummy.schema.url")); + schema.setMimeType(MimeType.APPLICATION_JSON); + + final Data data = new Data(); + data.setSubmissionSchema(schema); + data.setHash(hash); + + final ApiAttachment attachment = new ApiAttachment(); + attachment.setAttachmentId(UUID.randomUUID()); + + final ContentStructure contentStructure = new ContentStructure(); + contentStructure.setAttachments(List.of(attachment)); + contentStructure.setData(data); + + final Metadata metadata = new Metadata(); + metadata.setContentStructure(contentStructure); + + final byte[] metadataBytes = mapper.writeValueAsBytes(metadata); + + final RSAKey encryptionKey = privateKey; + final RSAKey decryptionKey = encryptionKey.toPublicJWK(); + + final String encryptedData = cryptoService.encryptBytes(decryptionKey, dataPayload.getBytes(StandardCharsets.UTF_8)); + final String encryptedMetadata = cryptoService.encryptBytes(decryptionKey, metadataBytes); + + final var submission = new Submission(); + submission.setSubmissionId(submissionId); + submission.setEncryptedData(encryptedData); + submission.setEncryptedMetadata(encryptedMetadata); + + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata(encryptedMetadata.split(ApplicationConfig.AUTH_TAG_SPLIT_TOKEN)[4]); + + when(subscriberMock.getSubmission(any())).thenReturn(submission); + when(subscriberMock.validateMetadata(any(), any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateData(any(), any(), any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.decryptStringContent(encryptionKey, submission.getEncryptedData())).thenReturn(dataPayload.getBytes()); + when(subscriberMock.decryptStringContent(encryptionKey, submission.getEncryptedMetadata())).thenReturn(metadataBytes); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); + when(subscriberMock.fetchAttachment(any(), any())).thenThrow(new RestApiException(HttpStatus.NOT_FOUND, "Attachment download failed")); + + // When + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> underTest.requestSubmission(submissionId)); + + // Then + assertThat(exception.getMessage(), containsString("Attachment download failed")); + verify(subscriberMock, times(1)).rejectSubmission(EventPayload.forRejectEvent(submission, List.of(new MissingAttachment(attachment.getAttachmentId())))); + } + + @Test + void testAttachmentEncryptionIssue() throws Exception { + + // Given + final var dataPayload = "{ some : 'data foo' }"; + final var submissionId = UUID.randomUUID(); + final CryptoService cryptoService = new JWECryptoService(new HashService()); + + final Hash hash = new Hash(); + hash.setContent(cryptoService.hashBytes(dataPayload.getBytes())); + hash.setSignatureType(SignatureType.SHA_512); + + final SubmissionSchema schema = new SubmissionSchema(); + schema.setSchemaUri(URI.create("https://dummy.schema.url")); + schema.setMimeType(MimeType.APPLICATION_JSON); + + final Data data = new Data(); + data.setSubmissionSchema(schema); + data.setHash(hash); + + final ApiAttachment attachment = new ApiAttachment(); + attachment.setAttachmentId(UUID.randomUUID()); + + final ContentStructure contentStructure = new ContentStructure(); + contentStructure.setAttachments(List.of(attachment)); + contentStructure.setData(data); + + final Metadata metadata = new Metadata(); + metadata.setContentStructure(contentStructure); + + final byte[] metadataBytes = mapper.writeValueAsBytes(metadata); + + final RSAKey encryptionKey = privateKey; + final RSAKey decryptionKey = encryptionKey.toPublicJWK(); + + final String encryptedData = cryptoService.encryptBytes(decryptionKey, dataPayload.getBytes(StandardCharsets.UTF_8)); + final String encryptedMetadata = cryptoService.encryptBytes(decryptionKey, metadataBytes); + + final var submission = new Submission(); + submission.setSubmissionId(submissionId); + submission.setEncryptedData(encryptedData); + submission.setEncryptedMetadata(encryptedMetadata); + + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata(encryptedMetadata.split(ApplicationConfig.AUTH_TAG_SPLIT_TOKEN)[4]); + + when(subscriberMock.getSubmission(any())).thenReturn(submission); + when(subscriberMock.validateMetadata(any(), any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateData(any(), any(), any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateHashIntegrity(any(), any())).thenReturn(ValidationResult.ok()); + when(subscriberMock.decryptStringContent(encryptionKey, submission.getEncryptedData())).thenReturn(dataPayload.getBytes()); + when(subscriberMock.decryptStringContent(encryptionKey, submission.getEncryptedMetadata())).thenReturn(metadataBytes); + when(subscriberMock.decryptStringContent(encryptionKey, "encrypt$dAtt@chm$nt")).thenThrow(new DecryptionException("Decrypting attachment failed")); + when(subscriberMock.getAuthenticationTagsForEvent(any(), any())).thenReturn(authenticationTags); + when(subscriberMock.fetchAttachment(any(), any())).thenReturn("encrypt$dAtt@chm$nt"); + + // When + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> underTest.requestSubmission(submissionId)); // Then - assertNull(receivedSubmission); - logs.assertContains("Attachment data for id null is corrupted"); + assertThat(exception.getMessage(), containsString("Decrypting attachment failed")); + verify(subscriberMock, times(1)).rejectSubmission(EventPayload.forRejectEvent(submission, List.of(new AttachmentEncryptionIssue(attachment.getAttachmentId())))); } @Test @@ -534,30 +764,73 @@ class SubscriberClientTest { } @Test - void testGetInvalidEventLog() { + void testNotExactlyOneSubmitEvent() { // Given - final var destinationId = UUID.randomUUID(); - final var caseId = UUID.randomUUID(); + final var submission = new Submission(); + submission.setSubmissionId(UUID.randomUUID()); + + final var expectedProblem = new NotExactlyOneSubmitEvent(); + + when(subscriberMock.getSubmission(submission.getSubmissionId())).thenReturn(submission); + when(subscriberMock.getAuthenticationTagsForEvent(Event.SUBMIT, submission)).thenThrow(new SubmitEventNotFoundException("no submit event in log")); + + // When + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> underTest.requestSubmission(submission.getSubmissionId())); + + // Then + assertThat(exception.getMessage(), containsString("The Event-Log is inconsistent because it does not contain exactly one 'submit' event.")); + verify(subscriberMock, times(1)).rejectSubmission(EventPayload.forRejectEvent(submission,List.of(expectedProblem))); + } - when(subscriberMock.getEventLog(any(), any())).thenThrow(new EventLogException("Invalid log")); + @Test + void testMissingAuthenticationTags() { + + // Given + final var submission = new Submission(); + submission.setSubmissionId(UUID.randomUUID()); + + final var expectedProblem = new MissingAuthenticationTags(); + + when(subscriberMock.getSubmission(submission.getSubmissionId())).thenReturn(submission); + when(subscriberMock.getAuthenticationTagsForEvent(Event.SUBMIT, submission)).thenThrow(new AuthenticationTagsEmptyException("empty auth tags")); // When - final EventLogException exception = assertThrows(EventLogException.class, () -> underTest.getEventLog(caseId, destinationId)); + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> underTest.requestSubmission(submission.getSubmissionId())); // Then - assertThat(exception.getMessage(), containsString("Invalid log")); + assertThat(exception.getMessage(), containsString("The 'submit-submission' event does not contain authentication tags")); + verify(subscriberMock, times(1)).rejectSubmission(EventPayload.forRejectEvent(submission,List.of(expectedProblem))); + } + + @Test + void testInvalidEventLog() { + // Given + final var submission = new Submission(); + submission.setSubmissionId(UUID.randomUUID()); + + final InvalidEventLog expectedProblem = new InvalidEventLog(); + + when(subscriberMock.getSubmission(submission.getSubmissionId())).thenReturn(submission); + when(subscriberMock.getAuthenticationTagsForEvent(Event.SUBMIT, submission)).thenThrow(new EventLogException("Invalid log")); + + // When + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> underTest.requestSubmission(submission.getSubmissionId())); + + // Then + assertThat(exception.getMessage(), containsString("The Event-Log is inconsistent")); + verify(subscriberMock, times(1)).rejectSubmission(EventPayload.forRejectEvent(submission,List.of(expectedProblem))); } @Test void validateCallback() { - when(this.subscriberMock.validateCallback(anyString(), anyLong(), anyString(), anyString())).thenReturn(ValidationResult.ok()); + when(subscriberMock.validateCallback(anyString(), anyLong(), anyString(), anyString())).thenReturn(ValidationResult.ok()); - ValidationResult validationResult = this.underTest.validateCallback("hmac", 0L, "body", "secret"); + final ValidationResult validationResult = underTest.validateCallback("hmac", 0L, "body", "secret"); - verify(this.subscriberMock, times(1)).validateCallback(anyString(), anyLong(), anyString(), anyString()); + verify(subscriberMock, times(1)).validateCallback(anyString(), anyLong(), anyString(), anyString()); assertTrue(validationResult.isValid()); assertFalse(validationResult.hasError()); } diff --git a/client/src/test/java/dev/fitko/fitconnect/client/cli/CommandLineClientTest.java b/client/src/test/java/dev/fitko/fitconnect/client/cli/CommandLineClientTest.java index 61e54c21aca6754a05348568d776afe5dc1b1802..2110fad5c7185ee8039ce5a31a452d84f7951a7a 100644 --- a/client/src/test/java/dev/fitko/fitconnect/client/cli/CommandLineClientTest.java +++ b/client/src/test/java/dev/fitko/fitconnect/client/cli/CommandLineClientTest.java @@ -10,10 +10,10 @@ import dev.fitko.fitconnect.client.SenderClient; import dev.fitko.fitconnect.client.SubscriberClient; import dev.fitko.fitconnect.client.cli.batch.CsvImporter; import dev.fitko.fitconnect.client.cli.batch.ImportRecord; -import dev.fitko.fitconnect.client.sender.model.EncryptedSubmissionPayload; -import dev.fitko.fitconnect.client.sender.model.SubmissionPayload; +import dev.fitko.fitconnect.client.sender.model.Attachment; +import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission; +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission; -import dev.fitko.fitconnect.client.subscriber.model.ReceivedAttachment; import dev.fitko.fitconnect.client.subscriber.model.ReceivedData; import dev.fitko.fitconnect.client.testutil.LogCaptor; import org.junit.jupiter.api.BeforeEach; @@ -23,6 +23,7 @@ import org.junit.jupiter.api.io.TempDir; import java.nio.file.Path; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; @@ -62,7 +63,7 @@ class CommandLineClientTest { // Given final var destinationId = UUID.randomUUID(); final Set<SubmissionForPickup> submissions = generateSubmissions(destinationId, 10); - when(subscriberClientMock.getAvailableSubmissions(destinationId)).thenReturn(submissions); + when(subscriberClientMock.getAvailableSubmissionsForDestination(destinationId)).thenReturn(submissions); // When underTest.run("list", "--destinationId=" + destinationId); @@ -78,14 +79,11 @@ class CommandLineClientTest { // Given final var submissionId = UUID.randomUUID(); - final ReceivedAttachment attachment = ReceivedAttachment.builder() - .data("attachment text".getBytes()) - .filename("testAttachment.txt") - .build(); + final Attachment attachment = Attachment.fromString("attachment text", "text/plain", "testAttachment.txt", "test"); final var expectedSubmission = new Submission(); final var data = new ReceivedData("{ \"test\" : \"data\"", MimeType.APPLICATION_JSON); - final var receivedSubmission = new ReceivedSubmission(subscriberMock, expectedSubmission, new Metadata(), data, List.of(attachment) ); + final var receivedSubmission = new ReceivedSubmission(subscriberMock, expectedSubmission, new Metadata(), data, List.of(attachment), Map.of(UUID.randomUUID(), "attachmentAuthTag")); when(subscriberClientMock.requestSubmission(submissionId)).thenReturn(receivedSubmission); @@ -109,7 +107,7 @@ class CommandLineClientTest { .submissionId(UUID.randomUUID()) .build(); - when(senderClientMock.submit(any(SubmissionPayload.class))).thenReturn(expectedSubmission); + when(senderClientMock.send(any(SendableSubmission.class))).thenReturn(expectedSubmission); // When underTest.run("send", @@ -132,8 +130,8 @@ class CommandLineClientTest { final var testDataPath = "src/test/resources/batch_data.csv"; final List<ImportRecord> importRecords = new CsvImporter().readRecords(testDataPath); - when(senderClientMock.submit(any(EncryptedSubmissionPayload.class))).thenReturn(SentSubmission.builder().build()); - when(senderClientMock.submit(any(SubmissionPayload.class))).thenReturn(SentSubmission.builder().build()); + when(senderClientMock.send(any(SendableEncryptedSubmission.class))).thenReturn(SentSubmission.builder().build()); + when(senderClientMock.send(any(SendableSubmission.class))).thenReturn(SentSubmission.builder().build()); // When underTest.run("batch", "--data=" + testDataPath); diff --git a/client/src/test/java/dev/fitko/fitconnect/client/cli/util/UUIDConverterTest.java b/client/src/test/java/dev/fitko/fitconnect/client/cli/util/UUIDConverterTest.java index 65877056a6381a1a86a9a378d6defa576d1f1996..704c05c06b26aacd7221d6be48b1b79d4cd3e991 100644 --- a/client/src/test/java/dev/fitko/fitconnect/client/cli/util/UUIDConverterTest.java +++ b/client/src/test/java/dev/fitko/fitconnect/client/cli/util/UUIDConverterTest.java @@ -5,7 +5,9 @@ import org.junit.jupiter.api.Test; import java.util.UUID; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; class UUIDConverterTest { diff --git a/client/src/test/java/dev/fitko/fitconnect/client/factory/ApplicationConfigLoaderTest.java b/client/src/test/java/dev/fitko/fitconnect/client/factory/ApplicationConfigLoaderTest.java index cbba30055796071e5784dc7821434c985a3c0308..180a52b2db29c1dc95e5e7fb174b2d6f935ec127 100644 --- a/client/src/test/java/dev/fitko/fitconnect/client/factory/ApplicationConfigLoaderTest.java +++ b/client/src/test/java/dev/fitko/fitconnect/client/factory/ApplicationConfigLoaderTest.java @@ -16,14 +16,17 @@ import java.nio.file.Path; import java.util.Objects; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertNotNull; class ApplicationConfigLoaderTest { @Test void testLoadMissingConfigFile() { - Assertions.assertThrows(InitializationException.class, () -> ApplicationConfigLoader.loadConfig("/no/path")); + Assertions.assertThrows(InitializationException.class, () -> ApplicationConfigLoader.loadConfigFromPath(Path.of("/no/path"))); } @Test @@ -31,7 +34,7 @@ class ApplicationConfigLoaderTest { // Given final String testConfigYaml = getResourceAsString("test-config-with-missing-env.yml"); - final ApplicationConfig config = ApplicationConfigLoader.loadConfigFromYaml(testConfigYaml); + final ApplicationConfig config = ApplicationConfigLoader.loadConfigFromYamlString(testConfigYaml); //When final InitializationException exception = Assertions.assertThrowsExactly(InitializationException.class, config::getCurrentEnvironment); @@ -57,7 +60,7 @@ class ApplicationConfigLoaderTest { final SenderConfig senderConfig = new SenderConfig("1", "123"); // When - final ApplicationConfig testConfig = ApplicationConfigLoader.loadConfigFromYaml(testConfigYaml); + final ApplicationConfig testConfig = ApplicationConfigLoader.loadConfigFromYamlString(testConfigYaml); // Then assertNotNull(testConfig); @@ -74,13 +77,28 @@ class ApplicationConfigLoaderTest { final Path configPath = Files.writeString(Path.of(tempDir.toString(), "test-config.yml"), testConfigYaml); // When - final ApplicationConfig testConfig = ApplicationConfigLoader.loadConfig(configPath); + final ApplicationConfig testConfig = ApplicationConfigLoader.loadConfigFromPath(configPath); // Then assertNotNull(testConfig); assertThat(testConfig.getActiveEnvironment(), is(new EnvironmentName("dev"))); } + @Test + void testSubscriberConfigContainsListOfPrivateDecryptionKeys(@TempDir final Path tempDir) throws IOException { + + // Given + final String testConfigYaml = getResourceAsString("test-config.yml"); + final Path configPath = Files.writeString(Path.of(tempDir.toString(), "test-config.yml"), testConfigYaml); + + // When + final ApplicationConfig testConfig = ApplicationConfigLoader.loadConfigFromPath(configPath); + + // Then + assertNotNull(testConfig); + assertThat(testConfig.getSubscriberConfig().getPrivateDecryptionKeyPaths(), hasSize(2)); + } + private String getResourceAsString(final String name) throws IOException { final ClassLoader classLoader = getClass().getClassLoader(); final File file = new File(Objects.requireNonNull(classLoader.getResource(name)).getFile()); diff --git a/client/src/test/java/dev/fitko/fitconnect/client/factory/ClientFactoryTest.java b/client/src/test/java/dev/fitko/fitconnect/client/factory/ClientFactoryTest.java index 9c1c539a6704aee684d822e66952c3d384d3f36c..e268031fc85cc989603296cb19ae93f5a13ca208 100644 --- a/client/src/test/java/dev/fitko/fitconnect/client/factory/ClientFactoryTest.java +++ b/client/src/test/java/dev/fitko/fitconnect/client/factory/ClientFactoryTest.java @@ -1,12 +1,19 @@ package dev.fitko.fitconnect.client.factory; -import dev.fitko.fitconnect.api.config.*; +import dev.fitko.fitconnect.api.config.ApplicationConfig; +import dev.fitko.fitconnect.api.config.Environment; +import dev.fitko.fitconnect.api.config.EnvironmentName; +import dev.fitko.fitconnect.api.config.SenderConfig; +import dev.fitko.fitconnect.api.config.SubscriberConfig; import dev.fitko.fitconnect.api.exceptions.InitializationException; import dev.fitko.fitconnect.api.exceptions.InvalidKeyException; import org.junit.jupiter.api.Test; +import java.util.List; import java.util.Map; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -14,9 +21,9 @@ class ClientFactoryTest { @Test void testMissingConfiguration() { - assertThrows(InitializationException.class, ClientFactory::senderClient); - assertThrows(InitializationException.class, ClientFactory::subscriberClient); - assertThrows(InitializationException.class, ClientFactory::routingClient); + assertThrows(InitializationException.class, () -> ClientFactory.getSenderClient(null)); + assertThrows(InitializationException.class, () -> ClientFactory.getSubscriberClient(null)); + assertThrows(InitializationException.class, () -> ClientFactory.getRoutingClient(null)); } @Test @@ -35,7 +42,7 @@ class ClientFactoryTest { .activeEnvironment(envName) .build(); - assertNotNull(ClientFactory.senderClient(senderConfig)); + assertNotNull(ClientFactory.getSenderClient(senderConfig)); } @Test @@ -47,7 +54,7 @@ class ClientFactoryTest { final var subscriber = SubscriberConfig.builder() .clientSecret("123") .clientSecret("abc") - .privateDecryptionKeyPath("src/test/resources/private_decryption_test_key.json") + .privateDecryptionKeyPaths(List.of("src/test/resources/private_decryption_test_key.json")) .privateSigningKeyPath("src/test/resources/private_test_signing_key.json") .build(); @@ -59,7 +66,7 @@ class ClientFactoryTest { .activeEnvironment(envName) .build(); - assertNotNull(ClientFactory.subscriberClient(subscriberConfig)); + assertNotNull(ClientFactory.getSubscriberClient(subscriberConfig)); } @Test @@ -76,7 +83,7 @@ class ClientFactoryTest { .senderConfig(senderConfig) .build(); - assertNotNull(ClientFactory.routingClient(routingConfig)); + assertNotNull(ClientFactory.getRoutingClient(routingConfig)); } @Test @@ -88,7 +95,7 @@ class ClientFactoryTest { final var subscriberConfig = SubscriberConfig.builder() .clientSecret("123") .clientSecret("abc") - .privateDecryptionKeyPath("src/test/resources/private_decryption_test_key.json") + .privateDecryptionKeyPaths(List.of("src/test/resources/private_decryption_test_key.json")) .privateSigningKeyPath("src/test/resources/invalid_signing_key.json") .build(); @@ -102,7 +109,7 @@ class ClientFactoryTest { assertThrows( InvalidKeyException.class, - () -> ClientFactory.subscriberClient(config), + () -> ClientFactory.getSubscriberClient(config), "Expected new ClientFactoryTest() to throw, but nothing was thrown" ); } @@ -116,7 +123,7 @@ class ClientFactoryTest { final var subscriberConfigWithoutKey = SubscriberConfig.builder() .clientSecret("123") .clientSecret("abc") - .privateDecryptionKeyPath("src/test/resources/invalid_private_key.json") + .privateDecryptionKeyPaths(List.of("src/test/resources/invalid_private_key.json")) .privateSigningKeyPath("src/test/resources/private_test_signing_key.json") .build(); @@ -130,8 +137,34 @@ class ClientFactoryTest { assertThrows( InvalidKeyException.class, - () -> ClientFactory.subscriberClient(config), - "Expected new ClientFactoryTest() to throw, but nothing was thrown" - ); + () -> ClientFactory.getSubscriberClient(config)); + } + + @Test + void testMultipleDecryptionKeysNotYetSupported() { + + final var envName = new EnvironmentName("DEV"); + final var environments = Map.of(envName, new Environment("https://auth", "", "", "", true)); + + final var subscriberConfigWithoutKey = SubscriberConfig.builder() + .clientSecret("123") + .clientSecret("abc") + .privateDecryptionKeyPaths(List.of("src/test/resources/private_decryption_test_key.json", "src/test/resources/private_decryption_test_key.json")) + .privateSigningKeyPath("src/test/resources/private_test_signing_key.json") + .build(); + + final var config = ApplicationConfig.builder() + .httpProxyHost("https://proxy.fitco.de") + .httpProxyPort(0) + .subscriberConfig(subscriberConfigWithoutKey) + .environments(environments) + .activeEnvironment(envName) + .build(); + + final InitializationException exception = assertThrows( + InitializationException.class, + () -> ClientFactory.getSubscriberClient(config)); + + assertThat(exception.getMessage(), containsString("Currently only one configured private key per subscriber is allowed !")); } } \ No newline at end of file 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 6dc3c86998232b50665f09785a5620966551a137..f596269773f80f821396208409a9ec79751ccc59 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 @@ -6,10 +6,8 @@ import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; import dev.fitko.fitconnect.api.services.Sender; -import dev.fitko.fitconnect.client.sender.EncryptedSubmissionBuilder; -import dev.fitko.fitconnect.client.sender.SubmissionBuilder; -import dev.fitko.fitconnect.client.sender.model.EncryptedSubmissionPayload; -import dev.fitko.fitconnect.client.sender.model.SubmissionPayload; +import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission; +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -37,7 +35,7 @@ class ValidDataGuardTest { } @Nested - class SubmissionPayloadDataTests { + class SendableSubmissionDataTests { @Test void testValidSubmissionPayload() { @@ -58,29 +56,28 @@ class ValidDataGuardTest { when(senderMock.getDestination(any())).thenReturn(destination); when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); - final SubmissionPayload submissionPayload = SubmissionBuilder.Builder() - .withJsonData("\"test\": \"json\"", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.randomUUID()) - .withServiceType("Test", "urn:de:fim:leika:leistung:99400048079000") + final SendableSubmission sendableSubmission = SendableSubmission.Builder() + .setDestination(UUID.randomUUID()) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test") + .setJsonData("\"test\": \"json\"", URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) .build(); // Then valid if no exception is thrown - underTest.ensureValidDataPayload(submissionPayload); + underTest.ensureValidDataPayload(sendableSubmission); } @Test void testMissingJsonData() { // Given - final SubmissionPayload submissionPayload = SubmissionBuilder.Builder() - .withJsonData(null, URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.randomUUID()) - .withServiceType("Test", "urn:de:fim:leika:leistung:99400048079000") + final SendableSubmission sendableSubmission = SendableSubmission.Builder() + .setDestination(UUID.randomUUID()) + .setServiceType("Test", "urn:de:fim:leika:leistung:99400048079000") + .setJsonData(null, URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) .build(); // When - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(submissionPayload)); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(sendableSubmission)); // Then assertThat(exception.getMessage(), containsString("Data is mandatory, but was null")); @@ -90,14 +87,14 @@ class ValidDataGuardTest { void testMissingXmlData() { // Given - final SubmissionPayload submissionPayload = SubmissionBuilder.Builder() - .withXmlData(null, URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.randomUUID()) - .withServiceType("Test", "urn:de:fim:leika:leistung:99400048079000") + final SendableSubmission sendableSubmission = SendableSubmission.Builder() + .setDestination(UUID.randomUUID()) + .setServiceType("Test", "urn:de:fim:leika:leistung:99400048079000") + .setXmlData(null, URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) .build(); // When - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(submissionPayload)); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(sendableSubmission)); // Then assertThat(exception.getMessage(), containsString("Data is mandatory, but was null")); @@ -109,14 +106,14 @@ class ValidDataGuardTest { // Given when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); - final SubmissionPayload submissionPayload = SubmissionBuilder.Builder() - .withJsonData("{}", URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(null) - .withServiceType("name", "urn:de:fim:leika:leistung:99400048079000") + final SendableSubmission sendableSubmission = SendableSubmission.Builder() + .setDestination(null) + .setServiceType("name", "urn:de:fim:leika:leistung:99400048079000") + .setJsonData("{}", URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) .build(); // When - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(submissionPayload)); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(sendableSubmission)); // Then assertThat(exception.getMessage(), containsString("DestinationId is mandatory, but was null")); @@ -128,14 +125,13 @@ class ValidDataGuardTest { // Given when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); - final SubmissionPayload submissionPayload = SubmissionBuilder.Builder() - .withJsonData("{\"test\" . \"data\"}", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.randomUUID()) - .withServiceType("name", "illegal:test:identifier") + final SendableSubmission sendableSubmission = SendableSubmission.Builder() + .setDestination(UUID.randomUUID()) + .setServiceType("name", "illegal:test:identifier") + .setJsonData("{\"test\" . \"data\"}", URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json"))) .build(); // When - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(submissionPayload)); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(sendableSubmission)); // Then assertThat(exception.getMessage(), containsString("LeikaKey has invalid format")); @@ -147,14 +143,13 @@ class ValidDataGuardTest { // Given when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); - final SubmissionPayload submissionPayload = SubmissionBuilder.Builder() - .withJsonData("{\"test\" . \"data\"}", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.randomUUID()) - .withServiceType("name", null) + final SendableSubmission sendableSubmission = SendableSubmission.Builder() + .setDestination(UUID.randomUUID()) + .setServiceType(null, "name") + .setJsonData("{\"test\" . \"data\"}", URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) .build(); // When - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(submissionPayload)); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(sendableSubmission)); // Then assertThat(exception.getMessage(), containsString("Leika key is mandatory, but was null")); @@ -173,14 +168,13 @@ class ValidDataGuardTest { when(senderMock.getDestination(any())).thenReturn(destination); when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); - final SubmissionPayload submissionPayload = SubmissionBuilder.Builder() - .withJsonData("\"test\": \"json\"", - URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.randomUUID()) - .withServiceType("Test", "urn:de:fim:leika:leistung:123456789101114") + final SendableSubmission sendableSubmission = SendableSubmission.Builder() + .setDestination(UUID.randomUUID()) + .setServiceType("urn:de:fim:leika:leistung:123456789101114", "Test") + .setJsonData("\"test\": \"json\"", URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) .build(); // When - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(submissionPayload)); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(sendableSubmission)); // Then assertThat(exception.getMessage(), containsString("Provided service type 'urn:de:fim:leika:leistung:123456789101114' is not allowed by the destination")); @@ -253,7 +247,7 @@ class ValidDataGuardTest { @Nested - class EncryptedSubmissionPayloadDataTests { + class SendableEncryptedSubmissionDataTests { @Test @@ -270,30 +264,30 @@ class ValidDataGuardTest { when(senderMock.getDestination(any())).thenReturn(destination); when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); - final EncryptedSubmissionPayload encryptedSubmissionPayload = EncryptedSubmissionBuilder.Builder() - .withEncryptedData("4Y0sJhadfrQnNZXeS7Pqh73FvtF") - .withEncryptedMetadata("4Y0sJhadfrQnNZXeS7Pqh73FvtF4Y0sJhadfrQnNZXeS7Pqh73FvtF") - .withDestination(UUID.randomUUID()) - .withServiceType("Test", "urn:de:fim:leika:leistung:99400048079000") + final SendableEncryptedSubmission sendableEncryptedSubmission = SendableEncryptedSubmission.Builder() + .setDestination(UUID.randomUUID()) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "TestService") + .setEncryptedMetadata("4Y0sJhadfrQnNZXeS7Pqh73FvtF4Y0sJhadfrQnNZXeS7Pqh73FvtF") + .setEncryptedData("4Y0sJhadfrQnNZXeS7Pqh73FvtF") .build(); // Then valid if no exception is thrown - underTest.ensureValidDataPayload(encryptedSubmissionPayload); + underTest.ensureValidDataPayload(sendableEncryptedSubmission); } @Test void testMissingEncryptedData() { // Given - final EncryptedSubmissionPayload encryptedSubmissionPayload = EncryptedSubmissionBuilder.Builder() - .withEncryptedData(null) - .withEncryptedMetadata("4Y0sJhadfrQnNZXeS7Pqh73FvtF4Y0sJhadfrQnNZXeS7Pqh73FvtF") - .withDestination(UUID.randomUUID()) - .withServiceType("Test", "urn:de:fim:leika:leistung:99400048079000") + final SendableEncryptedSubmission sendableEncryptedSubmission = SendableEncryptedSubmission.Builder() + .setDestination(UUID.randomUUID()) + .setServiceType("Test", "urn:de:fim:leika:leistung:99400048079000") + .setEncryptedMetadata("4Y0sJhadfrQnNZXeS7Pqh73FvtF4Y0sJhadfrQnNZXeS7Pqh73FvtF") + .setEncryptedData(null) .build(); // When - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(encryptedSubmissionPayload)); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(sendableEncryptedSubmission)); // Then assertThat(exception.getMessage(), containsString("Encrypted data is mandatory, but was null")); @@ -303,15 +297,15 @@ class ValidDataGuardTest { void testMissingEncryptedMetadata() { // Given - final EncryptedSubmissionPayload encryptedSubmissionPayload = EncryptedSubmissionBuilder.Builder() - .withEncryptedData("4Y0sJhadfrQnNZXeS7Pqh73FvtF") - .withEncryptedMetadata(null) - .withDestination(UUID.randomUUID()) - .withServiceType("Test", "urn:de:fim:leika:leistung:99400048079000") + final SendableEncryptedSubmission sendableEncryptedSubmission = SendableEncryptedSubmission.Builder() + .setDestination(UUID.randomUUID()) + .setServiceType("Test", "urn:de:fim:leika:leistung:99400048079000") + .setEncryptedMetadata(null) + .setEncryptedData("4Y0sJhadfrQnNZXeS7Pqh73FvtF") .build(); // When - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(encryptedSubmissionPayload)); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(sendableEncryptedSubmission)); // Then assertThat(exception.getMessage(), containsString("Encrypted metadata must not be null")); @@ -323,15 +317,15 @@ class ValidDataGuardTest { // Given when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); - final EncryptedSubmissionPayload encryptedSubmissionPayload = EncryptedSubmissionBuilder.Builder() - .withEncryptedData("4Y0sJhadfrQnNZXeS7Pqh73FvtF") - .withEncryptedMetadata("4Y0sJhadfrQnNZXeS7Pqh73FvtF4Y0sJhadfrQnNZXeS7Pqh73FvtF") - .withDestination(null) - .withServiceType("name", "urn:de:fim:leika:leistung:99400048079000") + final SendableEncryptedSubmission sendableEncryptedSubmission = SendableEncryptedSubmission.Builder() + .setDestination(null) + .setServiceType("name", "urn:de:fim:leika:leistung:99400048079000") + .setEncryptedMetadata("4Y0sJhadfrQnNZXeS7Pqh73FvtF4Y0sJhadfrQnNZXeS7Pqh73FvtF") + .setEncryptedData("4Y0sJhadfrQnNZXeS7Pqh73FvtF") .build(); // When - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(encryptedSubmissionPayload)); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(sendableEncryptedSubmission)); // Then assertThat(exception.getMessage(), containsString("DestinationId is mandatory, but was null")); @@ -343,15 +337,15 @@ class ValidDataGuardTest { // Given when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); - final EncryptedSubmissionPayload encryptedSubmissionPayload = EncryptedSubmissionBuilder.Builder() - .withEncryptedData("4Y0sJhadfrQnNZXeS7Pqh73FvtF") - .withEncryptedMetadata("4Y0sJhadfrQnNZXeS7Pqh73FvtF4Y0sJhadfrQnNZXeS7Pqh73FvtF") - .withDestination(UUID.randomUUID()) - .withServiceType("name", "illegal:test:identifier") + final SendableEncryptedSubmission sendableEncryptedSubmission = SendableEncryptedSubmission.Builder() + .setDestination(UUID.randomUUID()) + .setServiceType("name", "illegal:test:identifier") + .setEncryptedMetadata("4Y0sJhadfrQnNZXeS7Pqh73FvtF4Y0sJhadfrQnNZXeS7Pqh73FvtF") + .setEncryptedData("4Y0sJhadfrQnNZXeS7Pqh73FvtF") .build(); // When - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(encryptedSubmissionPayload)); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(sendableEncryptedSubmission)); // Then assertThat(exception.getMessage(), containsString("LeikaKey has invalid format")); @@ -363,14 +357,14 @@ class ValidDataGuardTest { // Given when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); - final EncryptedSubmissionPayload encryptedSubmissionPayload = EncryptedSubmissionBuilder.Builder() - .withEncryptedData("4Y0sJhadfrQnNZXeS7Pqh73FvtF") - .withEncryptedMetadata("4Y0sJhadfrQnNZXeS7Pqh73FvtF4Y0sJhadfrQnNZXeS7Pqh73FvtF") - .withDestination(UUID.randomUUID()) - .withServiceType("name", null) + final SendableEncryptedSubmission sendableEncryptedSubmission = SendableEncryptedSubmission.Builder() + .setDestination(UUID.randomUUID()) + .setServiceType(null, "name") + .setEncryptedMetadata("4Y0sJhadfrQnNZXeS7Pqh73FvtF4Y0sJhadfrQnNZXeS7Pqh73FvtF") + .setEncryptedData("4Y0sJhadfrQnNZXeS7Pqh73FvtF") .build(); // When - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(encryptedSubmissionPayload)); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(sendableEncryptedSubmission)); // Then assertThat(exception.getMessage(), containsString("Leika key is mandatory, but was null")); @@ -389,15 +383,15 @@ class ValidDataGuardTest { when(senderMock.getDestination(any())).thenReturn(destination); when(senderMock.validateJsonFormat(any())).thenReturn(ValidationResult.ok()); - final EncryptedSubmissionPayload encryptedSubmissionPayload = EncryptedSubmissionBuilder.Builder() - .withEncryptedData("4Y0sJhadfrQnNZXeS7Pqh73FvtF") - .withEncryptedMetadata("4Y0sJhadfrQnNZXeS7Pqh73FvtF4Y0sJhadfrQnNZXeS7Pqh73FvtF") - .withDestination(UUID.randomUUID()) - .withServiceType("name", "urn:de:fim:leika:leistung:11111111111111") + final SendableEncryptedSubmission sendableEncryptedSubmission = SendableEncryptedSubmission.Builder() + .setDestination(UUID.randomUUID()) + .setServiceType("urn:de:fim:leika:leistung:11111111111111", "name") + .setEncryptedMetadata("4Y0sJhadfrQnNZXeS7Pqh73FvtF4Y0sJhadfrQnNZXeS7Pqh73FvtF") + .setEncryptedData("4Y0sJhadfrQnNZXeS7Pqh73FvtF") .build(); // When - final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(encryptedSubmissionPayload)); + final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> underTest.ensureValidDataPayload(sendableEncryptedSubmission)); // Then assertThat(exception.getMessage(), containsString("Provided service type 'urn:de:fim:leika:leistung:11111111111111' is not allowed by the destination")); @@ -415,10 +409,10 @@ class ValidDataGuardTest { // When final IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> - SubmissionBuilder.Builder() - .withJsonData("{}", URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) - .withDestination(UUID.fromString(invalidUUID)) - .withServiceType("name", "test:key") + SendableSubmission.Builder() + .setDestination(UUID.fromString(invalidUUID)) + .setServiceType("name", "test:key") + .setJsonData("{}", URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")) .build()); // Then diff --git a/client/src/test/resources/private_decryption_test_key.json b/client/src/test/resources/private_decryption_test_key.json index 9be02d7261d9283ac546d8e3eaee54deb84693e9..c5c076ebf299198b47a5f91e457821d52ef36c88 100644 --- a/client/src/test/resources/private_decryption_test_key.json +++ b/client/src/test/resources/private_decryption_test_key.json @@ -1 +1,16 @@ -{"alg":"RSA-OAEP-256","d":"UuZ7DtiI46pxCeACoP6cDxWBT2PJea6Td5eTU2sm-jkGH1O_ShgBtqHI6KwOLenO0Z1GlHoVNzIx9NeazUpVdN1Oan42ximA6LXqP6GaaWD7qJmzuSER968F4StW7hv5aI1N35mFLESY2ENISfhmRGBsCalRrQVFU5lRwjH22m_JTdeMeRbCCZHxynH2GcOwei8lZgWTiu3cIz7giEwc3GrZHZMxhdBLBCJ3gxDer9O_V0UCr7W7VpLpFuaOZmKaB2io-Rgz5W71IIo-09IghWnV5VZWpXr1Dl1_UbhE9UK_N6gQ9HcFopBQR6huD2svUO2aVig3RLO1KG8AL6vQsp8uIa_7_TuVYnuo7S_zVRlungjs2UzABjRqsdUED9C-FmwpnbKcHA1V99U8Ag22VHXm0zcT58M09-_yUl9s4VG6y-yQEhfxTjL0LMtSL8XScRwf38mpgX54LFGOeImo8d6jFGbNMzMViStejmXU8nCBeRmEtHpDU-GxJSPhO0ZJNgNWH_RU-tmujodXxXvyp7yA3MFqvcKxUQGf73lz_y10lLSmAJSlG-3KYyHY1N5AlXF4m0N3yENcim3VPVZarpOgHfEF3gg3slthAFZqTeY2WKMBp2O_QBvyf3mylVKHECzY4XzScBiPA9qlbeRDoa-NxcQwT2bS_1cyk8_IYG8","dp":"-sRGGYORpYDRglqvK9RX6FCQUpjCwbyAeq3yU4sEnC2XWt37-bw-p0KzSs7OuTTSPNH5I9HrfAzVHPzGXWE_CJmkdvkGqLkmvFTFiuGY1yk3psYvLwzDPJjyKUzdI-GMdioL_IyjEfna2TxdiYLyzGCDtGJxrSVyunc2xcMx_k1u_HQcylog00P61kCnUVtGsDqMbYsShCQaZXwQK3wPL6KkjgVLtEYYYeP8NiJRL_XP8SCD4ebU_cJMLDWgq1DgxpfqIBz0fCNe0ty9LwEhMhmP6OpmU5mRAGzXNKOp_lYYEVmvRoEwcRyFdM1W4N61eu8EYGyfzTrH1gfG8FMLfQ","dq":"uC9tAhQI7vRE2isCeoxY3YgJd2RBOdRrIjowUoS7WuEho_aLKWgoJDSzZoXad9pindjaSRj9CRKZ7oRW1Dp0NQwTQdUsyZSZt4cB5yxg50ty5IE81XDX0pgyRCJOXNpKxSZYtcBUzqlId4dCRd2codqaKJkP77K0TmWlQve_CoCPuaSP4CGqt8GpQj1fKzI_NYYqNVk-1jSAa9PZ2E-gW0iJwQoUNkDRTBgIVLx1qu7aYieYeif8oTFjKGKkP3EXCTGXKSdLd729-37NgWR0YktLFQsWfiNHcy1_-eWI6HKc8onOv5IuchKQ9nmQIE1mQUZBATCtx-vdzQqF_alTKw","e":"AQAB","key_ops":["unwrapKey"],"kid":"jsMQHFiA2uMGtiG3Ro8RJnYdEhp5W5KjGW-Vcf3-YMk","kty":"RSA","n":"zEi7NpGkzGyG2-PDWTy72hvti-pGBZLidxbk10_fenjzOavKcO5yGXSCfX_Xl0-WYaJfb7Kz6CRRtnwBGx8mrsftodtxt3kdrFnf6chQ2JJ5dmnz_ErIbHjHaFlxXvEqv3ivqIQSZvuns8QJip8RGQ63g7nmPDyvBcvLMFUtnXy7Y6rzUI4HO2eeg8htMPCWN5-L7Ol5_IT11NuZK6J3_UN1B0P7fFGdVMhsNR5D_jHOD-U4jZQijyVgzIXxwN_vnf90e5_ZUgsLypwh2DT7qzhqES8hzIIk_Cjs5mCUwDjfiBh0g7LNjXaj0b7rAn2X6yPuMK6zYi_FXJYSCQ7LH3THi_h5r2--xQ2h40He23JjfNpEGu98uMgB_dvzH0Dco8OKh4Aj2wjLpZVFOS3Zpu-WUalP-ecEiZ6nzmCMoIpWM0U4t4tpGwC1X-MWNj75zYLI8-yd3ELVBx3PQSzDukGOVLBdtpF0txf7Np0i9RFRW-nxf3xPVFCbZYbc87GMZy1CeDT2NBJJHtQaPKBismnEXtXtiM7aJDRM74F5JXjDVR62eHGxFCy46oyI_NS3FmvkoVtV8hvbXBRMSU8sNeOrVKnbWpfjBGFGD8VbssKoAAdMfn6usOiiH71SBg-L9KoBwdRfu7XFE-WbSA2d03hMvEPJWk9srh5qGdtQ_QE","p":"_RRumCEIUoM1gipAOIeQZ_td0E2qx20bFHArBf1V7ev4v4S22tt1Bt9oa20KPfA6mokycaB8MBCX7LTRvBT1XwSh4sU2TptuibElrarQvFoJIVGqugqG4GxsBpZpy-Dvx9NJtMTjiiGXSn87-_8iSWrPlc4Vdbgb8S9Vz2x_Mp-uSwZaFOO3Gx4miD29y1ugE26hFF0RdgBDJnJEk71W9NG2D_MbnBBa3DrPesdU8rX08RurpV8C1TVUgelDxUBQUxrRXRaHOPbrwPBBRDOgzNlFiuzUdkw6qwlfhp9W1tn3j7uAnqMHxnaPhJJ51tFjd_7MgSawmmM415c4NWa5xw","q":"zqQpmsvtjeU486C2DZ_NjsDHanYXgfxiY3X25XpM0ojLrtIQWKUaFxlggJGSQzQ6QJqB-5sJu9EEE4DGZ3UYoakY_hzxv0uHOHrI20ohFgF5K3YQdqGRhC3Bo66ezpmD3FT1EPXQM2OLEAi82lUb7UrbP_DSU0BmYOd6kPd1a3FD2qm0DjA15IYW5vUHNisx79W3kt_8s7ZICjiVpvJTSa1TVIX3wPM0qLHOWbHEOswLENC4fZ7EFYkNr3MvtAOxLcJ_mi4zzhAkyGdKJbTQXt6bhD_gWL5K1iOFM5Vg_NRTjUMqndDK33KDYZhC-MexSR-IaN7MbP1Y787uFL5S9w","qi":"DHzvGmAC4OZIfvDBkqXrvrvBeqURfioiEN72SL6oPcBqof899UL50BVohU0a2IeydwLSSff1i0U0jfX8ss_YKnCw2MxH3eNdQ-JXAQp7sefYLiFKPlGQnaxCNL0Z3kDatPhJnfTHaJA_UKtuPtcIINK5lJH80Iqqg3rl1p5G84ujbIghcgJXGoRGjJfkpUQgarTwPzdScR8Xo-YmN4dNav2X76s5toTJbSc4wLh3z_vdgL4GTGv9jzRKBPJcv07SDOFz92VA2r2DiIPQxg1eAKq8MPwEQ_bkaqh6Sk57_-JT8CRN0vfZbHw_3jCc8ijqRQE-4HaKcVCAbgnTzgr8cw"} \ No newline at end of file +{ + "alg": "RSA-OAEP-256", + "d": "UuZ7DtiI46pxCeACoP6cDxWBT2PJea6Td5eTU2sm-jkGH1O_ShgBtqHI6KwOLenO0Z1GlHoVNzIx9NeazUpVdN1Oan42ximA6LXqP6GaaWD7qJmzuSER968F4StW7hv5aI1N35mFLESY2ENISfhmRGBsCalRrQVFU5lRwjH22m_JTdeMeRbCCZHxynH2GcOwei8lZgWTiu3cIz7giEwc3GrZHZMxhdBLBCJ3gxDer9O_V0UCr7W7VpLpFuaOZmKaB2io-Rgz5W71IIo-09IghWnV5VZWpXr1Dl1_UbhE9UK_N6gQ9HcFopBQR6huD2svUO2aVig3RLO1KG8AL6vQsp8uIa_7_TuVYnuo7S_zVRlungjs2UzABjRqsdUED9C-FmwpnbKcHA1V99U8Ag22VHXm0zcT58M09-_yUl9s4VG6y-yQEhfxTjL0LMtSL8XScRwf38mpgX54LFGOeImo8d6jFGbNMzMViStejmXU8nCBeRmEtHpDU-GxJSPhO0ZJNgNWH_RU-tmujodXxXvyp7yA3MFqvcKxUQGf73lz_y10lLSmAJSlG-3KYyHY1N5AlXF4m0N3yENcim3VPVZarpOgHfEF3gg3slthAFZqTeY2WKMBp2O_QBvyf3mylVKHECzY4XzScBiPA9qlbeRDoa-NxcQwT2bS_1cyk8_IYG8", + "dp": "-sRGGYORpYDRglqvK9RX6FCQUpjCwbyAeq3yU4sEnC2XWt37-bw-p0KzSs7OuTTSPNH5I9HrfAzVHPzGXWE_CJmkdvkGqLkmvFTFiuGY1yk3psYvLwzDPJjyKUzdI-GMdioL_IyjEfna2TxdiYLyzGCDtGJxrSVyunc2xcMx_k1u_HQcylog00P61kCnUVtGsDqMbYsShCQaZXwQK3wPL6KkjgVLtEYYYeP8NiJRL_XP8SCD4ebU_cJMLDWgq1DgxpfqIBz0fCNe0ty9LwEhMhmP6OpmU5mRAGzXNKOp_lYYEVmvRoEwcRyFdM1W4N61eu8EYGyfzTrH1gfG8FMLfQ", + "dq": "uC9tAhQI7vRE2isCeoxY3YgJd2RBOdRrIjowUoS7WuEho_aLKWgoJDSzZoXad9pindjaSRj9CRKZ7oRW1Dp0NQwTQdUsyZSZt4cB5yxg50ty5IE81XDX0pgyRCJOXNpKxSZYtcBUzqlId4dCRd2codqaKJkP77K0TmWlQve_CoCPuaSP4CGqt8GpQj1fKzI_NYYqNVk-1jSAa9PZ2E-gW0iJwQoUNkDRTBgIVLx1qu7aYieYeif8oTFjKGKkP3EXCTGXKSdLd729-37NgWR0YktLFQsWfiNHcy1_-eWI6HKc8onOv5IuchKQ9nmQIE1mQUZBATCtx-vdzQqF_alTKw", + "e": "AQAB", + "key_ops": [ + "unwrapKey" + ], + "kid": "jsMQHFiA2uMGtiG3Ro8RJnYdEhp5W5KjGW-Vcf3-YMk", + "kty": "RSA", + "n": "zEi7NpGkzGyG2-PDWTy72hvti-pGBZLidxbk10_fenjzOavKcO5yGXSCfX_Xl0-WYaJfb7Kz6CRRtnwBGx8mrsftodtxt3kdrFnf6chQ2JJ5dmnz_ErIbHjHaFlxXvEqv3ivqIQSZvuns8QJip8RGQ63g7nmPDyvBcvLMFUtnXy7Y6rzUI4HO2eeg8htMPCWN5-L7Ol5_IT11NuZK6J3_UN1B0P7fFGdVMhsNR5D_jHOD-U4jZQijyVgzIXxwN_vnf90e5_ZUgsLypwh2DT7qzhqES8hzIIk_Cjs5mCUwDjfiBh0g7LNjXaj0b7rAn2X6yPuMK6zYi_FXJYSCQ7LH3THi_h5r2--xQ2h40He23JjfNpEGu98uMgB_dvzH0Dco8OKh4Aj2wjLpZVFOS3Zpu-WUalP-ecEiZ6nzmCMoIpWM0U4t4tpGwC1X-MWNj75zYLI8-yd3ELVBx3PQSzDukGOVLBdtpF0txf7Np0i9RFRW-nxf3xPVFCbZYbc87GMZy1CeDT2NBJJHtQaPKBismnEXtXtiM7aJDRM74F5JXjDVR62eHGxFCy46oyI_NS3FmvkoVtV8hvbXBRMSU8sNeOrVKnbWpfjBGFGD8VbssKoAAdMfn6usOiiH71SBg-L9KoBwdRfu7XFE-WbSA2d03hMvEPJWk9srh5qGdtQ_QE", + "p": "_RRumCEIUoM1gipAOIeQZ_td0E2qx20bFHArBf1V7ev4v4S22tt1Bt9oa20KPfA6mokycaB8MBCX7LTRvBT1XwSh4sU2TptuibElrarQvFoJIVGqugqG4GxsBpZpy-Dvx9NJtMTjiiGXSn87-_8iSWrPlc4Vdbgb8S9Vz2x_Mp-uSwZaFOO3Gx4miD29y1ugE26hFF0RdgBDJnJEk71W9NG2D_MbnBBa3DrPesdU8rX08RurpV8C1TVUgelDxUBQUxrRXRaHOPbrwPBBRDOgzNlFiuzUdkw6qwlfhp9W1tn3j7uAnqMHxnaPhJJ51tFjd_7MgSawmmM415c4NWa5xw", + "q": "zqQpmsvtjeU486C2DZ_NjsDHanYXgfxiY3X25XpM0ojLrtIQWKUaFxlggJGSQzQ6QJqB-5sJu9EEE4DGZ3UYoakY_hzxv0uHOHrI20ohFgF5K3YQdqGRhC3Bo66ezpmD3FT1EPXQM2OLEAi82lUb7UrbP_DSU0BmYOd6kPd1a3FD2qm0DjA15IYW5vUHNisx79W3kt_8s7ZICjiVpvJTSa1TVIX3wPM0qLHOWbHEOswLENC4fZ7EFYkNr3MvtAOxLcJ_mi4zzhAkyGdKJbTQXt6bhD_gWL5K1iOFM5Vg_NRTjUMqndDK33KDYZhC-MexSR-IaN7MbP1Y787uFL5S9w", + "qi": "DHzvGmAC4OZIfvDBkqXrvrvBeqURfioiEN72SL6oPcBqof899UL50BVohU0a2IeydwLSSff1i0U0jfX8ss_YKnCw2MxH3eNdQ-JXAQp7sefYLiFKPlGQnaxCNL0Z3kDatPhJnfTHaJA_UKtuPtcIINK5lJH80Iqqg3rl1p5G84ujbIghcgJXGoRGjJfkpUQgarTwPzdScR8Xo-YmN4dNav2X76s5toTJbSc4wLh3z_vdgL4GTGv9jzRKBPJcv07SDOFz92VA2r2DiIPQxg1eAKq8MPwEQ_bkaqh6Sk57_-JT8CRN0vfZbHw_3jCc8ijqRQE-4HaKcVCAbgnTzgr8cw" +} \ No newline at end of file diff --git a/client/src/test/resources/test-config-with-missing-env.yml b/client/src/test/resources/test-config-with-missing-env.yml index dfcd83a0a367d2ea92f040b0c32868c29f146b4e..06f2c02522a284e94c724ff1694861291ca7ba93 100644 --- a/client/src/test/resources/test-config-with-missing-env.yml +++ b/client/src/test/resources/test-config-with-missing-env.yml @@ -5,7 +5,7 @@ senderConfig: subscriberConfig: clientId: "2" clientSecret: "456" - privateDecryptionKeyPath: "client/src/test/java/resources/private_decryption_test_key.json" + privateDecryptionKeyPaths: ["client/src/test/java/resources/private_decryption_test_key.json"] privateSigningKeyPath: "client/src/test/java/resources/private_test_signing_key.json" activeEnvironment: environment_that_does_not_exist diff --git a/client/src/test/resources/test-config.yml b/client/src/test/resources/test-config.yml index 8a88ff9792e379713362147528070cc2880bb826..bc70ce10c52fb51944035eaa38431eeb3452ca57 100644 --- a/client/src/test/resources/test-config.yml +++ b/client/src/test/resources/test-config.yml @@ -5,7 +5,7 @@ senderConfig: subscriberConfig: clientId: "2" clientSecret: "456" - privateDecryptionKeyPath: "client/src/test/java/resources/private_decryption_test_key.json" + privateDecryptionKeyPaths: ["client/src/test/java/resources/private_decryption_test_key.json", "path/to/fake_key.json"] privateSigningKeyPath: "client/src/test/java/resources/private_test_signing_key.json" activeEnvironment: dev diff --git a/client/src/test/resources/trusted-test-root-certificates/TEST-PCA20.pem b/client/src/test/resources/trusted-test-root-certificates/TEST-PCA20.pem new file mode 100644 index 0000000000000000000000000000000000000000..0c5c8b31bfcf8050ba3737dde617dc642f53a13c --- /dev/null +++ b/client/src/test/resources/trusted-test-root-certificates/TEST-PCA20.pem @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGVjCCBD6gAwIBAgIFAJhujJQwDQYJKoZIhvcNAQELBQAwNTELMAkGA1UEBhMC +REUxETAPBgNVBAoTCFRFU1QtUEtJMRMwEQYDVQQDEwpURVNULVBDQTIwMB4XDTE5 +MTIwMTAwMDAwMFoXDTI5MTIzMTIzNTk1OVowNTELMAkGA1UEBhMCREUxETAPBgNV +BAoTCFRFU1QtUEtJMRMwEQYDVQQDEwpURVNULVBDQTIwMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAoAo7xDpvHSqSOJGW1rtalhZayl0IZGAtmF1f7Ssv +lq9DsJxEu6lRhTwGHPzPEXZ4EMdHwHGnw3Zv4XL4Uc/FcsBOzVKFb6D2WLWXoJ/9 +D6rHpG+iC9148JcYTPLIrBIfPKpLdC2vKqVubqSUvLZl8fjmME1elAuqwu6qDmel +bTa5iO+fV0awZkx0KrlCA88a6amldCqFfeert/XKqsrk74ihVzaUiTCW9aKNBxuc +QbKHhU1W8jgzpQi3x758dRtoDqChbPFkTLNoQhLP8pPa3MynbXUC0XfF7h63zQNp +NBvqnUjBl5oTbimoUFLdh8Wo0Bs0ifzu6WC0fEkI7ZQVOBjHEvMvN+rqAecsG/Bv +CDVLVK27T+HC3zAZcusKa6/X73Sa3uO1EySOG6jwSmjTctCTx4qDjJrZZqZEZgSm +zQqWTyWyo5LCIx3cPce+kafAJqufasT/WZS5vQ/65fUt/tEzZUFG34Pl6sFhtfe+ +91adDlOYioLnPC8EcWcSDP4DRh8CbEEqu/oLEj/UhAmcJGWutHtNKmr59j/SO06L +bZpSGgy4OU+aCiWn3N8S1wwBYpWqn0S1r876OQIofKdHshWPhg8/KXh/yjgx6a5/ +HMuKPQVH1FmFeWlnsbkpdMNPSDHM5LjjIwEm6dy2TwP35jSoYw/leIB4izaz6pEO +MKUCAwEAAaOCAWswggFnMA8GA1UdEwEB/wQFMAMBAf8wPwYDVR0gBDgwNjA0Bgsr +BgEEAbMBAQIBAzAlMCMGCCsGAQUFBwIBFhdodHRwczovL3d3dy5ic2kuYnVuZC5k +ZTCBrAYDVR0fBIGkMIGhMFagVKBShlBsZGFwOi8vdGVzdC14NTAwLmJ1bmQuZGUv +Q049VEVTVC1QQ0EyMCxPPVRFU1QtUEtJLEM9REU/Y2VydGlmaWNhdGVSZXZvY2F0 +aW9uTGlzdDBHoEWgQ4ZBaHR0cDovL3Rlc3QteDUwMC5idW5kLmRlL2NnaS1iaW4v +c2hvd19hdHRyP2NuPVRFU1QtUENBMjAmYXR0cj1jcmwwDgYDVR0PAQH/BAQDAgEG +MB0GA1UdDgQWBBTYSvDyBHCCr6Ozgm4rlO4scqMswTA1BgNVHREELjAsgRFWLVBL +SUBic2kuYnVuZC5kZYYXaHR0cHM6Ly93d3cuYnNpLmJ1bmQuZGUwDQYJKoZIhvcN +AQELBQADggIBADK29j9Tkm6SjI0PNmYFbOl/8FsjVsN4pZ0g67z4Ro1egbldCbV9 +pst7PB9TSjaKUBClS9fJUU5izOpxj4LxSehpJeJZNs47sXuMkePPCQdKv7WmwvWL +YY2wiMJ7qF4PyvtIqX6Wyy8y0nhuAb0KpO4jBG0kbuuxvt5oe6PAVLfvXiVJSIjl +OmjxNV50ryVWb0/nEISvrOdAno9vY5jIAfBQQauCAy4XgMR/6uyEaB/aGcb5Qbc8 +HlDS8tqRGQklRJ4XX6mDU2w8tU2szHQoSJt41p6UyQt0BN0bLKtVmZr8RlsZERUk +4m8x37VmoltNxgkUV6SARORNmqKPrvSgu/FgbKayJ/+8h0qEKhvhCQmbqsjlDxvA +cv7jPelmEmNwKEdvToxpMzPpQKCvXelgxANDHPdVqnQpBeqb53VFr5oKCIfYK4Ka +TPIzntmaLnS8JbM3Bb36tyCvh+HX5IcWCskRAmh10k5FmB4xBcz394gjJDYrOEqV +euNduSFVLwxZq8K/0MC6gacrxytnfnChjBdd+7gE8TDbHjA2xd8mbHDC5c1VrrHx +s9coPr+nSZP2dltg/OMCPRBuOXL/vwjfh7Wc6Rl4LBn+H1Ql/J52s3b1aukMiL3p +SJOElvThgBKsPlf6ftwIANzr/v6m3iOt0ifNLVMsPpYM2c9nj8QcvFcu +-----END CERTIFICATE----- diff --git a/config.yml b/config.yml index c02589ef55f461b5815968aa3ee6e7fb0112074d..d2234eb6196472ab65a9ceb37f0a55b502e11926 100644 --- a/config.yml +++ b/config.yml @@ -20,8 +20,10 @@ subscriberConfig: clientId: "SubscriberClientID" clientSecret: "SubscriberSecret" - # Path that references the private decryption key file - privateDecryptionKeyPath: "path/to/decryption_key.json" + # Paths that references the available private decryption keys. + # This property is an array since there might be multiple destinations + # with different decryption keys assigned to the subscriber client + privateDecryptionKeyPaths: ["path/to/decryption_key.json"] # Path that references the signing key file that signs SET-Events privateSigningKeyPath: "path/to/signing_key.json" diff --git a/core/pom.xml b/core/pom.xml index 4cfce953fa99def0db16017731b937791924eb26..3ed1acb1678917e0559bae7afdf9fc037dc38cb1 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -81,6 +81,10 @@ <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + </plugin> </plugins> </build> diff --git a/core/src/main/java/dev/fitko/fitconnect/core/SubmissionSender.java b/core/src/main/java/dev/fitko/fitconnect/core/SubmissionSender.java index f66087eabaf1bc8d1f01c07c79039eefdd130b96..00302464512b983ae1f7116a8bdbe620e9f11069 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/SubmissionSender.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/SubmissionSender.java @@ -3,7 +3,7 @@ package dev.fitko.fitconnect.core; import com.nimbusds.jose.jwk.RSAKey; import dev.fitko.fitconnect.api.domain.model.destination.Destination; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; -import dev.fitko.fitconnect.api.domain.model.event.EventStatus; +import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; import dev.fitko.fitconnect.api.domain.model.submission.CreateSubmission; @@ -47,12 +47,6 @@ public class SubmissionSender implements Sender { this.keyService = keyService; } - @Override - public ValidationResult validatePublicKey(final RSAKey publicKey) { - LOGGER.info("Validating public key integrity"); - return validationService.validateEncryptionPublicKey(publicKey); - } - @Override public ValidationResult validateMetadata(final Metadata metadata) { LOGGER.info("Validating metadata"); @@ -134,7 +128,7 @@ public class SubmissionSender implements Sender { } @Override - public EventStatus getLastedEvent(final UUID destinationId, final UUID caseId, final UUID submissionId, final AuthenticationTags authenticationTags) { + public SubmissionStatus getLastedEvent(final UUID destinationId, final UUID caseId, final UUID submissionId, final AuthenticationTags authenticationTags) { LOGGER.info("Loading latest status for submission {}", submissionId); return eventLogService.getLastedEvent(destinationId, caseId, submissionId, authenticationTags); } diff --git a/core/src/main/java/dev/fitko/fitconnect/core/SubmissionSubscriber.java b/core/src/main/java/dev/fitko/fitconnect/core/SubmissionSubscriber.java index c47927ed62883d7403df37fa50ea5429a9d26554..3de5c7e0b9df2423b624cacec996d3d624782392 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/SubmissionSubscriber.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/SubmissionSubscriber.java @@ -2,12 +2,19 @@ package dev.fitko.fitconnect.core; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jwt.SignedJWT; +import dev.fitko.fitconnect.api.domain.model.destination.Destination; +import dev.fitko.fitconnect.api.domain.model.event.Event; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; import dev.fitko.fitconnect.api.domain.model.event.EventPayload; +import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentForValidation; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; +import dev.fitko.fitconnect.api.exceptions.DecryptionException; +import dev.fitko.fitconnect.api.exceptions.EventCreationException; +import dev.fitko.fitconnect.api.exceptions.RestApiException; import dev.fitko.fitconnect.api.services.Subscriber; import dev.fitko.fitconnect.api.services.crypto.CryptoService; import dev.fitko.fitconnect.api.services.events.EventLogService; @@ -44,44 +51,68 @@ public class SubmissionSubscriber implements Subscriber { } @Override - public byte[] decryptStringContent(final RSAKey privateKey, final String encryptedContent) { + public byte[] decryptStringContent(final RSAKey privateKey, final String encryptedContent) throws DecryptionException { LOGGER.info("Decrypting string"); - return cryptoService.decryptBytes(privateKey, encryptedContent); + return cryptoService.decryptToBytes(privateKey, encryptedContent); } @Override - public Set<SubmissionForPickup> pollAvailableSubmissions(final UUID destinationId, final int offset, final int limit) { + public Set<SubmissionForPickup> pollAvailableSubmissionsForDestination(final UUID destinationId, final int offset, final int limit) { LOGGER.info("Loading submission {}-{} for destination {}", offset, limit, destinationId); - return submissionService.pollAvailableSubmissions(destinationId, offset, limit).getSubmissions(); + return submissionService.pollAvailableSubmissionsForDestination(destinationId, offset, limit).getSubmissions(); } @Override - public Submission getSubmission(final UUID submissionId) { + public Set<SubmissionForPickup> pollAvailableSubmissions(final int offset, final int limit) { + LOGGER.info("Loading submission {}-{}", offset, limit); + return submissionService.pollAvailableSubmissions(offset, limit).getSubmissions(); + } + + @Override + public Submission getSubmission(final UUID submissionId) throws RestApiException { LOGGER.info("Loading submission {}", submissionId); return submissionService.getSubmission(submissionId); } @Override - public String fetchAttachment(final UUID submissionId, final UUID attachmentId) { + public String fetchAttachment(final UUID submissionId, final UUID attachmentId) throws RestApiException { LOGGER.info("Loading attachment {} for submission {}", attachmentId, submissionId); return submissionService.getAttachment(submissionId, attachmentId); } @Override - public List<EventLogEntry> getEventLog(final UUID caseId, final UUID destinationId) { + public List<EventLogEntry> getEventLog(final UUID caseId, final UUID destinationId) throws RestApiException { LOGGER.info("Loading event log for destination {}", destinationId); return eventLogService.getEventLog(caseId, destinationId); } + @Override + public AuthenticationTags getAuthenticationTagsForEvent(final Event event, final Submission submission) { + LOGGER.info("Loading authentication tags of {} event for submission {}", event, submission.getSubmissionId()); + return eventLogService.getAuthenticationTagsForEvent(event, submission); + } + + @Override + public ValidationResult validateMetadata(final Metadata metadata, final Submission submission, final AuthenticationTags authenticationTags) { + LOGGER.info("Validating metadata"); + final Destination destination = submissionService.getDestination(submission.getDestinationId()); + return validationService.validateMetadata(metadata, submission, destination, authenticationTags); + } + + @Override + public ValidationResult validateData(final byte[] data, final Submission submission, final Metadata metadata, final AuthenticationTags authenticationTags) { + LOGGER.info("Validating data"); + return validationService.validateData(data, submission, metadata, authenticationTags); + } @Override - public ValidationResult validateMetadata(final Metadata metadata) { - LOGGER.info("Validating metadata schema"); - return validationService.validateMetadataSchema(metadata); + public ValidationResult validateAttachments(final List<AttachmentForValidation> attachmentsForValidation, final AuthenticationTags authenticationTags) { + LOGGER.info("Validating attachments"); + return validationService.validateAttachments(attachmentsForValidation, authenticationTags); } @Override public ValidationResult validateHashIntegrity(final String originalHash, final byte[] data) { - LOGGER.info("Validating data hash integrity"); + LOGGER.info("Validating hash integrity"); return validationService.validateHashIntegrity(originalHash, data); } @@ -92,7 +123,7 @@ public class SubmissionSubscriber implements Subscriber { } @Override - public void acceptSubmission(final EventPayload eventPayload) { + public void acceptSubmission(final EventPayload eventPayload) throws RestApiException, EventCreationException { LOGGER.info("Accepting submission"); final SignedJWT confirmedSubmissionEvent = securityEventService.createAcceptSubmissionEvent(eventPayload); eventLogService.sendEvent(eventPayload.getCaseId(), confirmedSubmissionEvent.serialize()); @@ -100,7 +131,7 @@ public class SubmissionSubscriber implements Subscriber { } @Override - public void rejectSubmission(final EventPayload eventPayload) { + public void rejectSubmission(final EventPayload eventPayload) throws RestApiException, EventCreationException { LOGGER.info("Rejecting submission"); final SignedJWT rejectSubmissionEvent = securityEventService.createRejectSubmissionEvent(eventPayload); eventLogService.sendEvent(eventPayload.getCaseId(), rejectSubmissionEvent.serialize()); diff --git a/core/src/main/java/dev/fitko/fitconnect/core/crypto/JWECryptoService.java b/core/src/main/java/dev/fitko/fitconnect/core/crypto/JWECryptoService.java index 8f60615b620963e42ba33511fabacbaf28274812..e43b59f16f73efdedac896c74805bb1d34717424 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/crypto/JWECryptoService.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/crypto/JWECryptoService.java @@ -2,7 +2,13 @@ package dev.fitko.fitconnect.core.crypto; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.nimbusds.jose.*; +import com.nimbusds.jose.CompressionAlgorithm; +import com.nimbusds.jose.EncryptionMethod; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWEHeader; +import com.nimbusds.jose.JWEObject; +import com.nimbusds.jose.Payload; import com.nimbusds.jose.crypto.RSADecrypter; import com.nimbusds.jose.crypto.RSAEncrypter; import com.nimbusds.jose.jwk.RSAKey; @@ -33,21 +39,10 @@ public class JWECryptoService implements CryptoService { } @Override - public String decryptString(final RSAKey privateKey, final String encryptedData) throws DecryptionException { - return decrypt(privateKey, encryptedData).toString(); - } - - @Override - public byte[] decryptBytes(final RSAKey privateKey, final String encryptedData) throws DecryptionException { + public byte[] decryptToBytes(final RSAKey privateKey, final String encryptedData) throws DecryptionException { return decrypt(privateKey, encryptedData).toBytes(); } - @Override - public String encryptString(final RSAKey publicKey, final String data) throws EncryptionException { - final Payload payload = new Payload(data); - return encrypt(publicKey, payload); - } - @Override public String encryptObject(final RSAKey encryptionKey, final Object obj) throws EncryptionException { try { @@ -89,7 +84,7 @@ public class JWECryptoService implements CryptoService { jwe.decrypt(new RSADecrypter(privateKey)); LOGGER.info("Decrypting {} bytes took {} ", encData.getBytes().length, StopWatch.stopWithFormattedTime(start)); return jwe.getPayload(); - } catch (final ParseException | JOSEException e) { + } catch (final ParseException | IllegalStateException | JOSEException e) { throw new DecryptionException(e.getMessage(), e); } } diff --git a/core/src/main/java/dev/fitko/fitconnect/core/events/EventLogApiService.java b/core/src/main/java/dev/fitko/fitconnect/core/events/EventLogApiService.java index e3a6fc47798a867ba7a2b15519f47818b6d7b204..c6e49cbb155f6980f66e8908378c67077e6e8d05 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/events/EventLogApiService.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/events/EventLogApiService.java @@ -5,17 +5,17 @@ import dev.fitko.fitconnect.api.config.ApplicationConfig; import dev.fitko.fitconnect.api.domain.model.event.Event; import dev.fitko.fitconnect.api.domain.model.event.EventLog; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; -import dev.fitko.fitconnect.api.domain.model.event.EventStatus; -import dev.fitko.fitconnect.api.domain.model.event.SubmissionState; +import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; +import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.validation.ValidationContext; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; +import dev.fitko.fitconnect.api.exceptions.AuthenticationTagsEmptyException; import dev.fitko.fitconnect.api.exceptions.EventLogException; -import dev.fitko.fitconnect.api.exceptions.RestApiException; +import dev.fitko.fitconnect.api.exceptions.SubmitEventNotFoundException; import dev.fitko.fitconnect.api.services.auth.OAuthService; import dev.fitko.fitconnect.api.services.events.EventLogService; import dev.fitko.fitconnect.api.services.events.EventLogVerificationService; -import dev.fitko.fitconnect.core.util.EventLogUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpEntity; @@ -27,12 +27,16 @@ import org.springframework.web.client.RestTemplate; import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.SortedMap; -import java.util.TreeMap; import java.util.UUID; -import java.util.function.Function; import java.util.stream.Collectors; +import static dev.fitko.fitconnect.core.util.EventLogUtil.eventEqualsSubmissionId; +import static dev.fitko.fitconnect.core.util.EventLogUtil.eventToEntry; +import static dev.fitko.fitconnect.core.util.EventLogUtil.getAuthenticationTags; +import static dev.fitko.fitconnect.core.util.EventLogUtil.getJWTSFromEvents; +import static dev.fitko.fitconnect.core.util.EventLogUtil.getJwtsFromEvents; +import static dev.fitko.fitconnect.core.util.EventLogUtil.mapEventLogToEntries; + public class EventLogApiService implements EventLogService { private static final Logger LOGGER = LoggerFactory.getLogger(EventLogApiService.class); @@ -53,37 +57,59 @@ public class EventLogApiService implements EventLogService { } @Override - public List<EventLogEntry> getEventLog(final UUID caseId, final UUID destinationId) { + public List<EventLogEntry> getEventLog(final UUID caseId, final UUID destinationId) throws EventLogException { final EventLog eventLog = loadEventLog(caseId); - final List<SignedJWT> events = EventLogUtil.getJWTSFromEvents(eventLog.getEventLogs()); + final List<SignedJWT> events = getJWTSFromEvents(eventLog.getEventLogs()); final ValidationContext ctx = new ValidationContext(destinationId, caseId); - validateLog(eventLogVerifier.validateEventLogs(ctx, events)); - return EventLogUtil.mapEventLogToEntries(eventLog); + evaluateLogValidation(eventLogVerifier.validateEventLogs(ctx, events)); + return mapEventLogToEntries(eventLog); + } + + @Override + public AuthenticationTags getAuthenticationTagsForEvent(final Event event, final Submission submission) throws EventLogException { + + final EventLog eventLog = loadEventLog(submission.getCaseId()); + final List<SignedJWT> submitEvents = getJwtsFromEvents(event, submission, eventLog); + + if (submitEvents.size() != 1) { + throw new SubmitEventNotFoundException("Event log does not contain exactly one submit event"); + } + + final SignedJWT submitEvent = submitEvents.stream().findFirst().get(); + final AuthenticationTags authenticationTags = getAuthenticationTags(submitEvent); + + if(authenticationTags.getMetadata() == null && authenticationTags.getData() == null){ + throw new AuthenticationTagsEmptyException("Authentication tags are empty"); + } + + verifyEventWithAuthTags(submission, submitEvent, authenticationTags); + + return authenticationTags; } @Override - public EventStatus getLastedEvent(final UUID destinationId, final UUID caseId, final UUID submissionId, final AuthenticationTags authenticationTags) throws RestApiException, EventLogException { + public SubmissionStatus getLastedEvent(final UUID destinationId, final UUID caseId, final UUID submissionId, final AuthenticationTags authenticationTags) throws EventLogException { final EventLog eventLog = loadEventLog(caseId); - final SignedJWT latestEvent = getLatestEventForSubmission(submissionId, eventLog); + final SignedJWT latestEvent = getLatestEventForSubmission(submissionId, eventLog.getEventLogs()); if (latestEvent == null) { LOGGER.info("No events found for submission {}", submissionId); - return new EventStatus(); + return new SubmissionStatus(); } final var ctx = new ValidationContext(destinationId, caseId, authenticationTags); - validateLog(eventLogVerifier.validateEventLogs(ctx, List.of(latestEvent))); - final EventLogEntry eventLogEntry = EventLogUtil.eventToEntry(latestEvent); - return new EventStatus(eventLogEntry.getEvent().getState(), eventLogEntry.getProblems()); + evaluateLogValidation(eventLogVerifier.validateEventLogs(ctx, List.of(latestEvent))); + final EventLogEntry eventLogEntry = eventToEntry(latestEvent); + return new SubmissionStatus(eventLogEntry.getEvent().getState(), eventLogEntry.getProblems()); } @Override - public void sendEvent(final UUID caseId, final String signedAndSerializedSET) { + public void sendEvent(final UUID caseId, final String signedAndSerializedSET) throws EventLogException { final String url = config.getEventsEndpoint(); final HttpHeaders headers = getHttpHeaders("application/jose"); final HttpEntity<String> entity = new HttpEntity<>(signedAndSerializedSET, headers); try { restTemplate.exchange(url, HttpMethod.POST, entity, Void.class, caseId); } catch (final RestClientException e) { - throw new RestApiException("Sending event failed", e); + throw new EventLogException("Sending event failed", e); } } @@ -94,7 +120,7 @@ public class EventLogApiService implements EventLogService { try { return restTemplate.exchange(url, HttpMethod.GET, entity, EventLog.class, caseId).getBody(); } catch (final RestClientException e) { - throw new RestApiException("EventLog query failed", e); + throw new EventLogException("EventLog query failed", e); } } @@ -114,46 +140,33 @@ public class EventLogApiService implements EventLogService { return headers; } - private SignedJWT getLatestEventForSubmission(final UUID submissionId, final EventLog eventLog) { - - final List<SignedJWT> signedJWTS = EventLogUtil.getJWTSFromEvents(eventLog.getEventLogs()); - final SortedMap<Event, SignedJWT> eventsOrderedByState = getOrderedEvents(submissionId, signedJWTS); - final SignedJWT latestEvent = getLatestEvent(submissionId, signedJWTS); - - return latestEvent != null ? getLatestEventIfInAllowedOrder(eventsOrderedByState, latestEvent) : null; - } - - private SignedJWT getLatestEventIfInAllowedOrder(final SortedMap<Event, SignedJWT> eventsOrderedByState, final SignedJWT latestEvent) { - final SubmissionState latestAllowedState = eventsOrderedByState.lastKey().getState(); - final SubmissionState currentLatestState = EventLogUtil.getEventFromJWT(latestEvent).getState(); - if (latestAllowedState != currentLatestState) { - final String allowedOrder = eventsOrderedByState.keySet().stream().map(e -> e.getState().getName().toUpperCase()).collect(Collectors.joining("|")); - throw new EventLogException("Allowed transition [" + allowedOrder + "] should have [" + latestAllowedState + "] as latest state but was [" + currentLatestState + "]"); - } - return latestEvent; - } - - private SignedJWT getLatestEvent(final UUID submissionId, final List<SignedJWT> signedJWTS) { - return signedJWTS.stream() - .filter(EventLogUtil.eventEqualsSubmissionId(submissionId)) + private SignedJWT getLatestEventForSubmission(final UUID submissionId, final List<String> events) { + return getJWTSFromEvents(events).stream() + .filter(eventEqualsSubmissionId(submissionId)) .reduce((first, second) -> second) .orElse(null); } - private SortedMap<Event, SignedJWT> getOrderedEvents(final UUID submissionId, final List<SignedJWT> signedJWTS) { - return new TreeMap<>(signedJWTS.stream() - .filter(EventLogUtil.eventEqualsSubmissionId(submissionId)) - .collect(Collectors.toMap(EventLogUtil::getEventFromJWT, Function.identity(), (e1, e2) -> e1))); - } + private void verifyEventWithAuthTags(final Submission submission, final SignedJWT submitEvent, final AuthenticationTags authenticationTags) { + + final var ctx = new ValidationContext(submission.getDestinationId(), submission.getCaseId(), authenticationTags); + final List<ValidationResult> validationResults = eventLogVerifier.validateEventLogs(ctx, List.of(submitEvent)); - private void validateLog(final List<ValidationResult> results) { - if (!results.isEmpty()) { - final String errorMessages = results.stream() + if(!validationResults.isEmpty()){ + final String errorMessages = validationResults.stream() .map(ValidationResult::getError) - .map(Throwable::getMessage) - .distinct() + .map(Exception::getMessage) .collect(Collectors.joining("\n")); - throw new EventLogException("Event log contains invalid entries: \n" + errorMessages); + throw new EventLogException(errorMessages); } } + + private void evaluateLogValidation(final List<ValidationResult> results) { + results.stream() + .filter(ValidationResult::hasError) + .findFirst() + .ifPresent((result) -> { + throw new EventLogException(result.getError().getMessage(), result.getError()); + }); + } } diff --git a/core/src/main/java/dev/fitko/fitconnect/core/events/EventLogVerifier.java b/core/src/main/java/dev/fitko/fitconnect/core/events/EventLogVerifier.java index 905a5d52a3cb65adaa5753c0816da5b614863c53..b097f861baf262b3845c74a4030d166688f985c0 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/events/EventLogVerifier.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/events/EventLogVerifier.java @@ -5,6 +5,7 @@ import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.JWSVerifier; import com.nimbusds.jose.crypto.RSASSAVerifier; +import com.nimbusds.jose.jwk.KeyOperation; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; @@ -99,12 +100,12 @@ public class EventLogVerifier implements EventLogVerificationService { if (header.getAlgorithm() == null) { ctx.addError("The provided alg in the SET header must not be null."); } else { - ctx.addResult(header.getAlgorithm() == JWSAlgorithm.PS512, "The provided alg in the SET header is not allowed."); + ctx.addErrorIfTestFailed(header.getAlgorithm() == JWSAlgorithm.PS512, "The provided alg in the SET header is not allowed."); } if (header.getType() == null) { ctx.addError("The provided typ in the SET header must not be null."); } else { - ctx.addResult(header.getType().toString().equals(HEADER_TYPE), "The provided typ in the SET header is not " + HEADER_TYPE); + ctx.addErrorIfTestFailed(header.getType().toString().equals(HEADER_TYPE), "The provided typ in the SET header is not " + HEADER_TYPE); } } @@ -122,16 +123,16 @@ public class EventLogVerifier implements EventLogVerificationService { } private void validatePayload(final ValidationContext ctx, final JWTClaimsSet claims) throws ParseException { - ctx.addResult(claims.getClaim(ISSUER) != null, "The claim iss is missing in the payload of the SET."); - ctx.addResult(claims.getClaim(ISSUED_AT) != null, "The claim iat is missing in the payload of the SET."); - ctx.addResult(claims.getClaim(JWT_ID) != null, "The claim jti is missing in the payload of the SET."); - ctx.addResult(claims.getClaim(SUBJECT) != null, "The claim sub is missing in the payload of the SET."); - ctx.addResult(claims.getClaim(CLAIM_TXN) != null, "The claim txn is missing in the payload of the SET."); - ctx.addResult(claims.getClaim(CLAIM_EVENTS) != null, "The claim events is missing in the payload of the SET."); - ctx.addResult(claims.getJSONObjectClaim(CLAIM_EVENTS).keySet().size() == 1, "The events claims has must be exactly one event."); - ctx.addResult(claims.getStringClaim("sub").matches("(submission|case|reply):" + UUID_V4_PATTERN), "The provided subject does not match the allowed pattern."); - ctx.addResult(claims.getStringClaim(CLAIM_TXN).matches("case:" + UUID_V4_PATTERN), "The provided txn does not match the allowed pattern."); - getEventClaim(claims).ifPresentOrElse(event -> ctx.addResult(Event.fromSchemaUri(event) != null, "The provided event is not a valid event supported by this instance."), + ctx.addErrorIfTestFailed(claims.getClaim(ISSUER) != null, "The claim iss is missing in the payload of the SET."); + ctx.addErrorIfTestFailed(claims.getClaim(ISSUED_AT) != null, "The claim iat is missing in the payload of the SET."); + ctx.addErrorIfTestFailed(claims.getClaim(JWT_ID) != null, "The claim jti is missing in the payload of the SET."); + ctx.addErrorIfTestFailed(claims.getClaim(SUBJECT) != null, "The claim sub is missing in the payload of the SET."); + ctx.addErrorIfTestFailed(claims.getClaim(CLAIM_TXN) != null, "The claim txn is missing in the payload of the SET."); + ctx.addErrorIfTestFailed(claims.getClaim(CLAIM_EVENTS) != null, "The claim events is missing in the payload of the SET."); + ctx.addErrorIfTestFailed(claims.getJSONObjectClaim(CLAIM_EVENTS).keySet().size() == 1, "The events claims has must be exactly one event."); + ctx.addErrorIfTestFailed(claims.getStringClaim("sub").matches("(submission|case|reply):" + UUID_V4_PATTERN), "The provided subject does not match the allowed pattern."); + ctx.addErrorIfTestFailed(claims.getStringClaim(CLAIM_TXN).matches("case:" + UUID_V4_PATTERN), "The provided txn does not match the allowed pattern."); + getEventClaim(claims).ifPresentOrElse(event -> ctx.addErrorIfTestFailed(Event.fromSchemaUri(event) != null, "The provided event is not a valid event supported by this instance."), () -> ctx.addError("No events in JWT")); } @@ -139,7 +140,7 @@ public class EventLogVerifier implements EventLogVerificationService { if (signatureKey == null) { ctx.addError("The signature key cannot be validated, it must not be null."); } else { - ctx.addResult(validationService.validateSignaturePublicKey(signatureKey)); + ctx.addResult(validationService.validatePublicKey(signatureKey, KeyOperation.VERIFY)); } } @@ -148,7 +149,7 @@ public class EventLogVerifier implements EventLogVerificationService { ctx.addError("The signature cannot validated, signature key is unavailable."); } else { final JWSVerifier jwsVerifier = new RSASSAVerifier(signatureKey); - ctx.addResult(signedJWT.verify(jwsVerifier), "The signature of the token could not be verified with the specified key."); + ctx.addErrorIfTestFailed(signedJWT.verify(jwsVerifier), "The signature of the token could not be verified with the specified key."); } } @@ -177,15 +178,15 @@ public class EventLogVerifier implements EventLogVerificationService { ctx.addError("The event '" + event.getSchemaUri() + "' has to be created by the destination ('iss' claim must be an UUID)"); } if (destinationId != null) { - ctx.addResult(destinationId.equals(ctx.getDestinationId()), "The destination of the submission is not the issuer ('iss' claim must match submission.destinationId)"); + ctx.addErrorIfTestFailed(destinationId.equals(ctx.getDestinationId()), "The destination of the submission is not the issuer ('iss' claim must match submission.destinationId)"); } } private void validateAuthenticationTags(final ValidationContext ctx, final JWTClaimsSet payload) throws ParseException { final AuthenticationTags eventAuthTags = getAuthenticationTags(payload); final AuthenticationTags submissionAuthTags = ctx.getAuthenticationTags(); - ctx.addResult(eventAuthTags != null, "AuthenticationTags of event must not be null."); - ctx.addResult(submissionAuthTags != null, "AuthenticationTags of submission must not be null."); + ctx.addErrorIfTestFailed(eventAuthTags != null, "AuthenticationTags of event must not be null."); + ctx.addErrorIfTestFailed(submissionAuthTags != null, "AuthenticationTags of submission must not be null."); if (eventAuthTags != null && submissionAuthTags != null) { validateDataAuthTags(ctx, eventAuthTags.getData(), submissionAuthTags.getData()); validateMetadataAuthTags(ctx, eventAuthTags.getMetadata(), submissionAuthTags.getMetadata()); @@ -195,20 +196,20 @@ public class EventLogVerifier implements EventLogVerificationService { private void validateAttachmentsAuthTags(final ValidationContext ctx, final Map<UUID, String> eventAttachmentTags, final Map<UUID, String> submissionAttachmentTags) { if (eventAttachmentTags != null && submissionAttachmentTags != null) { - ctx.addResult(eventAttachmentTags.size() == submissionAttachmentTags.size(), "The events quantity of attachments does not match the submission."); + ctx.addErrorIfTestFailed(eventAttachmentTags.size() == submissionAttachmentTags.size(), "The events quantity of attachments does not match the submission."); eventAttachmentTags.forEach((key, eventTag) -> { final String submissionTag = submissionAttachmentTags.get(key); - ctx.addResult(eventTag.equals(submissionTag), "The authentication-tag for the attachment " + key + " does not match the submission."); + ctx.addErrorIfTestFailed(eventTag.equals(submissionTag), "The authentication-tag for the attachment " + key + " does not match the submission."); }); } } private void validateDataAuthTags(final ValidationContext ctx, final String eventDataAuthTag, final String submissionDataAuthTag) { - ctx.addResult(eventDataAuthTag.equals(submissionDataAuthTag), "The events data authentication-tag does not match the submission."); + ctx.addErrorIfTestFailed(eventDataAuthTag.equals(submissionDataAuthTag), "The events data authentication-tag does not match the submission."); } private void validateMetadataAuthTags(final ValidationContext ctx, final String eventMetadataAuthTag, final String submissionMetadataAuthTag) { - ctx.addResult(eventMetadataAuthTag.equals(submissionMetadataAuthTag), "The events metadata authentication-tag does not match the submission."); + ctx.addErrorIfTestFailed(eventMetadataAuthTag.equals(submissionMetadataAuthTag), "The events metadata authentication-tag does not match the submission."); } private static boolean eventContainsAuthTags(final JWTClaimsSet payload) throws ParseException { diff --git a/core/src/main/java/dev/fitko/fitconnect/core/events/SecurityEventTokenService.java b/core/src/main/java/dev/fitko/fitconnect/core/events/SecurityEventTokenService.java index 59c1e4126d0505921e8f403ba35da4c50a0fd8a0..0191811982550a7774f4cea128b76a09de31eeec 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/events/SecurityEventTokenService.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/events/SecurityEventTokenService.java @@ -21,6 +21,7 @@ import dev.fitko.fitconnect.api.services.events.SecurityEventService; import dev.fitko.fitconnect.api.services.validation.ValidationService; import java.text.ParseException; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -105,7 +106,7 @@ public class SecurityEventTokenService implements SecurityEventService { private Map<String, Object> buildEventsClaim(final Event event, final EventPayload eventPayload) { final Map<String, Object> events = new HashMap<>(); - if (!eventPayload.getProblems().isEmpty()) { + if (eventPayload.getProblems() != null && !eventPayload.getProblems().isEmpty()) { events.put("problems", eventPayload.getProblems()); } if (event.equals(ACCEPT)) { @@ -123,6 +124,9 @@ public class SecurityEventTokenService implements SecurityEventService { } private Map<UUID, String> buildAttachmentAuthenticationTags(final Map<UUID, String> encryptedAttachments) { + if(encryptedAttachments == null || encryptedAttachments.isEmpty()){ + return Collections.emptyMap(); + } return encryptedAttachments .entrySet() .stream() diff --git a/core/src/main/java/dev/fitko/fitconnect/core/keys/PublicKeyService.java b/core/src/main/java/dev/fitko/fitconnect/core/keys/PublicKeyService.java index 2ab47f490335b6d9aee31e5e76e7274b0867de14..2445d98c39707200eb5bbc8c080cda126bce8d05 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/keys/PublicKeyService.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/keys/PublicKeyService.java @@ -2,6 +2,7 @@ package dev.fitko.fitconnect.core.keys; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jose.jwk.KeyOperation; import com.nimbusds.jose.jwk.RSAKey; import dev.fitko.fitconnect.api.config.ApplicationConfig; import dev.fitko.fitconnect.api.domain.model.destination.Destination; @@ -107,12 +108,12 @@ public class PublicKeyService implements KeyService { } private void validateEncryptionKey(final RSAKey rsaKey) { - final ValidationResult result = validationService.validateEncryptionPublicKey(rsaKey); + final ValidationResult result = validationService.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY); validateResult(result, "Invalid public encryption key"); } private void validateSignatureKey(final RSAKey rsaKey) { - final ValidationResult result = validationService.validateSignaturePublicKey(rsaKey); + final ValidationResult result = validationService.validatePublicKey(rsaKey, KeyOperation.VERIFY); validateResult(result, "Public signature key is not valid"); } diff --git a/core/src/main/java/dev/fitko/fitconnect/core/routing/RoutingApiService.java b/core/src/main/java/dev/fitko/fitconnect/core/routing/RoutingApiService.java index 5639d27979c32fbed0d376736b8bbf82d4f1688c..fe18cf66f71a3b81a5dfb046252bab4bb99ec804 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/routing/RoutingApiService.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/routing/RoutingApiService.java @@ -17,7 +17,6 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; -import java.util.stream.Collectors; import static java.util.function.Predicate.not; @@ -89,7 +88,7 @@ public class RoutingApiService implements RoutingService { throw new RestApiException("At least one search criterion out of ags, ars or areaId must be set"); } - if(areaSearchCriteria.stream().filter(not(CriterionEmpty())).collect(Collectors.toSet()).size() > 1){ + if(areaSearchCriteria.stream().filter(not(CriterionEmpty())).count() > 1){ throw new RestApiException("Only one of ars, ags or areaId must be specified."); } } diff --git a/core/src/main/java/dev/fitko/fitconnect/core/submission/SubmissionApiService.java b/core/src/main/java/dev/fitko/fitconnect/core/submission/SubmissionApiService.java index c7f8ba441b92e2c093aa7751671d4ecb3c1de7c5..9af4798e4282d65266f8e255dc3dc8d54206efc1 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/submission/SubmissionApiService.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/submission/SubmissionApiService.java @@ -19,7 +19,9 @@ import lombok.Setter; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; @@ -42,7 +44,7 @@ public class SubmissionApiService implements SubmissionService { } @Override - public Destination getDestination(final UUID destinationID) { + public Destination getDestination(final UUID destinationID) throws RestApiException { final RequestSettings requestSettings = RequestSettings.builder() .url(config.getDestinationsEndpoint()) .method(HttpMethod.GET) @@ -55,7 +57,7 @@ public class SubmissionApiService implements SubmissionService { } @Override - public SubmissionForPickup announceSubmission(final CreateSubmission submission) { + public SubmissionForPickup announceSubmission(final CreateSubmission submission) throws RestApiException { final RequestSettings requestSettings = RequestSettings.builder() .url(config.getSubmissionsEndpoint()) .method(HttpMethod.POST) @@ -68,7 +70,7 @@ public class SubmissionApiService implements SubmissionService { } @Override - public void uploadAttachment(final UUID submissionId, final UUID attachmentId, final String encryptedAttachment) { + public void uploadAttachment(final UUID submissionId, final UUID attachmentId, final String encryptedAttachment) throws RestApiException { final Map<String, Object> params = new HashMap<>(); params.put("submissionId", submissionId); params.put("attachmentId", attachmentId); @@ -84,7 +86,7 @@ public class SubmissionApiService implements SubmissionService { } @Override - public String getAttachment(final UUID submissionId, final UUID attachmentId) { + public String getAttachment(final UUID submissionId, final UUID attachmentId) throws RestApiException { final Map<String, Object> params = new HashMap<>(); params.put("submissionId", submissionId); params.put("attachmentId", attachmentId); @@ -100,7 +102,7 @@ public class SubmissionApiService implements SubmissionService { } @Override - public Submission sendSubmission(final SubmitSubmission submission) { + public Submission sendSubmission(final SubmitSubmission submission) throws RestApiException { final Map<String, Object> params = Map.of("submissionId", submission.getSubmissionId()); final RequestSettings requestSettings = RequestSettings.builder() .url(config.getSubmissionEndpoint()) @@ -114,11 +116,24 @@ public class SubmissionApiService implements SubmissionService { } @Override - public SubmissionsForPickup pollAvailableSubmissions(final UUID destinationId, final int offset, final int limit) { - final Map<String, Object> params = new HashMap<>(); - params.put("destinationId", destinationId); - params.put("limit", limit); - params.put("offset", offset); + public SubmissionsForPickup pollAvailableSubmissions(final int offset, final int limit) throws RestApiException { + final String urlWithQueryParams = UriComponentsBuilder.fromHttpUrl(config.getSubmissionsEndpoint()) + .queryParam("limit", limit) + .queryParam("offset", offset) + .encode() + .toUriString(); + final RequestSettings requestSettings = RequestSettings.builder() + .url(urlWithQueryParams) + .method(HttpMethod.GET) + .responseType(SubmissionsForPickup.class) + .entity(getHttpEntity(getHeaders())) + .errorMessage("Could not poll for available submissions") + .build(); + return performRequest(requestSettings); + } + + @Override + public SubmissionsForPickup pollAvailableSubmissionsForDestination(final UUID destinationId, final int offset, final int limit) { final String urlWithQueryParams = UriComponentsBuilder.fromHttpUrl(config.getSubmissionsEndpoint()) .queryParam("destinationId", destinationId) .queryParam("limit", limit) @@ -130,14 +145,13 @@ public class SubmissionApiService implements SubmissionService { .method(HttpMethod.GET) .responseType(SubmissionsForPickup.class) .entity(getHttpEntity(getHeaders())) - .params(params) .errorMessage("Could not poll for available submissions on destination " + destinationId) .build(); return performRequest(requestSettings); } @Override - public Submission getSubmission(final UUID submissionId) { + public Submission getSubmission(final UUID submissionId) throws RestApiException { final Map<String, Object> params = Map.of("submissionId", submissionId); final RequestSettings requestSettings = RequestSettings.builder() .url(config.getSubmissionEndpoint()) @@ -157,12 +171,19 @@ public class SubmissionApiService implements SubmissionService { final Class<T> responseType = requestSettings.responseType; final Map<String, ?> params = requestSettings.params; try { - return restTemplate.exchange(url, method, entity, responseType, params).getBody(); + return evaluateStatusCode(restTemplate.exchange(url, method, entity, responseType, params)); } catch (final RestClientException e) { throw new RestApiException(e.getMessage(), e); } } + private <T> T evaluateStatusCode(final ResponseEntity<T> response) { + if(response.getStatusCode().equals(HttpStatus.NOT_FOUND)){ + throw new RestApiException(response.getStatusCode(), "requested entity could not be found"); + } + return response.getBody(); + } + private HttpEntity<String> getHttpEntity(final HttpHeaders headers) { return getHttpEntity(null, headers); } @@ -204,6 +225,7 @@ public class SubmissionApiService implements SubmissionService { private HttpEntity entity; private Class responseType; private String errorMessage; - private Map<String, Object> params; + @Builder.Default + private Map<String, Object> params = new HashMap<>(); } } diff --git a/core/src/main/java/dev/fitko/fitconnect/core/util/EventLogUtil.java b/core/src/main/java/dev/fitko/fitconnect/core/util/EventLogUtil.java index 875133cc2011a6d9e3d9ea584d7617d97dff888f..aad6d08a97b22e227f72124789621887b73ae69f 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/util/EventLogUtil.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/util/EventLogUtil.java @@ -4,19 +4,31 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; -import dev.fitko.fitconnect.api.domain.model.event.*; +import dev.fitko.fitconnect.api.config.ApplicationConfig; +import dev.fitko.fitconnect.api.domain.model.event.Event; +import dev.fitko.fitconnect.api.domain.model.event.EventClaimFields; +import dev.fitko.fitconnect.api.domain.model.event.EventIssuer; +import dev.fitko.fitconnect.api.domain.model.event.EventLog; +import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; +import dev.fitko.fitconnect.api.domain.model.event.TypeAndUUID; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; +import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.exceptions.EventCreationException; import dev.fitko.fitconnect.api.exceptions.EventLogException; import java.text.ParseException; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; import java.util.function.Predicate; import java.util.stream.Collectors; import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; -import static dev.fitko.fitconnect.api.domain.model.event.EventIssuer.*; +import static dev.fitko.fitconnect.api.domain.model.event.EventIssuer.DESTINATION; +import static dev.fitko.fitconnect.api.domain.model.event.EventIssuer.SUBMISSION_SERVICE; +import static dev.fitko.fitconnect.api.domain.model.event.EventIssuer.UNDEFINED; public final class EventLogUtil { @@ -79,8 +91,8 @@ public final class EventLogUtil { final String eventName = eventEntry.getKey(); final Map<String, Object> eventPayload = eventEntry.getValue(); final List<Problem> problems = new ArrayList<>(); - if (eventPayload.containsKey("problems")) { - problems.addAll((List<Problem>) eventPayload.get("problems")); + if (eventPayload.containsKey(EventClaimFields.PROBLEMS)) { + problems.addAll((List<Problem>) eventPayload.get(EventClaimFields.PROBLEMS)); } return EventLogEntry.builder() .event(Event.fromSchemaUri(eventName)) @@ -88,7 +100,7 @@ public final class EventLogUtil { .issueTime(claimsSet.getIssueTime()) .eventId(getEventId(claimsSet)) .caseId(getTransactionClaim(claimsSet)) - .submissionId(getSubmissionId(claimsSet)) + .submissionId(getSubmissionIdFromJWT(claimsSet)) .problems(problems) .build(); } @@ -107,10 +119,14 @@ public final class EventLogUtil { } } - private static UUID getSubmissionId(final JWTClaimsSet claimsSet) { + public static UUID getSubmissionIdFromJWT(final JWTClaimsSet claimsSet) { return TypeAndUUID.fromString(claimsSet.getSubject()).getUuid(); } + public static UUID getSubmissionIdFromJWT(final SignedJWT signedJWT) { + return TypeAndUUID.fromString(getClaimsSet(signedJWT).getSubject()).getUuid(); + } + private static UUID getEventId(final JWTClaimsSet claimsSet) { return UUID.fromString(claimsSet.getJWTID()); } @@ -139,18 +155,38 @@ public final class EventLogUtil { } } + public static AuthenticationTags getAuthenticationTags(final SignedJWT jwt) { + return getAuthenticationTags(getClaimsSet(jwt)); + } + public static AuthenticationTags getAuthenticationTags(final JWTClaimsSet claims) { try { final Map<String, Object> eventsClaim = (Map) claims.getClaim(EventClaimFields.CLAIM_EVENTS); final Map.Entry<String, Map> eventEntry = (Map.Entry) eventsClaim.entrySet().iterator().next(); final Map<String, Object> events = eventEntry.getValue(); - final String authTags = MAPPER.writeValueAsString(events.get("authenticationTags")); + final String authTags = MAPPER.writeValueAsString(events.get(EventClaimFields.AUTHENTICATION_TAGS)); return MAPPER.readValue(authTags, AuthenticationTags.class); } catch (final JsonProcessingException e) { throw new EventCreationException(e.getMessage(), e); } } + public static String getAuthenticationTagFromEncryptedData(final String encryptedData) { + try { + final String[] parts = encryptedData.split(ApplicationConfig.AUTH_TAG_SPLIT_TOKEN); + return parts[parts.length - 1]; + } catch (final Exception e) { + return null; + } + } + + public static List<SignedJWT> getJwtsFromEvents(final Event event, final Submission submission, final EventLog eventLog) { + return getJWTSFromEvents(eventLog.getEventLogs()).stream() + .filter(jwt -> getSubmissionIdFromJWT(jwt).equals(submission.getSubmissionId())) + .filter(jwt -> getEventFromJWT(jwt).equals(event)) + .collect(Collectors.toList()); + } + public static Predicate<SignedJWT> eventEqualsSubmissionId(final UUID submissionId) { return jwt -> { try { diff --git a/core/src/main/java/dev/fitko/fitconnect/core/util/FileUtil.java b/core/src/main/java/dev/fitko/fitconnect/core/util/FileUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..a3ee31e0c6e9ef561dfbabaa130a9992638b0c22 --- /dev/null +++ b/core/src/main/java/dev/fitko/fitconnect/core/util/FileUtil.java @@ -0,0 +1,52 @@ +package dev.fitko.fitconnect.core.util; + +import dev.fitko.fitconnect.api.exceptions.FileHandlingException; +import dev.fitko.fitconnect.api.exceptions.RootCertificateException; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class FileUtil { + + public static List<String> loadContentOfFilesInDirectory(String directoryPath) { + + File folder = new File(Objects.requireNonNull(FileUtil.class.getClassLoader().getResource(directoryPath)).getFile()); + + return Arrays.stream(Objects.requireNonNull(folder.listFiles())).map(file -> { + try { + return Files.readString(file.toPath()); + } catch (IOException e) { + throw new FileHandlingException(e); + } + }).collect(Collectors.toList()); + } + + public static String loadContentOfFile(String filePath) { + + try { + return Files.readString(new File(Objects.requireNonNull(FileUtil.class.getClassLoader().getResource(filePath)).getFile()).toPath()); + } catch (IOException e) { + throw new FileHandlingException(e); + } + } + + public static X509Certificate convertToX509Certificate(String certificateString) { + + InputStream certificateInputStream = new ByteArrayInputStream(certificateString.getBytes()); + try { + return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certificateInputStream); + } catch (CertificateException e) { + throw new RootCertificateException(e); + } + } +} diff --git a/core/src/main/java/dev/fitko/fitconnect/core/util/Strings.java b/core/src/main/java/dev/fitko/fitconnect/core/util/Strings.java index c422a8f36da209601d4479ace25d8e0670a73ef0..5d323824e5b6d10ebc3095692b86d5925765c765 100644 --- a/core/src/main/java/dev/fitko/fitconnect/core/util/Strings.java +++ b/core/src/main/java/dev/fitko/fitconnect/core/util/Strings.java @@ -19,7 +19,7 @@ public final class Strings { * Tests a given string on not null and not empty * * @param s string to test - * @return true if the string is not null AND not empty, false if one condition does not apply + * @return true if the string is not null AND not empty, false if one dev.fitko.fitconnect.integrationtests.condition does not apply */ public static boolean isNotNullOrEmpty(final String s) { return !isNullOrEmpty(s); 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 37455f13cf52a0e8c6b8e053712ae8ce11da4454..8300a33054ab1797bad2ea6a915a11760080e885 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 @@ -1,5 +1,6 @@ package dev.fitko.fitconnect.core.validation; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -11,15 +12,35 @@ import com.nimbusds.jose.jwk.KeyOperation; import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.util.Base64; import dev.fitko.fitconnect.api.config.ApplicationConfig; +import dev.fitko.fitconnect.api.domain.model.destination.Destination; +import dev.fitko.fitconnect.api.domain.model.destination.DestinationService; import dev.fitko.fitconnect.api.domain.model.event.EventClaimFields; +import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; +import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; +import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.AttachmentHashMismatch; +import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.IncorrectAttachmentAuthenticationTag; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataHashMismatch; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataJsonSyntaxViolation; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataXmlSyntaxViolation; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.IncorrectDataAuthenticationTag; +import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.*; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentForValidation; +import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; +import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; +import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema; +import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel; +import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; import dev.fitko.fitconnect.api.exceptions.DataIntegrityException; +import dev.fitko.fitconnect.api.exceptions.RootCertificateException; import dev.fitko.fitconnect.api.exceptions.SchemaNotFoundException; import dev.fitko.fitconnect.api.exceptions.ValidationException; import dev.fitko.fitconnect.api.services.crypto.MessageDigestService; import dev.fitko.fitconnect.api.services.schema.SchemaProvider; import dev.fitko.fitconnect.api.services.validation.ValidationService; +import dev.fitko.fitconnect.core.util.FileUtil; import dev.fitko.fitconnect.core.util.Strings; import dev.fitko.fitconnect.jwkvalidator.JWKValidator; import dev.fitko.fitconnect.jwkvalidator.JWKValidatorBuilder; @@ -33,7 +54,6 @@ import org.xml.sax.XMLReader; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.InetSocketAddress; @@ -46,52 +66,43 @@ import java.text.SimpleDateFormat; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; +import static dev.fitko.fitconnect.core.util.EventLogUtil.getAuthenticationTagFromEncryptedData; import static dev.fitko.fitconnect.jwkvalidator.JWKValidator.withX5CValidation; import static dev.fitko.fitconnect.jwkvalidator.JWKValidator.withoutX5CValidation; +import static java.util.Objects.isNull; public class DefaultValidationService implements ValidationService { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultValidationService.class); - private static final ObjectMapper MAPPER = new ObjectMapper().setDateFormat(new SimpleDateFormat("yyyy-MM-dd")); + private static final ObjectMapper MAPPER = new ObjectMapper().setDateFormat(new SimpleDateFormat("yyyy-MM-dd")).setSerializationInclusion(JsonInclude.Include.NON_NULL); private static final JsonSchemaFactory SCHEMA_FACTORY_DRAFT_2020 = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012); - private static final JsonSchemaFactory SCHEMA_FACTORY_DRAFT_2007 = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7); public static final String VALID_SCHEMA_URL_EXPRESSION = "https://schema\\.fitko\\.de/fit-connect/metadata/1\\.\\d+\\.\\d+/metadata.schema.json"; private final MessageDigestService messageDigestService; private final SchemaProvider schemaProvider; private final ApplicationConfig config; + private final List<String> rootCertificates; - public DefaultValidationService(final ApplicationConfig config, final MessageDigestService messageDigestService, final SchemaProvider schemaProvider) { + public DefaultValidationService(final ApplicationConfig config, final MessageDigestService messageDigestService, + final SchemaProvider schemaProvider, final List<String> rootCertificates) { this.config = config; this.messageDigestService = messageDigestService; this.schemaProvider = schemaProvider; + this.rootCertificates = rootCertificates; } @Override - public ValidationResult validateEncryptionPublicKey(final RSAKey publicKey) { + public ValidationResult validatePublicKey(final RSAKey publicKey, final KeyOperation keyOperation) { try { - validateKey(publicKey, KeyOperation.WRAP_KEY); + return validateKey(publicKey, keyOperation); } catch (final Exception e) { return ValidationResult.error(e); } - return ValidationResult.ok(); - } - - @Override - public ValidationResult validateSignaturePublicKey(final RSAKey signatureKey) { - try { - validateKey(signatureKey, KeyOperation.VERIFY); - } catch (final Exception e) { - return ValidationResult.error(e); - } - return ValidationResult.ok(); } @Override @@ -113,7 +124,9 @@ public class DefaultValidationService implements ValidationService { public ValidationResult validateMetadataSchema(final Metadata metadata) { if (metadata.getSchema() != null && !metadata.getSchema().isEmpty() && !metadata.getSchema().matches(VALID_SCHEMA_URL_EXPRESSION)) { - return ValidationResult.error(new ValidationException("The provided metadata schema is not supported.")); + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#required-schema-reference + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#metadatenschema + return ValidationResult.problem(new UnsupportedMetadataSchema(metadata.getSchema())); } try { @@ -121,7 +134,8 @@ public class DefaultValidationService implements ValidationService { final JsonNode inputNode = MAPPER.readTree(metadataJson); return validate2020JsonSchema(schemaProvider.loadMetadataSchema(config.getMetadataSchemaWriteVersion()), inputNode); } catch (final JsonProcessingException e) { - return ValidationResult.error(e); + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#schema-pr%C3%BCfung + return ValidationResult.withErrorAndProblem(e, new MetadataSchemaViolation()); } } @@ -143,7 +157,7 @@ public class DefaultValidationService implements ValidationService { final byte[] originalHash = messageDigestService.fromHexString(originalHexHash); final boolean hashesAreNotEqual = !messageDigestService.verify(originalHash, data); if (hashesAreNotEqual) { - return ValidationResult.error(new DataIntegrityException("Hash sum of transmitted data does not equal the hash of the sender.")); + return ValidationResult.error(new DataIntegrityException("Metadata contains invalid hash value")); } return ValidationResult.ok(); } catch (final IllegalArgumentException | NullPointerException e) { @@ -151,6 +165,7 @@ public class DefaultValidationService implements ValidationService { } } + @Override public ValidationResult validateJsonFormat(final String json) { try { @@ -190,15 +205,157 @@ public class DefaultValidationService implements ValidationService { return ValidationResult.ok(); } + @Override + public ValidationResult validateData(final byte[] decryptedData, final Submission submission, final Metadata metadata, final AuthenticationTags authenticationTags) { + + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen-1 + final var dataAuthTag = getAuthenticationTagFromEncryptedData(submission.getEncryptedData()); + if (!authenticationTags.getData().equals(dataAuthTag)) { + return ValidationResult.problem(new IncorrectDataAuthenticationTag()); + } + + final Data data = metadata.getContentStructure().getData(); + + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#submission-data-hash + final String hashFromSender = data.getHash().getContent(); + final ValidationResult hashValidation = validateHashIntegrity(hashFromSender, decryptedData); + if (hashValidation.hasError()) { + return ValidationResult.problem(new DataHashMismatch()); + } + + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#syntax-validierung-1 + if (data.getSubmissionSchema().getMimeType().equals(MimeType.APPLICATION_JSON)) { + final ValidationResult jsonValidation = validateJsonFormat(new String(decryptedData, StandardCharsets.UTF_8)); + if (jsonValidation.hasError()) { + return ValidationResult.problem(new DataJsonSyntaxViolation()); + } + } + + if (data.getSubmissionSchema().getMimeType().equals(MimeType.APPLICATION_XML)) { + final ValidationResult xmlValidation = validateXmlFormat(new String(decryptedData, StandardCharsets.UTF_8)); + if (xmlValidation.hasError()) { + return ValidationResult.problem(new DataXmlSyntaxViolation()); + } + } + return ValidationResult.ok(); + } + + @Override + public ValidationResult validateAttachments(final List<AttachmentForValidation> attachmentsForValidation, final AuthenticationTags authenticationTags) { + + final Map<UUID, String> eventAuthTags = authenticationTags.getAttachments(); + final List<Problem> validationProblems = new ArrayList<>(); + + for (final AttachmentForValidation attachment : attachmentsForValidation) { + + final UUID attachmentId = attachment.getAttachmentId(); + + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen-2 + final String authTagFromEvent = eventAuthTags.get(attachmentId); + final String authTagFromAttachment = getAuthenticationTagFromEncryptedData(attachment.getEncryptedData()); + if (!authTagFromEvent.equals(authTagFromAttachment)) { + validationProblems.add(new IncorrectAttachmentAuthenticationTag(attachmentId)); + } + + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#attachment-hash + final ValidationResult validationResult = validateHashIntegrity(attachment.getMetadataHash(), attachment.getDecryptedData()); + if (validationResult.hasError()) { + validationProblems.add(new AttachmentHashMismatch(attachmentId)); + } + } + + return validationProblems.isEmpty() ? ValidationResult.ok() : ValidationResult.problems(validationProblems); + } + + @Override + public ValidationResult validateMetadata(final Metadata metadata, final Submission submission, final Destination destination, final AuthenticationTags eventAuthenticationTags) { + + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen + final var submissionMetadataAuthTag = getAuthenticationTagFromEncryptedData(submission.getEncryptedMetadata()); + if (!eventAuthenticationTags.getMetadata().equals(submissionMetadataAuthTag)) { + return ValidationResult.problem(new IncorrectMetadataAuthenticationTag()); + } + + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#syntax-validierung + if (invalidJsonSyntax(metadata)) { + return ValidationResult.problem(new MetadataJsonSyntaxViolation()); + } + + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#fachdatensatz + if (metadata.getContentStructure().getData() == null) { + return ValidationResult.problem(new MissingData()); + } + + final ValidationResult validationResult = validateMetadataSchema(metadata); + if (validationResult.hasProblems()) { + return validationResult; + } + + if (metadata.getPublicServiceType() != null) { + final String serviceIdentifier = metadata.getPublicServiceType().getIdentifier(); + if (!serviceIdentifier.equals(submission.getServiceType().getIdentifier())) { + return ValidationResult.problem(new ServiceMismatch()); + } + } + + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#verwaltungsleistung-abgleichen-ii + final var destinationService = destination.getServices() + .stream() + .map(DestinationService::getIdentifier) + .filter(identifier -> identifier.equals(submission.getServiceType().getIdentifier())) + .findFirst(); + + if (destinationService.isEmpty()) { + return ValidationResult.problem(new UnsupportedService()); + } + + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#fachdatenschema + if (destination.getServices().isEmpty()) { + return ValidationResult.problem(new UnsupportedDataSchema()); + } + final URI submissionDataSchemaUri = metadata.getContentStructure().getData().getSubmissionSchema().getSchemaUri(); + final boolean matchingSchemas = matchingDestinationAndSubmissionSchema(destination, submissionDataSchemaUri); + if (!matchingSchemas) { + return ValidationResult.problem(new UnsupportedDataSchema()); + } + + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#liste-der-anlagen-abgleichen + final ValidationResult attachmentValidation = checkAttachmentCount(submission, metadata, eventAuthenticationTags); + if (attachmentValidation.hasProblems()) { + return attachmentValidation; + } + + // https://docs.fitko.de/fit-connect/docs/receiving/verification/#r%C3%BCckkanal + final ReplyChannel submissionReplyChannel = metadata.getReplyChannel(); + final ReplyChannel destinationReplyChannel = destination.getReplyChannels(); + + if (submissionReplyChannel != null) { + final List<Class<?>> submissionReplyChannelClass = getNonNullReplyChannelTypes(submissionReplyChannel); + final List<Class<?>> destinationReplyChannelClass = getNonNullReplyChannelTypes(destinationReplyChannel); + if (!new HashSet<>(destinationReplyChannelClass).containsAll(submissionReplyChannelClass)) { + return ValidationResult.problem(new UnsupportedReplyChannel()); + } + } + + return ValidationResult.ok(); + } + + private static boolean matchingDestinationAndSubmissionSchema(final Destination destination, final URI submissionDataSchemaUri) { + return destination.getServices().stream() + .flatMap(service -> service.getSubmissionSchemas().stream()) + .map(SubmissionSchema::getSchemaUri) + .anyMatch(submissionDataSchemaUri::equals); + } + private ValidationResult validate2020JsonSchema(final String schema, final JsonNode inputNode) { return returnValidationResult(SCHEMA_FACTORY_DRAFT_2020.getSchema(schema).validate(inputNode)); } - private void validateKey(final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException, CertificateEncodingException { + private ValidationResult validateKey(final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException { if (config.getCurrentEnvironment().isAllowInsecurePublicKey()) { - validateWithoutCertChain(publicKey, purpose); + return validateWithoutCertChain(publicKey, purpose); } else { - validateCertChain(publicKey, purpose); + return validateCertChain(publicKey, purpose); } } @@ -206,68 +363,72 @@ public class DefaultValidationService implements ValidationService { if (errors.isEmpty()) { return ValidationResult.ok(); } - return ValidationResult.error(new ValidationException(errorsToSingleString(errors))); - } - - private void validateWithX509AndProxy(final List<String> pemCerts, final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException { - final var proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(config.getHttpProxyHost(), config.getHttpProxyPort())); - LOGGER.info("Using proxy {} for key validation", proxy); - addLogLevel(withX5CValidation() - .withProxy(proxy) - .withRootCertificatesAsPEM(pemCerts)) - .validate(publicKey, purpose); - } - - - private void validateWithX509AndWithoutProxy(final List<String> pemCerts, final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException { - LOGGER.info("No proxy set for validation"); - addLogLevel(withX5CValidation() - .withoutProxy() - .withRootCertificatesAsPEM(pemCerts)) - .validate(publicKey, purpose); + return ValidationResult.withErrorAndProblem(new ValidationException(errorsToSingleString(errors)), new MetadataSchemaViolation()); } private JWKValidator addLogLevel(final JWKValidatorBuilder.JWKValidatorX5CErrorHandling validator) { - if (validateSilent()) { - return validator.withoutThrowingExceptions().withErrorLogLevel(LogLevel.WARN).build(); - } else { - return validator.withThrowingExceptions().withErrorLogLevel(LogLevel.ERROR).build(); - } + return validator.withThrowingExceptions().withErrorLogLevel(LogLevel.ERROR).build(); } - private void validateWithoutCertChain(final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException { + private ValidationResult validateWithoutCertChain(final RSAKey publicKey, final KeyOperation purpose) { LOGGER.info("Validating public key without XC5 certificate chain"); - withoutX5CValidation().withErrorLogLevel(validateSilent() ? LogLevel.WARN : LogLevel.ERROR).build().validate(publicKey, purpose); + try { + withoutX5CValidation().withErrorLogLevel(config.getCurrentEnvironment().isAllowInsecurePublicKey() ? + LogLevel.WARN : LogLevel.ERROR).build().validate(publicKey, purpose); + } catch (JWKValidationException exception) { + return ValidationResult.error(exception); + } + return ValidationResult.ok(); } - private void validateCertChain(final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException, CertificateEncodingException { - final List<String> pemCerts = getPemCerts(publicKey.getParsedX509CertChain()); + ValidationResult validateCertChain(final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException { + final List<X509Certificate> x509CertChain = publicKey.getParsedX509CertChain(); + if (x509CertChain == null) { + throw new IllegalStateException("public key with id '" + publicKey.getKeyID() + "' does not contain a certificate chain"); + } + final List<String> trustedRootCertificates = this.loadTrustedRootCertificates(); LOGGER.info("Validation public key with XC5 certificate chain checks"); if (isProxySet()) { - validateWithX509AndProxy(pemCerts, publicKey, purpose); + return validateWithX509AndProxy(trustedRootCertificates, publicKey, purpose); } else { - validateWithX509AndWithoutProxy(pemCerts, publicKey, purpose); + return validateWithX509AndWithoutProxy(trustedRootCertificates, publicKey, purpose); } } - private List<String> getPemCerts(final List<X509Certificate> certChain) throws CertificateEncodingException { - final List<String> pemCerts = new ArrayList<>(); - for (final X509Certificate cert : certChain) { - pemCerts.add(getEncodedString(cert)); - } - return pemCerts; - } - - private String getEncodedString(final X509Certificate x509Certificate) throws CertificateEncodingException { - return Base64.encode(x509Certificate.getEncoded()).toString(); + private List<String> loadTrustedRootCertificates() { + + return rootCertificates.stream() + .map(FileUtil::convertToX509Certificate) + .map(cert -> { + try { + return Base64.encode(cert.getEncoded()).toString(); + } catch (CertificateEncodingException e) { + throw new RootCertificateException(e); + } + }).collect(Collectors.toList()); } private boolean isProxySet() { return config.getHttpProxyPort() != null && Strings.isNotNullOrEmpty(config.getHttpProxyHost()); } - private boolean validateSilent() { - return config.getCurrentEnvironment().isAllowInsecurePublicKey(); + private ValidationResult validateWithX509AndProxy(final List<String> trustedRootCertificates, final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException { + final var proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(config.getHttpProxyHost(), config.getHttpProxyPort())); + LOGGER.info("Using proxy {} for key validation", proxy); + addLogLevel(withX5CValidation() + .withProxy(proxy) + .withRootCertificatesAsPEM(trustedRootCertificates)) + .validate(publicKey); + return ValidationResult.ok(); + } + + private ValidationResult validateWithX509AndWithoutProxy(final List<String> trustedRootCertificates, final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException { + LOGGER.info("No proxy set for validation"); + addLogLevel(withX5CValidation() + .withoutProxy() + .withRootCertificatesAsPEM(trustedRootCertificates)) + .validate(publicKey); + return ValidationResult.ok(); } private String errorsToSingleString(final Set<ValidationMessage> errors) { @@ -282,4 +443,43 @@ public class DefaultValidationService implements ValidationService { return saxParserFactory.newSAXParser().getXMLReader(); } + private static List<Class<?>> getNonNullReplyChannelTypes(final ReplyChannel destinationReplyChannel) { + return Stream.of(destinationReplyChannel.getEMail(), + destinationReplyChannel.getDeMail(), + destinationReplyChannel.getFink(), + destinationReplyChannel.getElster()) + .filter(Objects::nonNull) + .map(Object::getClass) + .collect(Collectors.toList()); + } + + private boolean invalidJsonSyntax(final Metadata metadata) { + try { + return metadata == null || validateJsonFormat(MAPPER.writeValueAsString(metadata)).hasError(); + } catch (final JsonProcessingException | NullPointerException e) { + return true; + } + } + + private ValidationResult checkAttachmentCount(final Submission submission, final Metadata metadata, final AuthenticationTags authenticationTags) { + + final List<UUID> submissionAttachmentIds = submission.getAttachments(); + final List<ApiAttachment> metadataAttachments = metadata.getContentStructure().getAttachments(); + final Map<UUID, String> attachmentAuthTags = authenticationTags.getAttachments(); + + if ((isNull(submissionAttachmentIds) || submissionAttachmentIds.isEmpty()) && (isNull(attachmentAuthTags) || attachmentAuthTags.isEmpty())) { + return ValidationResult.ok(); + } + + if (metadataAttachments.size() != submissionAttachmentIds.size() + || attachmentAuthTags.size() != submissionAttachmentIds.size() + || !attachmentAuthTags.keySet().containsAll(submissionAttachmentIds) + || !submissionAttachmentIds.stream().allMatch(id -> metadataAttachments.stream().anyMatch(m -> m.getAttachmentId().equals(id))) + || !submissionAttachmentIds.stream().allMatch(attachmentAuthTags::containsKey)) { + return ValidationResult.problem(new AttachmentsMismatch()); + } + + return ValidationResult.ok(); + } + } diff --git a/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-14.pem b/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-14.pem new file mode 100644 index 0000000000000000000000000000000000000000..f4182592f83967c0c55b74ad915d00093675fd56 --- /dev/null +++ b/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-14.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEhjCCA26gAwIBAgIFAMZqKTUwDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMC +REUxGTAXBgNVBAoTEFBLSS0xLVZlcndhbHR1bmcxHDAaBgNVBAMTE1BDQS0xLVZl +cndhbHR1bmctMTQwHhcNMTMxMjAxMDAwMDAwWhcNMjMxMjMxMjM1OTU5WjBGMQsw +CQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEcMBoGA1UEAxMT +UENBLTEtVmVyd2FsdHVuZy0xNDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgC +ggEBAIJoqiA4T2iN/qq+0g/xdJ1rpsB/yozONjC0mmlSeBoBKtArrxg51GYFl/Ep +WzEF3QffcE5jg4xEgcNk8CXJDKPKHAldLsYEohx5So8EJsuGRLrjkt6SC5Kf1+aM +Vq7mt9qUPIuvloaQWiVkNvDFNSeam+K0I/zjK++0xHuQRACIaUbtmShuahKHlJR3 +DUM6CT9uWQB6FWv+qRAo6xsmHP/bYSMAv5/Z0bOnLZ6cruZ+QPGPksP0VX9M2pXY +cecYAlbYzuNYWAUhmx1P/ZhbBa0SKhDTO+JpHeCnKRiLt7MKPnUPyAtib7600CsI +TvFAjem9JanXKV0wK4sIsAMvg00CARGjggF7MIIBdzAPBgNVHRMBAf8EBTADAQH/ +MD8GA1UdIAQ4MDYwNAYLKwYBBAG9dAECAQMwJTAjBggrBgEFBQcCARYXaHR0cHM6 +Ly93d3cuYnNpLmJ1bmQuZGUwgbwGA1UdHwSBtDCBsTBioGCgXoZcbGRhcDovL3g1 +MDAuYnVuZC5kZS9DTj1QQ0EtMS1WZXJ3YWx0dW5nLTE0LE89UEtJLTEtVmVyd2Fs +dHVuZyxDPURFP2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3QwS6BJoEeGRWh0dHA6 +Ly94NTAwLmJ1bmQuZGUvY2dpLWJpbi9zaG93X2F0dHI/Y249UENBLTEtVmVyd2Fs +dHVuZy0xNCZhdHRyPWNybDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBPzjBBU +Oufi9p0vosZp9r10O+QYMDUGA1UdEQQuMCyBEVYtUEtJQGJzaS5idW5kLmRlhhdo +dHRwczovL3d3dy5ic2kuYnVuZC5kZTANBgkqhkiG9w0BAQsFAAOCAQEAFAHRjPm6 +D4Om2S5P1Gcya4W+AUr5bbMFxTvi+Jl1odoIDtBkKkYQ0rv6HOx8kzBaOzwM7InU +8/Cp3P4yHpPL6dSBgtopqkZ5xDCRoeLIzRtwKzctev2sQi9UgXu8HOW4N8A+abNB +dEj5ehviHztZXlHuhfXAEQA6Q/Gc0XR5CWd5yQkirWFFaIhtINHs3UckH8dsC+Se +5AVpq7mG+DtOMtaMQRTg3ElBBdNivQ0jRz4hIur1B4TrhNNZZxUhkzTjAZ7EoA6w +2nZwPs29xgPt39Qf+3s/EfdoWzrTflrJ8tlBBueJTkI+PXIufURzDC40ciFm8g2Q +XJpE4ye1vXCZgg== +-----END CERTIFICATE----- diff --git a/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-15.pem b/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-15.pem new file mode 100644 index 0000000000000000000000000000000000000000..1a2ced199c3faf6a95690f44acb3dfa381a33f43 --- /dev/null +++ b/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-15.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEiDCCA3CgAwIBAgIFAKSHzIkwDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMC +REUxGTAXBgNVBAoTEFBLSS0xLVZlcndhbHR1bmcxHDAaBgNVBAMTE1BDQS0xLVZl +cndhbHR1bmctMTUwHhcNMTUwODE4MDkwMTI4WhcNMjUwODE4MjM1OTU5WjBGMQsw +CQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEcMBoGA1UEAxMT +UENBLTEtVmVyd2FsdHVuZy0xNTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAJ7vMHkXf9JYOROTvOez3qM8c47Lv/wqIkauT0+m9IuPG/DSnzv8cTNqaOwH +Zt3ga9dwiL5GMbfIDfyx49j56HBPQafS8E8N6v5XOHYemzrmTeD6e09tVsugoaEt +pSN1VPNF3JKQM7vyl2IxWakSVgBtWffNrV1/X4199jDXrInsyNxMiyG20RLMEY3D +BwUVyiK/yJPoWob3vIF4gSxode/i1h6IvsS//sJYll85BGPivNtRQA20JkcRZc/l +wh+B+6WFIhEmNpoR5qN8nPVuWw7u2PEl9aptJgIUivSGuVueu9aikfQRiBZ0AqwI +OGg7PrLG3rg8OOrqzr4z38YxL28CAwEAAaOCAXswggF3MA8GA1UdEwEB/wQFMAMB +Af8wPwYDVR0gBDgwNjA0BgsrBgEEAb10AQIBAzAlMCMGCCsGAQUFBwIBFhdodHRw +czovL3d3dy5ic2kuYnVuZC5kZTCBvAYDVR0fBIG0MIGxMGKgYKBehlxsZGFwOi8v +eDUwMC5idW5kLmRlL0NOPVBDQS0xLVZlcndhbHR1bmctMTUsTz1QS0ktMS1WZXJ3 +YWx0dW5nLEM9REU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDBLoEmgR4ZFaHR0 +cDovL3g1MDAuYnVuZC5kZS9jZ2ktYmluL3Nob3dfYXR0cj9jbj1QQ0EtMS1WZXJ3 +YWx0dW5nLTE1JmF0dHI9Y3JsMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU1ikn +WlJeLd3JZ+o81or280pnKTwwNQYDVR0RBC4wLIERVi1QS0lAYnNpLmJ1bmQuZGWG +F2h0dHBzOi8vd3d3LmJzaS5idW5kLmRlMA0GCSqGSIb3DQEBCwUAA4IBAQBdqWrx +75QWqRUwsDyHiKQle99K7PgqnVY5YR9SOswl7MT7jzK7yGfdGK5gQzyHDpGsoPJ/ +uIR2IkMb/pFFDA5D+Csw9mComooqP0n9wQCx64B1Q7o5DBvWftf4SCElTkbzSnR0 +qKqcP6U0I3If7OvPIgCiV3fM2EqEqAsFwvMtsd5gymLloR2LzUSCTdQbgq+bleR+ +G7mzDLw2QUgeVCP58RsSYECAPnRcY1qP9r0zuw2vILQuWZrBs5SY8ml6an730wmV +3kUftPb2r8QDq2ZCRbNTcpuV5KYnXykEiUKPtBbyLdcV/LCPLk70zpe1dzAplr3X +GFI0v+/UY+SXm8fI +-----END CERTIFICATE----- diff --git a/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-17.pem b/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-17.pem new file mode 100644 index 0000000000000000000000000000000000000000..7bf9bd30ec484578e509afb36cb5f7a851dd60be --- /dev/null +++ b/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-17.pem @@ -0,0 +1,37 @@ +-----BEGIN CERTIFICATE----- +MIIGiDCCBHCgAwIBAgIFAKdNgZUwDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMC +REUxGTAXBgNVBAoTEFBLSS0xLVZlcndhbHR1bmcxHDAaBgNVBAMTE1BDQS0xLVZl +cndhbHR1bmctMTcwHhcNMTYxMjAxMDAwMDAwWhcNMjYxMjMxMjM1OTU5WjBGMQsw +CQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEcMBoGA1UEAxMT +UENBLTEtVmVyd2FsdHVuZy0xNzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAJYQHveQAaTZlo4uZqQB4E7NuiMFngaOwJHGL7cpAOdFYYe4pV828HWJkDVh +zKkeZbM229Weqs8DlPTIX34Ec/XPstJY3cQX4r93WeKoA1SMt2KX0CoPOAFz5BaG +C9KG+GSAwc052FGjxCr+Uf0NyhPHFCWoH3hN+H9rNrLV+CnKDWVQB+LjuZIiou4N +WLG1vSOQAPTSRcsghFScecab+HvfsnWkwNGrRJd0M0QxtZ+/1MnoeH4UfIazhYpM +gR3/+EdlLcGrzDvLaPcncXChJBL2bCDhVTsw2w1zlVQeGXZF9s2e0nXy4k4OvNmP +QgzFDNkQ2O6qAKJoTUnPgc2PGnvD1EopUKLuRM4orFOJZurTA1rHLJhNtfsgzx3B +/1clBBN2cSF339atYW/k4TGn9tVZi/1ecZdNjetvhlMuhq6dm7IwfpqIke0iRP6k +A8NdUckdalHyN3fVn+YTGJG/Mdb42KHdd3GTbPR5Eq3U3uydHCYkodT13jnQfVXH +PTFsMWzo++rz76Q0stqGpw/ZGkY4+L5uPCtShn+pOrE8bR67GhIwWsl6fVc97G30 +8Lg6q7O/6nFJMJ9bC/mwt273CoTntCFAI/FOTOCYSPdDGp2YBfEbld19u75XzY62 +w67uNSRYTbS0x1C1q7JltAZJDqAmBqC21ywUCm1rJLZDTGYhAgMBAAGjggF7MIIB +dzAPBgNVHRMBAf8EBTADAQH/MD8GA1UdIAQ4MDYwNAYLKwYBBAG9dAECAQMwJTAj +BggrBgEFBQcCARYXaHR0cHM6Ly93d3cuYnNpLmJ1bmQuZGUwgbwGA1UdHwSBtDCB +sTBioGCgXoZcbGRhcDovL3g1MDAuYnVuZC5kZS9DTj1QQ0EtMS1WZXJ3YWx0dW5n +LTE3LE89UEtJLTEtVmVyd2FsdHVuZyxDPURFP2NlcnRpZmljYXRlUmV2b2NhdGlv +bkxpc3QwS6BJoEeGRWh0dHA6Ly94NTAwLmJ1bmQuZGUvY2dpLWJpbi9zaG93X2F0 +dHI/Y249UENBLTEtVmVyd2FsdHVuZy0xNyZhdHRyPWNybDAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHP4cgAoNWzXx+2NtTIUPcXrSsSrMDUGA1UdEQQuMCyBEVYt +UEtJQGJzaS5idW5kLmRlhhdodHRwczovL3d3dy5ic2kuYnVuZC5kZTANBgkqhkiG +9w0BAQsFAAOCAgEAf+FXmZcM5ZvaUl3P8tqwKhoG01iXLVXZjzljKbzyGOy4Ut56 +BfiepIjIOUtqMmSKxXm7/dVmmjeoHRgwGV9DTO9A4x/HTNPN76f77NmJzrGliXB1 +lFWfUwou4wdJUh/phqjK6gJ2dGGP2JdxjTE7FKifb4nuWL64zsLr5lxn+nZ6mtl0 +oqgsYDHtXXqhoBZrpyi9kzYWg9c3XWZS2iO2LNJzFQf+xjrlNtbV4Nw1kTBQwzJV +3+MWLOlV4TOSoFitY/A8ufxjUtHIuL7QvgWdIwZz5jU8W3rctDvoJmnUMPkj61As +5OEguVSQbf/opLdLNw6xKyOxrhfi+GZhcNdqmk1AplOIhNM5wX6/LuXOOn9u3Nqx +rxIqFk7EGapMvmtmI5vnzxWaRbVBj3gQwnB4sKtKMhm7+axebY0fRyXA3j9J17Iz +/4qjSDU4dCk4qwOg2TlnQzq2rwZs7CGuzTwXh05ofQnNSMpEma95/J7gmZ9R8rmN +LFUcP43QjINrX52Q9Tz3s4pvf2W041C3neusmnnaSCPlNJJIRe0qNqMHmZUKwsFM +XSzuM6TcY33eIf/aqfum5voXUgAx+f/G51r17dtBiewkYDqi2U38plHtzjLvPUy4 +m1vxNXN7p2wLFTtYw3OTKTHw9ejjuoPBHNHoLzAgatC1WjTgbFsfR7D3wAA= +-----END CERTIFICATE----- diff --git a/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-20.pem b/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-20.pem new file mode 100644 index 0000000000000000000000000000000000000000..1afb21031c97ee13ec769d01b4bcc02b40f4f877 --- /dev/null +++ b/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-20.pem @@ -0,0 +1,37 @@ +-----BEGIN CERTIFICATE----- +MIIGiDCCBHCgAwIBAgIFANoFi14wDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMC +REUxGTAXBgNVBAoTEFBLSS0xLVZlcndhbHR1bmcxHDAaBgNVBAMTE1BDQS0xLVZl +cndhbHR1bmctMjAwHhcNMTkxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBGMQsw +CQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEcMBoGA1UEAxMT +UENBLTEtVmVyd2FsdHVuZy0yMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAIEgWIZDMqr9t0pxV6TJ/dVSxv7UQPItIL9nY2D1PExxuPR2knOGBdWCWxXX +Zdhz3C4XsD8o9INUz3BFDYzvo48PtZitzAawNhgQ09WaBJpqbQkrGrpATAsXSREL +dSxi1vx7yeaMLMc1zH6iwo/Xd7pcQLxiJkgN2lhw4ORoNk4HBYwGcPjz/eEctrRy +EbN1R3WSarg6+J58AKoatNVxw+XTaLmnYthHoJHeCI67nl/Ecrs6rmTivGvVi5PV +RAQ0fCAsR702if0SL1F+YdDd2no+HX/2Kf2HR+/zuvIpctY/iRDRth/vf1IZL8Bx +KHg1NY7i/7wmR3JxyQHnWWL6puOofDLq8i947e4aVHHBP7o6KbOAwau72Goa6XAr +ttzVvDkKuI1wdka+wjNOsXsFJ+D6fSYCKC6x+nX/VCtfm2A0+JeetZV3ovUz387E +l1MGRvxlHcuFWukj/pB6t0lvbJqnkxyq0ZGVweeFHyK6/TxFW2u+2fD/wJOKH+5y +Quo4vKJxvrIK1UeFgRP5LCro+EYCC0kWYfyw9/wMZJDA3h7GbPR4FtNWH6deXGqr +oY+HO3PPFM9o3Jz+xMkrla3kcEeRDjVMGFnSJhr61SvTqhrqYrj9nqx3du3e/mux ++7FxF6m3sYX8ICYppeuGxO1CGMVbC1uqh1gsnVsKv9RRUEw9AgMBAAGjggF7MIIB +dzAPBgNVHRMBAf8EBTADAQH/MD8GA1UdIAQ4MDYwNAYLKwYBBAG9dAECAQMwJTAj +BggrBgEFBQcCARYXaHR0cHM6Ly93d3cuYnNpLmJ1bmQuZGUwgbwGA1UdHwSBtDCB +sTBioGCgXoZcbGRhcDovL3g1MDAuYnVuZC5kZS9DTj1QQ0EtMS1WZXJ3YWx0dW5n +LTIwLE89UEtJLTEtVmVyd2FsdHVuZyxDPURFP2NlcnRpZmljYXRlUmV2b2NhdGlv +bkxpc3QwS6BJoEeGRWh0dHA6Ly94NTAwLmJ1bmQuZGUvY2dpLWJpbi9zaG93X2F0 +dHI/Y249UENBLTEtVmVyd2FsdHVuZy0yMCZhdHRyPWNybDAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFPOPHzb4BN01+d4qhA1jggfppHFCMDUGA1UdEQQuMCyBEVYt +UEtJQGJzaS5idW5kLmRlhhdodHRwczovL3d3dy5ic2kuYnVuZC5kZTANBgkqhkiG +9w0BAQsFAAOCAgEARNpaiEE3gm/gLIfLn0d8TQF8DxHmRFszIhb8NVCXJWlMJn2J +/UW+XzARsJiB4J0X6tvJcV9AjPwmvlgqAdlo/fce3UwLLPHqrSMuF/xS0Do2CeV1 +OjgXC9OZmuJshPCthSqReAh9h5cllKMsMaS/gM0i1o5QOCpa/0bTDxlTZ0exQw+4 +I4O7gi5SABaYyWJhYzLr1EIROaGcvJk78olGQMYnX4CNq3PDXy9HZiKPh3K07V2J +8GM36iGxF1haTnjGopmtcUQ2lP/Ng2P0qwCFZ1UFIxIOTX5//O/rRhAroFdP+NwT +cdJ4S4xPHNffUWPFCrT8ghEj9Hgy7H/uAtgw5PpnYieevKFjRlOvd/vp9tKjvGre +GnU7xxS3xDbuRuNinQyJWVV/29KReumysi27qTS2cJxIYk2WLvt/1eMZt52xzK32 +G5Pn8ocCK2gMFz7ldyH3jTa5BQFuzFSJ05P1tnVkUpeKlU4aSt8GtW884ifvwhkw +OKyAFAQRXvQSFkE505Kzy/6JZcanoy7e/pRlDBtN70MfBOQl8ucVzAVs/tuuTJAg +3cVCmHv/hlEgVRVPaUJXhtX1u+5yUWMEm2380uW9w9/PAbdtm0kfRb19p32YMkDi +hREyKz/Kpgl5NvoU71RRVqISeJhqJ/k2qYfyYRkPRzUiP9qzyyFS7WE8sXs= +-----END CERTIFICATE----- diff --git a/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-23.pem b/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-23.pem new file mode 100644 index 0000000000000000000000000000000000000000..c4ff111515d238e5643906257f3fd6c276c36251 --- /dev/null +++ b/core/src/main/resources/trusted-root-certificates/PCA-1-Verwaltung-23.pem @@ -0,0 +1,37 @@ +-----BEGIN CERTIFICATE----- +MIIGiDCCBHCgAwIBAgIFAKXvkjAwDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMC +REUxGTAXBgNVBAoTEFBLSS0xLVZlcndhbHR1bmcxHDAaBgNVBAMTE1BDQS0xLVZl +cndhbHR1bmctMjMwHhcNMjIxMjAxMDAwMDAwWhcNMzIxMjMxMjM1OTU5WjBGMQsw +CQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEcMBoGA1UEAxMT +UENBLTEtVmVyd2FsdHVuZy0yMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAMXZeBfphlB9m/8J8wVxSjEtOqfdftHEiQeITx31Md9by7+ppxHKzhQcDrRU +3QxZnpNuoj+pIz73ZTPzBuz8PnW5BMnzPYK/0+TJiWDiUmWQ4NxwgHxV+S1KLX6S +gkwr3L6C3VuC54rVnGeDTV6RyVzzQBpaGv1H7E2HRJh7MP3adV+Wk9DyvR4m6AE7 +6R8rI8qCv99pso2y5mAR0ulfA9zyNKxBooYSefGxQgNn+Fwb+41TBMv3iDEro7x5 +3tGHsOC/8F+xOxBe093/P2GHtEdGdAKeo6hhEq8encG0+uHIj4SljiTzKZw7PYcK +LLZvvUyCytSuFA5Z4nRXLO2hsdqaJfT1+qecs1uJjYx0ZlzVGEsIWOpKtbB9vcvq +IkYspykCsWcBnf1tB9F+IERw59jsGD9nZKWIgHRgI5fqHzaf0A+LwbeIO4Fn1keY +wmYF8bNFtMCWW51ukrTQjoOc7EaE9S460E7pdmCG4NGau/bN8iQQf/cDBuRvlEpG +aN7NeaEXWFbOBHyJKWqpquJBABFVZFaaQpBxWlnnV+o1EZ5IZPtefHFSGMxycJRb +m6pVfGmuEo+z7xYClLH1gMDHiIDKy+RbLNMyVWUh5viu28EU29CXA7Nx6LmfLwvl +Gr1vyCu4UAXOTrRZwff0X19tk5TjHy/SK9jnXkKURe9WuKW1AgMBAAGjggF7MIIB +dzAPBgNVHRMBAf8EBTADAQH/MD8GA1UdIAQ4MDYwNAYLBAB/AAcDBgEBAQQwJTAj +BggrBgEFBQcCARYXaHR0cHM6Ly93d3cuYnNpLmJ1bmQuZGUwgbwGA1UdHwSBtDCB +sTBioGCgXoZcbGRhcDovL3g1MDAuYnVuZC5kZS9DTj1QQ0EtMS1WZXJ3YWx0dW5n +LTIzLE89UEtJLTEtVmVyd2FsdHVuZyxDPURFP2NlcnRpZmljYXRlUmV2b2NhdGlv +bkxpc3QwS6BJoEeGRWh0dHA6Ly94NTAwLmJ1bmQuZGUvY2dpLWJpbi9zaG93X2F0 +dHI/Y249UENBLTEtVmVyd2FsdHVuZy0yMyZhdHRyPWNybDAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFJ2BqFcYkLlZ9LOTo6q+LLqE9BnGMDUGA1UdEQQuMCyBEVYt +UEtJQGJzaS5idW5kLmRlhhdodHRwczovL3d3dy5ic2kuYnVuZC5kZTANBgkqhkiG +9w0BAQsFAAOCAgEAQiNGSGjBukYu5TRDaw2J7RPcDZltf/Nz6dmwzo1gXFb7ALnd +X+Kv9OLxqTKcpywwI017xgRjzxoLlk8PZNkxl+2fuY0vqvW/c8ys2/KjXGHTgwKm +xwvDov8vBfQxl4fXiFPxeixZujT0YZKVJ2L+6HcqzrNVNDpgNzpeLPVZxNZc0rOz +3jxnCGwGH0mskPAMjObn9KizDge1Zve9l10jaPyyKeZq+MDhHtbwxBEICC8yCx4W ++FcETRJpQSmfP7Yr33cbitb+IaQpUf9pPRqIRUfc8Zc/lOBv49q2N+iOrCNv5XHw +mS5HnuQ4yvSJBNgVqU+dTHvcZZVjSXCTpdwmzlmJpEtPFtJks09Ug1TYU0gQY1JU +KW3rfndKCcsXcYA0Frvm/EA3arwbXIQRdiz1cQchHSn+IVRiUY7H9Am7ktHhPpM9 +haNpg7rfLQJnYyKKZxEs2LI84oPqRNvHBVFMFO2rY+K9HG/HWV3SixQtLAvLQOAe +vIXuLdCZJMG8O/3Of0xLlGC/GhCXoP0g4lr7KluUzUTZj6K1q0b8NgVUqLtNiKfY +58BefTEwUv4kSMmyQQAH69sx2wLKVKTqkOP3c81jDhf7OQs41cMGu+HRXtgy4FEI +F3WfIOCHR3z1jJckcZwjh4WoaOgv8sGxubwjGLi2ZelCjt9ZKql+oF5LWvY= +-----END CERTIFICATE----- diff --git a/core/src/test/java/dev/fitko/fitconnect/core/SubmissionSenderTest.java b/core/src/test/java/dev/fitko/fitconnect/core/SubmissionSenderTest.java index 46a8f81341450573d2aa69c7c65c0ec54c063f20..88ee81c44b3cddc395b3325f31adae0bcacb26ce 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/SubmissionSenderTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/SubmissionSenderTest.java @@ -14,7 +14,7 @@ import dev.fitko.fitconnect.api.domain.model.event.Event; import dev.fitko.fitconnect.api.domain.model.event.EventClaimFields; import dev.fitko.fitconnect.api.domain.model.event.EventHeaderFields; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; -import dev.fitko.fitconnect.api.domain.model.event.EventStatus; +import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; import dev.fitko.fitconnect.api.domain.model.submission.CreateSubmission; @@ -33,7 +33,6 @@ import dev.fitko.fitconnect.api.services.events.EventLogService; import dev.fitko.fitconnect.api.services.keys.KeyService; import dev.fitko.fitconnect.api.services.submission.SubmissionService; import dev.fitko.fitconnect.api.services.validation.ValidationService; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -62,21 +61,19 @@ import static org.mockito.Mockito.when; public class SubmissionSenderTest { - private SubmissionService submissionServiceMock; - private EventLogService eventLogServiceMock; - private CryptoService cryptoServiceMock; - private ValidationService validationServiceMock; - private KeyService keyServiceMock; - private Sender underTest; + private final SubmissionService submissionServiceMock; + private final EventLogService eventLogServiceMock; + private final CryptoService cryptoServiceMock; + private final ValidationService validationServiceMock; + private final KeyService keyServiceMock; + private final Sender underTest; - @BeforeEach - public void setUp() { + public SubmissionSenderTest(){ submissionServiceMock = mock(SubmissionService.class); eventLogServiceMock = mock(EventLogService.class); cryptoServiceMock = mock(CryptoService.class); validationServiceMock = mock(ValidationService.class); keyServiceMock = mock(KeyService.class); - underTest = new SubmissionSender( submissionServiceMock, eventLogServiceMock, @@ -86,25 +83,6 @@ public class SubmissionSenderTest { ); } - @Test - void validatePublicKey() throws JOSEException { - - //Given - final RSAKey publicKey = new RSAKeyGenerator(2048) - .keyUse(KeyUse.ENCRYPTION) - .keyID(UUID.randomUUID().toString()) - .generate() - .toPublicJWK(); - - when(validationServiceMock.validateEncryptionPublicKey(any())).thenReturn(ValidationResult.ok()); - - // When - final ValidationResult validationResult = underTest.validatePublicKey(publicKey); - - // Then - assertTrue(validationResult.isValid()); - } - @Test void encryptBytes() throws JOSEException { @@ -423,12 +401,12 @@ public class SubmissionSenderTest { final UUID destinationId = UUID.randomUUID(); final UUID submissionId = UUID.randomUUID(); - final EventStatus expectedStatus = new EventStatus(ACCEPTED, Collections.emptyList()); + final SubmissionStatus expectedStatus = new SubmissionStatus(ACCEPTED, Collections.emptyList()); when(eventLogServiceMock.getLastedEvent(any(), any(), any(), any())).thenReturn(expectedStatus); // When - final EventStatus status = underTest.getLastedEvent(destinationId, caseId, submissionId, new AuthenticationTags()); + final SubmissionStatus status = underTest.getLastedEvent(destinationId, caseId, submissionId, new AuthenticationTags()); // Then assertThat(status.getStatus(), is(ACCEPTED)); diff --git a/core/src/test/java/dev/fitko/fitconnect/core/SubmissionSubscriberTest.java b/core/src/test/java/dev/fitko/fitconnect/core/SubmissionSubscriberTest.java index 8c267a7d6b9ed1f5abafbe27f117c66fbae524ac..08813b25f7a59a1d805dccdaf31f56828c0c8506 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/SubmissionSubscriberTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/SubmissionSubscriberTest.java @@ -7,8 +7,14 @@ import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; import com.nimbusds.jwt.SignedJWT; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; import dev.fitko.fitconnect.api.domain.model.event.EventPayload; +import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataHashMismatch; +import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MetadataSchemaViolation; import dev.fitko.fitconnect.api.domain.model.event.problems.submission.AttachmentsMismatch; +import dev.fitko.fitconnect.api.domain.model.metadata.Hash; import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentForValidation; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; import dev.fitko.fitconnect.api.domain.model.submission.SubmissionsForPickup; @@ -22,7 +28,6 @@ import dev.fitko.fitconnect.api.services.events.EventLogService; import dev.fitko.fitconnect.api.services.events.SecurityEventService; import dev.fitko.fitconnect.api.services.submission.SubmissionService; import dev.fitko.fitconnect.api.services.validation.ValidationService; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -34,6 +39,7 @@ import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -41,6 +47,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -49,16 +56,15 @@ import static org.mockito.Mockito.when; class SubmissionSubscriberTest { - private SubmissionService submissionServiceMock; - private EventLogService eventLogServiceMock; - private CryptoService cryptoServiceMock; - private ValidationService validationServiceMock; - private SecurityEventService securityEventServiceMock; + private final SubmissionService submissionServiceMock; + private final EventLogService eventLogServiceMock; + private final CryptoService cryptoServiceMock; + private final ValidationService validationServiceMock; + private final SecurityEventService securityEventServiceMock; - private Subscriber underTest; + private final Subscriber underTest; - @BeforeEach - public void setUp() { + public SubmissionSubscriberTest() { submissionServiceMock = mock(SubmissionService.class); eventLogServiceMock = mock(EventLogService.class); @@ -86,7 +92,7 @@ class SubmissionSubscriberTest { final var expectedDecryptedString = "decryptedString"; - when(cryptoServiceMock.decryptBytes(any(), any())).thenReturn(expectedDecryptedString.getBytes()); + when(cryptoServiceMock.decryptToBytes(any(), any())).thenReturn(expectedDecryptedString.getBytes()); // When final byte[] decryptedBytes = underTest.decryptStringContent(privateKey, "encryptedString"); @@ -105,10 +111,10 @@ class SubmissionSubscriberTest { final SubmissionsForPickup availableSubmissions = new SubmissionsForPickup(); availableSubmissions.setSubmissions(Set.of(submission)); - when(submissionServiceMock.pollAvailableSubmissions(any(), anyInt(), anyInt())).thenReturn(availableSubmissions); + when(submissionServiceMock.pollAvailableSubmissionsForDestination(any(), anyInt(), anyInt())).thenReturn(availableSubmissions); // When - final Set<SubmissionForPickup> submissionForPickups = underTest.pollAvailableSubmissions(destinationId, 0, 100); + final Set<SubmissionForPickup> submissionForPickups = underTest.pollAvailableSubmissionsForDestination(destinationId, 0, 100); // Then assertThat(submissionForPickups, equalTo(availableSubmissions.getSubmissions())); @@ -169,10 +175,10 @@ class SubmissionSubscriberTest { void validateMetadata() { // Given - when(validationServiceMock.validateMetadataSchema(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validateMetadata(any(), any(), any(), any())).thenReturn(ValidationResult.ok()); // When - final ValidationResult validationResult = underTest.validateMetadata(new Metadata()); + final ValidationResult validationResult = underTest.validateMetadata(new Metadata(), new Submission(), new AuthenticationTags()); // Then assertTrue(validationResult.isValid()); @@ -182,14 +188,28 @@ class SubmissionSubscriberTest { void invalidMetadata() { // Given - when(validationServiceMock.validateMetadataSchema(any())).thenReturn(ValidationResult.error(new Exception("Failed parsing metadata"))); + when(validationServiceMock.validateMetadata(any(), any(), any(), any())).thenReturn(ValidationResult.problem(new MetadataSchemaViolation())); // When - final ValidationResult validationResult = underTest.validateMetadata(new Metadata()); + final ValidationResult validationResult = underTest.validateMetadata(new Metadata(), new Submission(), new AuthenticationTags()); // Then - assertTrue(validationResult.hasError()); - assertThat(validationResult.getError().getMessage(), containsString("Failed parsing metadata")); + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(MetadataSchemaViolation.class)); + } + + @Test + void invalidData() { + + // Given + when(validationServiceMock.validateData(any(), any(), any(), any())).thenReturn(ValidationResult.problem(new DataHashMismatch())); + + // When + final ValidationResult validationResult = underTest.validateData("foo".getBytes(), new Submission(), new Metadata(), new AuthenticationTags()); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(DataHashMismatch.class)); } @Test @@ -228,7 +248,7 @@ class SubmissionSubscriberTest { submission.setDestinationId(UUID.randomUUID()); submission.setCaseId(UUID.randomUUID()); - final EventPayload eventPayload = new EventPayload(submission); + final EventPayload eventPayload = EventPayload.forAcceptEvent(submission); final var eventJWT = getResourceAsString("/signed_jwt.txt"); @@ -254,7 +274,7 @@ class SubmissionSubscriberTest { final var problem = new AttachmentsMismatch(); - final EventPayload eventPayload = new EventPayload(submission, List.of(problem)); + final EventPayload eventPayload = EventPayload.forRejectEvent(submission, List.of(problem)); final var eventJWT = getResourceAsString("/signed_jwt.txt"); when(securityEventServiceMock.createRejectSubmissionEvent(any())).thenReturn(SignedJWT.parse(eventJWT)); @@ -302,6 +322,29 @@ class SubmissionSubscriberTest { assertThrows(EventLogException.class, () -> underTest.getEventLog(destinationId, caseId)); } + @Test + void testAttachmentValidationFailed() { + + // Given + final Hash hash = new Hash(); + hash.setContent("hashValue"); + + final ApiAttachment attachmentMetadata = new ApiAttachment(); + attachmentMetadata.setAttachmentId(UUID.randomUUID()); + attachmentMetadata.setHash(hash); + + final AttachmentForValidation attachmentForValidation = new AttachmentForValidation(attachmentMetadata, null, null); + + when(validationServiceMock.validateAttachments(anyList(), any())).thenReturn(ValidationResult.problem(new AttachmentsMismatch())); + + // When + final ValidationResult validationResult = underTest.validateAttachments(List.of(attachmentForValidation), new AuthenticationTags()); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(AttachmentsMismatch.class)); + } + private String getResourceAsString(final String filename) throws IOException { return new String(SubmissionSenderTest.class.getResourceAsStream(filename).readAllBytes()); diff --git a/core/src/test/java/dev/fitko/fitconnect/core/auth/DefaultOAuthServiceTest.java b/core/src/test/java/dev/fitko/fitconnect/core/auth/DefaultOAuthServiceTest.java index b57c75fa951806e1a6d661fc16dfb93c564cae3e..85189df78d7104ac77ea5b5961c93ff246b4fe7b 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/auth/DefaultOAuthServiceTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/auth/DefaultOAuthServiceTest.java @@ -10,10 +10,11 @@ import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; import org.springframework.web.client.RestTemplate; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertThrows; class DefaultOAuthServiceTest extends RestEndpointBase { diff --git a/core/src/test/java/dev/fitko/fitconnect/core/crypto/HashServiceTest.java b/core/src/test/java/dev/fitko/fitconnect/core/crypto/HashServiceTest.java index 4bc545c524f000d7e8c9479d5b3b8f331b3e529b..75d95c87bd89d128fbf245774b515d1b2edc7c05 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/crypto/HashServiceTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/crypto/HashServiceTest.java @@ -92,7 +92,7 @@ class HashServiceTest { @Test void calculateHmac() { - String httpBody = "{\"type\":\"https://schema.fitko.de/fit-connect/submission-api/callbacks/new-submissions\",\"submissionIds\":[\"f39ab143-d91a-474a-b69f-b00f1a1873c2\"]}"; + final String httpBody = "{\"type\":\"https://schema.fitko.de/fit-connect/submission-api/callbacks/new-submissions\",\"submissionIds\":[\"f39ab143-d91a-474a-b69f-b00f1a1873c2\"]}"; assertEquals("798cd0edb70c08e5b32aa8a18cbbc8ff6b3078c51af6d011ff4e32e470c746234fc4314821fe5185264b029e962bd37de33f3b9fc5f1a93c40ce6672845e90df", diff --git a/core/src/test/java/dev/fitko/fitconnect/core/crypto/JWECryptoServiceTest.java b/core/src/test/java/dev/fitko/fitconnect/core/crypto/JWECryptoServiceTest.java index 6ed327129dc300562ea4eacf6d2784336f1eedf2..30b474eda10becc6fd8a6fb990f6db5eb20743da 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/crypto/JWECryptoServiceTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/crypto/JWECryptoServiceTest.java @@ -1,6 +1,5 @@ package dev.fitko.fitconnect.core.crypto; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.jwk.KeyUse; @@ -10,52 +9,32 @@ import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; import dev.fitko.fitconnect.api.services.crypto.CryptoService; import org.junit.jupiter.api.Test; +import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; public class JWECryptoServiceTest { - final CryptoService underTest = new JWECryptoService(new HashService()); + final CryptoService underTest; - @Test - void encryptString() throws JOSEException { - // Given - final RSAKey key = generateRsaKey(4096); - final RSAKey publicKey = key.toPublicJWK(); + final RSAKey privateKey; - // When - final String encryptedData = underTest.encryptString(publicKey, "some foo"); - - // Then - assertNotNull(encryptedData); - assertNotEquals("some foo", encryptedData); + public JWECryptoServiceTest() throws JOSEException { + underTest = new JWECryptoService(new HashService()); + privateKey = generateRsaKey(2048); } @Test - void decryptString() throws JOSEException { + void encryptBytes() { // Given - final RSAKey privateKey = generateRsaKey(4096); final RSAKey publicKey = privateKey.toPublicJWK(); - final String dataFromSender = underTest.encryptString(publicKey, "some foo"); - - // When - final String decrypted = underTest.decryptString(privateKey, dataFromSender); - - // Then - assertNotNull(decrypted); - assertEquals("some foo", decrypted); - } - - @Test - void encryptBytes() throws JOSEException { - // Given - final RSAKey key = generateRsaKey(4096); - final RSAKey publicKey = key.toPublicJWK(); // When final String encryptedData = underTest.encryptBytes(publicKey, "some foo".getBytes(StandardCharsets.UTF_8)); @@ -66,30 +45,29 @@ public class JWECryptoServiceTest { } @Test - void encryptObject() throws JOSEException, JsonProcessingException { + void encryptObject() throws IOException { // Given - final RSAKey key = generateRsaKey(4096); - + final RSAKey publicKey = privateKey.toPublicJWK(); final Metadata metadata = new Metadata(); // When - final String encryptedData = underTest.encryptObject(key, metadata); + final String encryptedData = underTest.encryptObject(publicKey, metadata); // Then assertNotNull(encryptedData); - assertThat(new ObjectMapper().readValue(underTest.decryptString(key, encryptedData), Metadata.class), is(metadata)); + assertThat(new ObjectMapper().readValue(underTest.decryptToBytes(privateKey, encryptedData), Metadata.class), is(metadata)); } @Test - void decryptBytes() throws JOSEException { + void decryptBytes() { + // Given - final RSAKey privateKey = generateRsaKey(4096); final RSAKey publicKey = privateKey.toPublicJWK(); - final String dataFromSender = underTest.encryptString(publicKey, "some foo"); + final String dataFromSender = underTest.encryptBytes(publicKey, "some foo".getBytes()); // When - final byte[] decrypted = underTest.decryptBytes(privateKey, dataFromSender); + final byte[] decrypted = underTest.decryptToBytes(privateKey, dataFromSender); // Then assertNotNull(decrypted); diff --git a/core/src/test/java/dev/fitko/fitconnect/core/events/EventLogApiServiceTest.java b/core/src/test/java/dev/fitko/fitconnect/core/events/EventLogApiServiceTest.java index dcbcbf336b1e76eeb3bb4b79d7801be8a1560295..ee5fc7f91197e9f87261e959334b60155831ae4b 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/events/EventLogApiServiceTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/events/EventLogApiServiceTest.java @@ -6,7 +6,7 @@ import com.github.tomakehurst.wiremock.http.ResponseDefinition; import dev.fitko.fitconnect.api.domain.auth.OAuthToken; import dev.fitko.fitconnect.api.domain.model.event.EventLog; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; -import dev.fitko.fitconnect.api.domain.model.event.EventStatus; +import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus; import dev.fitko.fitconnect.api.domain.model.event.SubmissionState; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; @@ -157,46 +157,12 @@ class EventLogApiServiceTest extends RestEndpointBase { .withStatus(200))); // When - final EventStatus lastedEvent = underTest.getLastedEvent(destinationId, caseId, submissionId, new AuthenticationTags()); + final SubmissionStatus lastedEvent = underTest.getLastedEvent(destinationId, caseId, submissionId, new AuthenticationTags()); // Then assertThat(lastedEvent.getStatus(), is(SubmissionState.SUBMITTED)); } - @Test - void testGetLatestEventWithWrongStatusOrder() throws JsonProcessingException { - - // Given - final var caseId = UUID.randomUUID(); - final var destinationId = UUID.randomUUID(); - final var submissionId = UUID.fromString("4f5cecb0-d028-4c68-a7a0-d5a222666e53"); - - final var expectedLogWithIllegalOrderOfEvents = new EventLog(); - expectedLogWithIllegalOrderOfEvents.setEventLogs(List.of(submittedEvent, createdEvent)); - - final var authToken = new OAuthToken(); - authToken.setAccessToken("abc"); - authToken.setExpiresIn(1800); - - when(authServiceMock.getCurrentToken()).thenReturn(authToken); - when(verifierMock.validateEventLogs(ArgumentMatchers.any(), anyList())).thenReturn(Collections.emptyList()); - - final String urlPath = "/v1/cases/" + caseId + "/events"; - - wireMockServer.stubFor( - get(urlEqualTo(urlPath)) - .willReturn(ok() - .withBody(new ObjectMapper().writeValueAsString(expectedLogWithIllegalOrderOfEvents)) - .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE) - .withStatus(200))); - - // When - final EventLogException exception = assertThrows(EventLogException.class, () -> underTest.getLastedEvent(destinationId, caseId, submissionId, new AuthenticationTags())); - - // Then - assertThat(exception.getMessage(), containsString("Allowed transition [INCOMPLETE|SUBMITTED] should have [SUBMITTED] as latest state but was [INCOMPLETE]")); - } - @Test void sendEvent() { // Given diff --git a/core/src/test/java/dev/fitko/fitconnect/core/events/EventLogVerifierTest.java b/core/src/test/java/dev/fitko/fitconnect/core/events/EventLogVerifierTest.java index d4ccb711db8fc3df8e10a5bc368e30e6e0b79ea4..5ffbed66bd10d45e100021a38cca55da4d91ff51 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/events/EventLogVerifierTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/events/EventLogVerifierTest.java @@ -7,14 +7,16 @@ import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; import dev.fitko.fitconnect.api.config.ApplicationConfig; -import dev.fitko.fitconnect.api.domain.model.event.*; +import dev.fitko.fitconnect.api.domain.model.event.Event; +import dev.fitko.fitconnect.api.domain.model.event.EventClaimFields; +import dev.fitko.fitconnect.api.domain.model.event.EventHeaderFields; import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; import dev.fitko.fitconnect.api.domain.validation.ValidationContext; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; import dev.fitko.fitconnect.api.exceptions.SchemaNotFoundException; -import dev.fitko.fitconnect.api.services.keys.KeyService; import dev.fitko.fitconnect.api.services.events.EventLogVerificationService; +import dev.fitko.fitconnect.api.services.keys.KeyService; import dev.fitko.fitconnect.api.services.validation.ValidationService; import dev.fitko.fitconnect.core.util.EventLogUtil; import org.junit.jupiter.api.BeforeEach; @@ -24,11 +26,15 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.text.ParseException; -import java.util.*; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.UUID; import static dev.fitko.fitconnect.api.domain.model.event.Event.ACCEPT; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -69,7 +75,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -107,7 +113,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId, authenticationTags); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -136,7 +142,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId, null); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -172,7 +178,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId, authTags); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -210,7 +216,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -248,7 +254,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.error(new SchemaNotFoundException("Schema not found"))); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -283,7 +289,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -324,7 +330,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -365,7 +371,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -404,7 +410,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -444,7 +450,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getSubmissionServicePublicKey(any())).thenReturn(rsaKey); // When @@ -482,7 +488,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -520,7 +526,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -557,7 +563,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -596,7 +602,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey); // When @@ -633,7 +639,7 @@ class EventLogVerifierTest { final ValidationContext ctx = new ValidationContext(destinationId, caseId); when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok()); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(null); // When diff --git a/core/src/test/java/dev/fitko/fitconnect/core/events/SecurityEventTokenServiceTest.java b/core/src/test/java/dev/fitko/fitconnect/core/events/SecurityEventTokenServiceTest.java index a790df4634bbef654f6e6d7eefda5a7661df9da7..f60b983e01badd884391a7056297fc5a96e51fe0 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/events/SecurityEventTokenServiceTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/events/SecurityEventTokenServiceTest.java @@ -11,8 +11,8 @@ import dev.fitko.fitconnect.api.config.ApplicationConfig; import dev.fitko.fitconnect.api.config.Environment; import dev.fitko.fitconnect.api.config.EnvironmentName; import dev.fitko.fitconnect.api.config.SchemaConfig; -import dev.fitko.fitconnect.api.domain.model.event.EventPayload; import dev.fitko.fitconnect.api.domain.model.event.Event; +import dev.fitko.fitconnect.api.domain.model.event.EventPayload; import dev.fitko.fitconnect.api.domain.model.event.problems.submission.AttachmentsMismatch; import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.schema.SchemaResources; @@ -24,6 +24,7 @@ import dev.fitko.fitconnect.api.services.validation.ValidationService; import dev.fitko.fitconnect.core.crypto.HashService; import dev.fitko.fitconnect.core.crypto.JWECryptoService; import dev.fitko.fitconnect.core.schema.SchemaResourceProvider; +import dev.fitko.fitconnect.core.util.FileUtil; import dev.fitko.fitconnect.core.validation.DefaultValidationService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -40,7 +41,9 @@ import java.util.UUID; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; class SecurityEventTokenServiceTest { @@ -63,21 +66,21 @@ class SecurityEventTokenServiceTest { final List<String> destinationSchemas = SchemaConfig.getDestinationSchemaPaths("/destination-schema"); final SchemaResources schemaResources = new SchemaResources(setSchemas, metadataSchemas, destinationSchemas); final SchemaProvider schemaProvider = new SchemaResourceProvider(schemaResources); - - validationService = new DefaultValidationService(config, new HashService(), schemaProvider); - underTest = new SecurityEventTokenService(config, validationService, signingKey); + this.validationService = new DefaultValidationService(config, new HashService(), schemaProvider, + FileUtil.loadContentOfFilesInDirectory("trusted-test-root-certificates")); + this.underTest = new SecurityEventTokenService(config, this.validationService, this.signingKey); } @Test void testAcceptSubmissionEventWithoutProblems() throws JOSEException { // Given - final var encryptedData = cryptoService.encryptString(encryptionKey, "test data"); - final var encryptedMetadata = cryptoService.encryptString(encryptionKey, "test metadata"); + final var encryptedData = cryptoService.encryptObject(encryptionKey, "test data"); + final var encryptedMetadata = cryptoService.encryptObject(encryptionKey, "test metadata"); final var encryptedAttachments = new HashMap<UUID, String>(); - encryptedAttachments.put(UUID.randomUUID(), cryptoService.encryptString(encryptionKey, "test attachment 1")); - encryptedAttachments.put(UUID.randomUUID(), cryptoService.encryptString(encryptionKey, "test attachment 2")); + encryptedAttachments.put(UUID.randomUUID(), cryptoService.encryptObject(encryptionKey, "test attachment 1")); + encryptedAttachments.put(UUID.randomUUID(), cryptoService.encryptObject(encryptionKey, "test attachment 2")); final var submission = new Submission(); submission.setSubmissionId(UUID.randomUUID()); @@ -86,7 +89,7 @@ class SecurityEventTokenServiceTest { submission.setEncryptedData(encryptedData); submission.setEncryptedMetadata(encryptedMetadata); - final EventPayload eventPayload = new EventPayload(submission, encryptedAttachments); + final EventPayload eventPayload = EventPayload.forAcceptEventWithAttachments(submission, encryptedAttachments); // When final SignedJWT signedJWT = underTest.createAcceptSubmissionEvent(eventPayload); @@ -109,11 +112,11 @@ class SecurityEventTokenServiceTest { void testAcceptSubmissionEventWithProblem() throws JOSEException { // Given - final var encryptedData = cryptoService.encryptString(encryptionKey, "test data"); - final var encryptedMetadata = cryptoService.encryptString(encryptionKey, "test metadata"); + final var encryptedData = cryptoService.encryptObject(encryptionKey, "test data"); + final var encryptedMetadata = cryptoService.encryptObject(encryptionKey, "test metadata"); final var encryptedAttachments = new HashMap<UUID, String>(); - encryptedAttachments.put(UUID.randomUUID(), cryptoService.encryptString(encryptionKey, "test attachment")); + encryptedAttachments.put(UUID.randomUUID(), cryptoService.encryptObject(encryptionKey, "test attachment")); final var submission = new Submission(); submission.setSubmissionId(UUID.randomUUID()); @@ -124,7 +127,7 @@ class SecurityEventTokenServiceTest { final var problem = new AttachmentsMismatch(); - final EventPayload eventPayload = new EventPayload(submission, encryptedAttachments, List.of(problem)); + final EventPayload eventPayload = EventPayload.forAcceptEventWithAttachments(submission, encryptedAttachments, problem); // When final SignedJWT signedJWT = underTest.createAcceptSubmissionEvent(eventPayload); @@ -155,7 +158,7 @@ class SecurityEventTokenServiceTest { submission.setDestinationId(UUID.randomUUID()); submission.setCaseId(UUID.randomUUID()); - final EventPayload eventPayload = new EventPayload(submission, List.of(problem)); + final EventPayload eventPayload = EventPayload.forRejectEvent(submission, List.of(problem)); // When final SignedJWT signedJWT = underTest.createRejectSubmissionEvent(eventPayload); @@ -183,9 +186,9 @@ class SecurityEventTokenServiceTest { submission.setSubmissionId(UUID.randomUUID()); submission.setDestinationId(UUID.randomUUID()); submission.setCaseId(UUID.randomUUID()); - submission.setEncryptedData(cryptoService.encryptString(encryptionKey, "test data")); + submission.setEncryptedData(cryptoService.encryptObject(encryptionKey, "test data")); - final EventPayload eventPayload = new EventPayload(submission, Map.of()); + final EventPayload eventPayload = EventPayload.forAcceptEvent(submission); // When final EventCreationException exception = assertThrows(EventCreationException.class, () -> underTest.createAcceptSubmissionEvent(eventPayload)); @@ -202,9 +205,9 @@ class SecurityEventTokenServiceTest { submission.setSubmissionId(UUID.randomUUID()); submission.setDestinationId(UUID.randomUUID()); submission.setCaseId(UUID.randomUUID()); - submission.setEncryptedMetadata(cryptoService.encryptString(encryptionKey, "test metadata")); + submission.setEncryptedMetadata(cryptoService.encryptObject(encryptionKey, "test metadata")); - final EventPayload eventPayload = new EventPayload(submission, Map.of()); + final EventPayload eventPayload = EventPayload.forAcceptEvent(submission); // When final EventCreationException exception = assertThrows(EventCreationException.class, () -> underTest.createAcceptSubmissionEvent(eventPayload)); @@ -222,7 +225,7 @@ class SecurityEventTokenServiceTest { submission.setDestinationId(UUID.randomUUID()); submission.setCaseId(UUID.randomUUID()); - final EventPayload eventPayload = new EventPayload(submission); + final EventPayload eventPayload = EventPayload.forAcceptEvent(submission); // When final EventCreationException exception = assertThrows(EventCreationException.class, () -> underTest.createAcceptSubmissionEvent(eventPayload)); @@ -241,7 +244,7 @@ class SecurityEventTokenServiceTest { submission.setDestinationId(UUID.randomUUID()); submission.setCaseId(UUID.randomUUID()); - final EventPayload eventPayload = new EventPayload(submission); + final EventPayload eventPayload = EventPayload.forAcceptEvent(submission); // When final EventCreationException exception = assertThrows(EventCreationException.class, () -> underTest.createRejectSubmissionEvent(eventPayload)); diff --git a/core/src/test/java/dev/fitko/fitconnect/core/events/ValidationContextTest.java b/core/src/test/java/dev/fitko/fitconnect/core/events/ValidationContextTest.java index 8d8e02bd07fe80da53d8cb7c9c2976015ec2e292..4ff9591ebf96533d8efba98dd3081ca1f7929a6c 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/events/ValidationContextTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/events/ValidationContextTest.java @@ -23,7 +23,7 @@ class ValidationContextTest { final ValidationContext underTest = new ValidationContext(UUID.randomUUID(), UUID.randomUUID()); // When - underTest.addResult(1+2 == 3, "1+2 does not equal 3"); + underTest.addErrorIfTestFailed(1+2 == 3, "1+2 does not equal 3"); final List<ValidationResult> results = underTest.getValidationResults(); // Then @@ -37,7 +37,7 @@ class ValidationContextTest { final ValidationContext underTest = new ValidationContext(UUID.randomUUID(), UUID.randomUUID()); // When - underTest.addResult(1+2 == 2, "1+2 does not equal 3"); + underTest.addErrorIfTestFailed(1+2 == 2, "1+2 does not equal 3"); final List<ValidationResult> results = underTest.getValidationResults(); // Then @@ -71,7 +71,7 @@ class ValidationContextTest { final List<ValidationResult> results = underTest.getValidationResults(); // Then - assertTrue(results.size() == 1); + assertEquals(1, results.size()); assertTrue(results.get(0).hasError()); assertThat(results.get(0).getError().getMessage(), is("Test failed")); } @@ -87,7 +87,7 @@ class ValidationContextTest { final List<ValidationResult> results = underTest.getValidationResults(); // Then - assertTrue(results.size() == 1); + assertEquals(1, results.size()); assertTrue(results.get(0).hasError()); assertThat(results.get(0).getError().getMessage(), is("Test failed")); } diff --git a/core/src/test/java/dev/fitko/fitconnect/core/http/ProxyRestTemplateTest.java b/core/src/test/java/dev/fitko/fitconnect/core/http/ProxyRestTemplateTest.java index 5dd93e1bdaacedef13ac6076321832eb07fe3240..2a832c8e5d6376fb766c78e1a6d160cadea02ece 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/http/ProxyRestTemplateTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/http/ProxyRestTemplateTest.java @@ -9,7 +9,13 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpMethod; import org.springframework.web.client.RestTemplate; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; public class ProxyRestTemplateTest { diff --git a/core/src/test/java/dev/fitko/fitconnect/core/http/RestServiceTest.java b/core/src/test/java/dev/fitko/fitconnect/core/http/RestServiceTest.java index 1c88e3ffc82bb29104e427f94cf454f74ace4320..3b3830ec579507c051c30bc28f720f51aa7d45b0 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/http/RestServiceTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/http/RestServiceTest.java @@ -7,7 +7,10 @@ import org.springframework.web.client.RestTemplate; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; class RestServiceTest { diff --git a/core/src/test/java/dev/fitko/fitconnect/core/keys/PublicKeyServiceTest.java b/core/src/test/java/dev/fitko/fitconnect/core/keys/PublicKeyServiceTest.java index 54b90334da0c9a89fc1ae3323418afa358daa032..82e637c367d39bdc0adf42987fadb1878dcc3429 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/keys/PublicKeyServiceTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/keys/PublicKeyServiceTest.java @@ -91,7 +91,7 @@ class PublicKeyServiceTest extends RestEndpointBase { when(authServiceMock.getCurrentToken()).thenReturn(authToken); when(submissionServiceMock.getDestination(any())).thenReturn(destination); - when(validationServiceMock.validateEncryptionPublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); // When final RSAKey rsaKey = underTest.getPublicEncryptionKey(destination.getDestinationId()); @@ -134,7 +134,7 @@ class PublicKeyServiceTest extends RestEndpointBase { when(authServiceMock.getCurrentToken()).thenReturn(authToken); when(submissionServiceMock.getDestination(any())).thenReturn(destination); - when(validationServiceMock.validateEncryptionPublicKey(any())).thenReturn(ValidationResult.error(new Exception("Public key is insecure !"))); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.error(new Exception("Public key is insecure !"))); // When final InvalidKeyException exception = assertThrows(InvalidKeyException.class, () -> keyService.getPublicEncryptionKey(destination.getDestinationId())); @@ -165,7 +165,7 @@ class PublicKeyServiceTest extends RestEndpointBase { when(authServiceMock.getCurrentToken()).thenReturn(authToken); when(submissionServiceMock.getDestination(any())).thenReturn(destination); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); // When final RSAKey retrievedSignatureKey = underTest.getPublicSignatureKey(destination.getDestinationId(), "123"); @@ -195,7 +195,7 @@ class PublicKeyServiceTest extends RestEndpointBase { .withStatus(200))); when(authServiceMock.getCurrentToken()).thenReturn(authToken); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); // When final RSAKey retrievedSignatureKey = underTest.getSubmissionServicePublicKey("123"); @@ -225,7 +225,7 @@ class PublicKeyServiceTest extends RestEndpointBase { .withStatus(200))); when(authServiceMock.getCurrentToken()).thenReturn(authToken); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); // When final RSAKey retrievedSignatureKey = underTest.getPortalPublicKey("123"); @@ -255,7 +255,7 @@ class PublicKeyServiceTest extends RestEndpointBase { .withStatus(200))); when(authServiceMock.getCurrentToken()).thenReturn(authToken); - when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok()); + when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok()); // When final RSAKey retrievedSignatureKey = underTest.getWellKnownKeysForSubmissionUrl("http://localhost:" + wireMockServer.port() + "/custom/path", "123"); diff --git a/core/src/test/java/dev/fitko/fitconnect/core/routing/RoutingApiServiceTest.java b/core/src/test/java/dev/fitko/fitconnect/core/routing/RoutingApiServiceTest.java index 486a1150b398200e7b3c97319795b39c29b39630..2c291cc7969c168ec73a2c4e9516341f8e24f467 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/routing/RoutingApiServiceTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/routing/RoutingApiServiceTest.java @@ -21,11 +21,15 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; public class RoutingApiServiceTest extends RestEndpointBase { @@ -120,4 +124,18 @@ public class RoutingApiServiceTest extends RestEndpointBase { assertThat(exception.getMessage(), containsString(" one search criterion out of ags, ars or areaId must be set")); } + + @Test + void testFindRoutesWithMoreThanOneAreaCriterion() { + + // Given + final var leikaKey = "99123456760610"; + + // When + final RestApiException exception = assertThrows(RestApiException.class, () -> underTest.getRoutes(leikaKey, "123", "456", null, 0, 10)); + + // Then + assertThat(exception.getMessage(), containsString("Only one of ars, ags or areaId must be specified.")); + + } } 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 0b1899e945d8a6eda17770d21ff9a9ba381ae8e5..f152ca3021c02ed1ef9759d97e6013bded6e29d8 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 @@ -14,7 +14,9 @@ import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; class SchemaResourceProviderTest { diff --git a/core/src/test/java/dev/fitko/fitconnect/core/submission/SubmissionApiServiceTest.java b/core/src/test/java/dev/fitko/fitconnect/core/submission/SubmissionApiServiceTest.java index ba35c8acd3eb1d12eccb251955f9cf781263488c..e257c31c964f675b715703274c81d590d671c936 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/submission/SubmissionApiServiceTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/submission/SubmissionApiServiceTest.java @@ -28,10 +28,20 @@ import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.put; +import static com.github.tomakehurst.wiremock.client.WireMock.putRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.when; class SubmissionApiServiceTest extends RestEndpointBase { @@ -243,7 +253,7 @@ class SubmissionApiServiceTest extends RestEndpointBase { .withStatus(200))).getResponse(); // When - final SubmissionsForPickup submissions = underTest.pollAvailableSubmissions(destinationId, offset, limit); + final SubmissionsForPickup submissions = underTest.pollAvailableSubmissionsForDestination(destinationId, offset, limit); // Then assertNotNull(submissions); diff --git a/core/src/test/java/dev/fitko/fitconnect/core/util/EventLogUtilTest.java b/core/src/test/java/dev/fitko/fitconnect/core/util/EventLogUtilTest.java index a311988454806c716794e3a89b35593a4b48818d..4ba6454ce2cc4e9b9fa5527b4f2fcb2037743d3a 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/util/EventLogUtilTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/util/EventLogUtilTest.java @@ -5,6 +5,7 @@ import dev.fitko.fitconnect.api.domain.model.event.Event; import dev.fitko.fitconnect.api.domain.model.event.EventIssuer; import dev.fitko.fitconnect.api.domain.model.event.EventLog; import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataEncryptionIssue; import dev.fitko.fitconnect.api.domain.model.event.problems.submission.InvalidEventLog; import dev.fitko.fitconnect.api.exceptions.EventLogException; import org.junit.jupiter.api.Test; @@ -18,7 +19,10 @@ import static dev.fitko.fitconnect.api.domain.model.event.Event.ACCEPT; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; class EventLogUtilTest { diff --git a/core/src/test/java/dev/fitko/fitconnect/core/util/StringsTest.java b/core/src/test/java/dev/fitko/fitconnect/core/util/StringsTest.java index 9d76bbf710208845e3deb75c68a55e61e1b41cff..3a1bee2d1604d57d93d516ef54be68b154440533 100644 --- a/core/src/test/java/dev/fitko/fitconnect/core/util/StringsTest.java +++ b/core/src/test/java/dev/fitko/fitconnect/core/util/StringsTest.java @@ -2,7 +2,7 @@ package dev.fitko.fitconnect.core.util; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; class StringsTest { 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 ba7f6ec3e1f553294408cf421a9218f2ad9f2462..13c1e5df5faabaef03184261ecae403970109f80 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 @@ -12,22 +12,36 @@ import dev.fitko.fitconnect.api.config.ApplicationConfig; import dev.fitko.fitconnect.api.config.Environment; import dev.fitko.fitconnect.api.config.EnvironmentName; import dev.fitko.fitconnect.api.config.SchemaConfig; -import dev.fitko.fitconnect.api.domain.model.metadata.ContentStructure; -import dev.fitko.fitconnect.api.domain.model.metadata.Hash; -import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; -import dev.fitko.fitconnect.api.domain.model.metadata.SignatureType; +import dev.fitko.fitconnect.api.domain.model.destination.Destination; +import dev.fitko.fitconnect.api.domain.model.destination.DestinationService; +import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags; +import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; +import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.AttachmentHashMismatch; +import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.IncorrectAttachmentAuthenticationTag; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataHashMismatch; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataJsonSyntaxViolation; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataXmlSyntaxViolation; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.IncorrectDataAuthenticationTag; +import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.*; +import dev.fitko.fitconnect.api.domain.model.metadata.*; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentForValidation; import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema; +import dev.fitko.fitconnect.api.domain.model.replychannel.Email; +import dev.fitko.fitconnect.api.domain.model.replychannel.Fink; +import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel; +import dev.fitko.fitconnect.api.domain.model.submission.ServiceType; +import dev.fitko.fitconnect.api.domain.model.submission.Submission; import dev.fitko.fitconnect.api.domain.schema.SchemaResources; import dev.fitko.fitconnect.api.domain.validation.ValidationResult; -import dev.fitko.fitconnect.api.exceptions.ValidationException; import dev.fitko.fitconnect.api.services.crypto.MessageDigestService; import dev.fitko.fitconnect.api.services.schema.SchemaProvider; -import dev.fitko.fitconnect.api.services.validation.ValidationService; import dev.fitko.fitconnect.core.crypto.HashService; import dev.fitko.fitconnect.core.schema.SchemaResourceProvider; import dev.fitko.fitconnect.core.testutil.LogCaptor; +import dev.fitko.fitconnect.core.util.FileUtil; import dev.fitko.fitconnect.jwkvalidator.exceptions.JWKValidationException; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.BeforeEach; @@ -35,19 +49,21 @@ import org.junit.jupiter.api.Test; import java.io.IOException; import java.net.URI; +import java.text.ParseException; import java.time.ZonedDateTime; import java.util.*; +import java.util.stream.Collectors; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; class DefaultValidationServiceTest { - private ValidationService underTest; + private DefaultValidationService underTest; private MessageDigestService hashService; private static final LogCaptor logs = new LogCaptor(); private SchemaProvider schemaProvider; @@ -55,13 +71,16 @@ class DefaultValidationServiceTest { @BeforeEach void setUp() { final var config = getApplicationConfig(true); - hashService = new HashService(); + final List<String> setSchemas = SchemaConfig.getSetSchemaFilePaths("/set-schema"); final List<String> metadataSchemas = SchemaConfig.getMetadataSchemaFileNames("/metadata-schema"); final List<String> destinationSchemas = SchemaConfig.getDestinationSchemaPaths("/destination-schema"); final SchemaResources schemaResources = new SchemaResources(setSchemas, metadataSchemas, destinationSchemas); + + hashService = new HashService(); schemaProvider = new SchemaResourceProvider(schemaResources); - underTest = new DefaultValidationService(config, hashService, schemaProvider); + underTest = new DefaultValidationService(config, hashService, schemaProvider, + FileUtil.loadContentOfFilesInDirectory("trusted-test-root-certificates")); } @Test @@ -75,7 +94,7 @@ class DefaultValidationServiceTest { .generate(); // When - final ValidationResult validationResult = underTest.validateEncryptionPublicKey(rsaKey); + final ValidationResult validationResult = underTest.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY); // Then assertTrue(validationResult.isValid()); @@ -92,7 +111,7 @@ class DefaultValidationServiceTest { .generate(); // When - final ValidationResult validationResult = underTest.validateEncryptionPublicKey(rsaKey); + final ValidationResult validationResult = underTest.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY); // Then assertTrue(validationResult.hasError()); @@ -114,15 +133,16 @@ class DefaultValidationServiceTest { config.setEnvironments(Map.of(envName, env)); config.setActiveEnvironment(envName); - final DefaultValidationService underTest = new DefaultValidationService(config, hashService, schemaProvider); + final DefaultValidationService underTest = new DefaultValidationService(config, hashService, schemaProvider, + FileUtil.loadContentOfFilesInDirectory("trusted-test-root-certificates")); final RSAKey rsaKey = getRsaKeyWithCertChain(); // When - underTest.validateEncryptionPublicKey(rsaKey); + underTest.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY); // Then - logs.assertContains("Using proxy HTTP @ https://localhost/<unresolved>:8080 for key validation"); + logs.assertContains("Using proxy HTTP @ https://localhost:8080 for key validation"); } @Test @@ -130,7 +150,8 @@ class DefaultValidationServiceTest { // Given final ApplicationConfig config = getApplicationConfig(true); - final var underTest = new DefaultValidationService(config, hashService, schemaProvider); + final var underTest = new DefaultValidationService(config, hashService, schemaProvider, + FileUtil.loadContentOfFilesInDirectory("trusted-test-root-certificates")); final RSAKey rsaKey = new RSAKeyGenerator(4096) .keyOperations(Set.of(KeyOperation.WRAP_KEY)) @@ -139,7 +160,7 @@ class DefaultValidationServiceTest { .generate(); // When - final ValidationResult validationResult = underTest.validateEncryptionPublicKey(rsaKey); + final ValidationResult validationResult = underTest.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY); // Then assertTrue(validationResult.isValid()); @@ -150,7 +171,8 @@ class DefaultValidationServiceTest { // Given final ApplicationConfig config = getApplicationConfig(false); - final var underTest = new DefaultValidationService(config, hashService, schemaProvider); + final var underTest = new DefaultValidationService(config, hashService, schemaProvider, + FileUtil.loadContentOfFilesInDirectory("trusted-test-root-certificates")); final RSAKey rsaKey = new RSAKeyGenerator(4096) .keyOperations(Set.of(KeyOperation.ENCRYPT)) @@ -159,16 +181,16 @@ class DefaultValidationServiceTest { .generate(); // When - final ValidationResult validationResult = underTest.validateEncryptionPublicKey(rsaKey); + final ValidationResult validationResult = underTest.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY); // Then assertTrue(validationResult.hasError()); - assertThat(validationResult.getError().getMessage(), containsString("\"certChain\" is null")); + assertThat(validationResult.getError().getMessage(), containsString("\"x5c\" is null")); } @Test - void validateMetadata() { + void testValidMetadataSchema() { // Given final var submissionSchema = new SubmissionSchema(); @@ -198,6 +220,474 @@ class DefaultValidationServiceTest { assertTrue(validationResult.isValid()); } + @Test + void testValidMetadata() { + + // Given + final var submissionSchema = new SubmissionSchema(); + submissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")); + submissionSchema.setMimeType(MimeType.APPLICATION_JSON); + + final var hash = new Hash(); + hash.setContent(hashService.toHexString(hashService.createHash("someBogusContent".getBytes()))); + hash.setSignatureType(SignatureType.SHA_512); + + final var data = new Data(); + data.setSubmissionSchema(submissionSchema); + data.setHash(hash); + + final var attachmentOne = new ApiAttachment(); + attachmentOne.setAttachmentId(UUID.randomUUID()); + + final var attachmentTwo = new ApiAttachment(); + attachmentTwo.setAttachmentId(UUID.randomUUID()); + + final var contentStructure = new ContentStructure(); + contentStructure.setData(data); + contentStructure.setAttachments(List.of(attachmentOne, attachmentTwo)); + + final PublicServiceType publicServiceType = new PublicServiceType(); + publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + + final ReplyChannel replyChannel = new ReplyChannel(); + replyChannel.setEMail(new Email("test@mail.net", false, null)); + + final var metadata = new Metadata(); + metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString()); + metadata.setContentStructure(contentStructure); + metadata.setPublicServiceType(publicServiceType); + metadata.setReplyChannel(replyChannel); + + final ServiceType serviceType = new ServiceType(); + serviceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + + final var submission = new Submission(); + submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag"); + submission.setServiceType(serviceType); + submission.setAttachments(contentStructure.getAttachments().stream().map(ApiAttachment::getAttachmentId).collect(Collectors.toList())); + + final DestinationService destinationService = new DestinationService(); + destinationService.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + destinationService.setSubmissionSchemas(Set.of(submissionSchema)); + + final var destination = new Destination(); + destination.setServices(Set.of(destinationService)); + destination.setReplyChannels(replyChannel); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("metadataAuthTag"); + authenticationTags.setAttachments(Map.of(attachmentOne.getAttachmentId(), "authTag", attachmentTwo.getAttachmentId(), "authTag")); + + // When + final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags); + + // Then + assertTrue(validationResult.isValid()); + } + + @Test + void testIncorrectMetadataAuthenticationTag() { + + // Given + final var metadata = new Metadata(); + final var destination = new Destination(); + + final var submission = new Submission(); + submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag"); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("wrongMetadataAuthTag"); + + // When + final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), is(new IncorrectMetadataAuthenticationTag())); + } + + + @Test + void testUnsupportedMetadataSchema() { + + // Given + final String unsupportedSchema = "https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json/metadata_schema_3.2.1.json"; + + final Data data = new Data(); + + final ContentStructure contentStructure = new ContentStructure(); + contentStructure.setData(data); + + final var metadata = new Metadata(); + metadata.setContentStructure(contentStructure); + metadata.setSchema(unsupportedSchema); + final var destination = new Destination(); + + final var submission = new Submission(); + submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag"); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("metadataAuthTag"); + + // When + final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), is(new UnsupportedMetadataSchema(unsupportedSchema))); + } + + @Test + void testMetadataSyntaxViolation() { + + // Given + final var destination = new Destination(); + + final var submission = new Submission(); + submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag"); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("metadataAuthTag"); + + // When + final ValidationResult validationResult = underTest.validateMetadata(null, submission, destination, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(MetadataJsonSyntaxViolation.class)); + } + + @Test + void testMissingData() { + + // Given + final var destination = new Destination(); + + final var submission = new Submission(); + submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag"); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("metadataAuthTag"); + + final var metadata = new Metadata(); + metadata.setContentStructure(new ContentStructure()); + + // When + final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(MissingData.class)); + } + + @Test + void testMetadataSchemaViolation() { + + // Given + final ContentStructure contentStructure = new ContentStructure(); + contentStructure.setData(new Data()); + + final var metadata = new Metadata(); + metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString()); + metadata.setContentStructure(contentStructure); + + final var destination = new Destination(); + + final var submission = new Submission(); + submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag"); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("metadataAuthTag"); + + // When + final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), is(new MetadataSchemaViolation())); + } + + @Test + void testServiceMismatch() { + + // Given + final var hash = new Hash(); + hash.setContent(hashService.toHexString(hashService.createHash("someBogusContent".getBytes()))); + hash.setSignatureType(SignatureType.SHA_512); + + final SubmissionSchema submissionSchema = new SubmissionSchema(); + submissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json")); + submissionSchema.setMimeType(MimeType.APPLICATION_JSON); + + final var data = new Data(); + data.setSubmissionSchema(submissionSchema); + data.setHash(hash); + + final ContentStructure contentStructure = new ContentStructure(); + contentStructure.setData(data); + contentStructure.setAttachments(Collections.emptyList()); + + final PublicServiceType publicServiceType = new PublicServiceType(); + publicServiceType.setIdentifier("urn:de:fim:leika:leistung:123456789"); + + final var metadata = new Metadata(); + metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString()); + metadata.setContentStructure(contentStructure); + metadata.setPublicServiceType(publicServiceType); + + final var destination = new Destination(); + + final ServiceType submissionServiceType = new ServiceType(); + submissionServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + + final var submission = new Submission(); + submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag"); + submission.setServiceType(submissionServiceType); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("metadataAuthTag"); + + // When + final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(ServiceMismatch.class)); + } + + @Test + void testUnsupportedService() { + + // Given + final SubmissionSchema submissionSchema = new SubmissionSchema(); + submissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json")); + submissionSchema.setMimeType(MimeType.APPLICATION_JSON); + + final Hash hash = new Hash(); + hash.setContent(hashService.toHexString(hashService.createHash("someBogusContent".getBytes()))); + hash.setSignatureType(SignatureType.SHA_512); + + final Data data = new Data(); + data.setSubmissionSchema(submissionSchema); + data.setHash(hash); + + final ContentStructure contentStructure = new ContentStructure(); + contentStructure.setAttachments(Collections.emptyList()); + contentStructure.setData(data); + + final PublicServiceType publicServiceType = new PublicServiceType(); + publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + + final var metadata = new Metadata(); + metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString()); + metadata.setContentStructure(contentStructure); + metadata.setPublicServiceType(publicServiceType); + + final DestinationService destinationService = new DestinationService(); + destinationService.setIdentifier("urn:de:fim:leika:leistung:9999999999999"); + + final var destination = new Destination(); + destination.setServices(Set.of(destinationService)); + + final ServiceType submissionServiceType = new ServiceType(); + submissionServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + + final var submission = new Submission(); + submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag"); + submission.setServiceType(submissionServiceType); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("metadataAuthTag"); + + // When + final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(UnsupportedService.class)); + } + + @Test + void testUnsupportedDataSchema() { + + // Given + final SubmissionSchema submissionSchema = new SubmissionSchema(); + submissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json")); + submissionSchema.setMimeType(MimeType.APPLICATION_JSON); + + final Hash hash = new Hash(); + hash.setContent(hashService.toHexString(hashService.createHash("someBogusContent".getBytes()))); + hash.setSignatureType(SignatureType.SHA_512); + + final Data data = new Data(); + data.setSubmissionSchema(submissionSchema); + data.setHash(hash); + + final ContentStructure contentStructure = new ContentStructure(); + contentStructure.setAttachments(Collections.emptyList()); + contentStructure.setData(data); + + final PublicServiceType publicServiceType = new PublicServiceType(); + publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + + final var metadata = new Metadata(); + metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString()); + metadata.setContentStructure(contentStructure); + metadata.setPublicServiceType(publicServiceType); + + final SubmissionSchema destinationSubmissionSchema = new SubmissionSchema(); + destinationSubmissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fit-connect/metadata/3.0.0/metadata.schema.json")); + destinationSubmissionSchema.setMimeType(MimeType.APPLICATION_JSON); + + final DestinationService destinationService = new DestinationService(); + destinationService.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + destinationService.setSubmissionSchemas(Set.of(destinationSubmissionSchema)); + + final var destination = new Destination(); + destination.setServices(Set.of(destinationService)); + + final ServiceType submissionServiceType = new ServiceType(); + submissionServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + + final var submission = new Submission(); + submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag"); + submission.setServiceType(submissionServiceType); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("metadataAuthTag"); + + // When + final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(UnsupportedDataSchema.class)); + } + + @Test + void testAttachmentsMismatch() { + + // Given + final SubmissionSchema schema = new SubmissionSchema(); + schema.setSchemaUri(URI.create("https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json")); + schema.setMimeType(MimeType.APPLICATION_JSON); + + final Hash hash = new Hash(); + hash.setContent(hashService.toHexString(hashService.createHash("someBogusContent".getBytes()))); + hash.setSignatureType(SignatureType.SHA_512); + + final Data data = new Data(); + data.setSubmissionSchema(schema); + data.setHash(hash); + + final ContentStructure contentStructure = new ContentStructure(); + contentStructure.setData(data); + + final PublicServiceType publicServiceType = new PublicServiceType(); + publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + + final var metadata = new Metadata(); + metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString()); + metadata.setContentStructure(contentStructure); + metadata.setPublicServiceType(publicServiceType); + + final DestinationService destinationService = new DestinationService(); + destinationService.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + destinationService.setSubmissionSchemas(Set.of(schema)); + + final var destination = new Destination(); + destination.setServices(Set.of(destinationService)); + + final ServiceType submissionServiceType = new ServiceType(); + submissionServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + + final var submission = new Submission(); + submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag"); + submission.setServiceType(submissionServiceType); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("metadataAuthTag"); + + // attachmentId that were announced + submission.setAttachments(List.of(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID())); + + // attachment in metadata that doesn't match + final ApiAttachment attachment = new ApiAttachment(); + attachment.setAttachmentId(UUID.randomUUID()); + contentStructure.setAttachments(List.of(attachment)); + + // authTag from submit event + authenticationTags.setAttachments(Map.of(attachment.getAttachmentId(), "attachmentAuthTag")); + + // When + final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(AttachmentsMismatch.class)); + } + + @Test + void testUnsupportedReplyChannel() { + + // Given + final SubmissionSchema schema = new SubmissionSchema(); + schema.setSchemaUri(URI.create("https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json")); + schema.setMimeType(MimeType.APPLICATION_JSON); + + final Hash hash = new Hash(); + hash.setContent(hashService.toHexString(hashService.createHash("someBogusContent".getBytes()))); + hash.setSignatureType(SignatureType.SHA_512); + + final Data data = new Data(); + data.setSubmissionSchema(schema); + data.setHash(hash); + + final ContentStructure contentStructure = new ContentStructure(); + contentStructure.setAttachments(Collections.emptyList()); + contentStructure.setData(data); + + final PublicServiceType publicServiceType = new PublicServiceType(); + publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + + final var metadata = new Metadata(); + metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString()); + metadata.setContentStructure(contentStructure); + metadata.setPublicServiceType(publicServiceType); + + final DestinationService destinationService = new DestinationService(); + destinationService.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + destinationService.setSubmissionSchemas(Set.of(schema)); + + final var destination = new Destination(); + destination.setServices(Set.of(destinationService)); + + final ServiceType submissionServiceType = new ServiceType(); + submissionServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + + final var submission = new Submission(); + submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag"); + submission.setServiceType(submissionServiceType); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setMetadata("metadataAuthTag"); + + final ReplyChannel submissionReplyChannel = new ReplyChannel(); + submissionReplyChannel.setEMail(new Email("test@mail.org", false, null)); + metadata.setReplyChannel(submissionReplyChannel); + + final ReplyChannel destinationReplyChannel = new ReplyChannel(); + destinationReplyChannel.setFink(new Fink("finkPostBoxRef", "finkHost")); + destination.setReplyChannels(destinationReplyChannel); + + // When + final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(UnsupportedReplyChannel.class)); + } + @Test void validateMetadataWithoutSchemaAttributeButValidSchema() { @@ -207,7 +697,7 @@ class DefaultValidationServiceTest { final var metadata = new Metadata(); metadata.setContentStructure(contentStructure); - assertTrue(this.underTest.validateMetadataSchema(metadata).isValid()); + assertTrue(underTest.validateMetadataSchema(metadata).isValid()); } @Test @@ -218,9 +708,8 @@ class DefaultValidationServiceTest { final ValidationResult validationResult = underTest.validateMetadataSchema(metadata); - assertTrue(validationResult.hasError()); - assertThat(validationResult.getError().getClass(), equalTo(ValidationException.class)); - assertThat(validationResult.getError().getMessage(), containsString("The provided metadata schema is not supported.")); + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems(), contains(new UnsupportedMetadataSchema(metadata.getSchema()))); } @Test @@ -282,6 +771,276 @@ class DefaultValidationServiceTest { assertThat(validationResult.getError().getMessage(), containsString("$.contentStructure.data.hash.content: does not match the regex pattern ^[a-f0-9]{128}$")); } + @Test + void testIncorrectAttachmentAuthenticationTag() { + + // Given + final var firstAttachmentId = UUID.randomUUID(); + final var secondAttachmentId = UUID.randomUUID(); + + final var firstDecryptedData = "first test attachment".getBytes(); + final var firstEncryptedData = "part1.part2.part3.part4.authTag1"; + final var firstOriginalHash = hashService.toHexString(hashService.createHash(firstDecryptedData)); + + final byte[] secondDecryptedData = "second test attachment".getBytes(); + final var secondEncryptedData = "part1.part2.part3.part4.authTag2"; + final var secondOriginalHash = hashService.toHexString(hashService.createHash(secondDecryptedData)); + + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setAttachments(Map.of(firstAttachmentId, "notMatchingAuthTag1", secondAttachmentId, "notMatchingAuthTag2")); + + final Hash firstHash = new Hash(); + firstHash.setContent(firstOriginalHash); + + final ApiAttachment firstAttachmentMetadata = new ApiAttachment(); + firstAttachmentMetadata.setAttachmentId(firstAttachmentId); + firstAttachmentMetadata.setHash(firstHash); + + final Hash secondHash = new Hash(); + secondHash.setContent(secondOriginalHash); + + final ApiAttachment secondAttachmentMetadata = new ApiAttachment(); + secondAttachmentMetadata.setAttachmentId(secondAttachmentId); + secondAttachmentMetadata.setHash(secondHash); + + final AttachmentForValidation firstAttachment = new AttachmentForValidation(firstAttachmentMetadata, firstEncryptedData, firstDecryptedData); + final AttachmentForValidation secondAttachment = new AttachmentForValidation(secondAttachmentMetadata, secondEncryptedData, secondDecryptedData); + + // When + final ValidationResult validationResult = underTest.validateAttachments(List.of(firstAttachment, secondAttachment), authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems(), hasSize(2)); + assertThat(validationResult.getProblems().stream().map(Problem::getClass).collect(Collectors.toList()), hasItems(IncorrectAttachmentAuthenticationTag.class)); + assertThat(validationResult.getProblems().get(0).getInstance(), containsString("attachment:" + firstAttachmentId)); + assertThat(validationResult.getProblems().get(1).getInstance(), containsString("attachment:" + secondAttachmentId)); + } + + @Test + void testAttachmentHashMismatch() { + + // Given + final var firstAttachmentId = UUID.randomUUID(); + final var secondAttachmentId = UUID.randomUUID(); + + final var firstDecryptedData = "first test attachment".getBytes(); + final var firstEncryptedData = "part1.part2.part3.part4.authTag1"; + final var firstOriginalHash = hashService.toHexString(hashService.createHash(firstDecryptedData)); + + final byte[] secondDecryptedData = "second test attachment".getBytes(); + final var secondEncryptedData = "part1.part2.part3.part4.authTag2"; + final var secondOriginalHash = hashService.toHexString(hashService.createHash(secondDecryptedData)); + + final AuthenticationTags authenticationTags = new AuthenticationTags(); + authenticationTags.setAttachments(Map.of(firstAttachmentId, "authTag1", secondAttachmentId, "authTag2")); + + final Hash firstHash = new Hash(); + firstHash.setContent(firstOriginalHash); + + final ApiAttachment firstAttachmentMetadata = new ApiAttachment(); + firstAttachmentMetadata.setAttachmentId(firstAttachmentId); + firstAttachmentMetadata.setHash(firstHash); + + final Hash secondHash = new Hash(); + secondHash.setContent(secondOriginalHash); + + final ApiAttachment secondAttachmentMetadata = new ApiAttachment(); + secondAttachmentMetadata.setAttachmentId(secondAttachmentId); + secondAttachmentMetadata.setHash(secondHash); + + final AttachmentForValidation firstAttachment = new AttachmentForValidation(firstAttachmentMetadata, firstEncryptedData, "modified attachment 1 content".getBytes()); + final AttachmentForValidation secondAttachment = new AttachmentForValidation(secondAttachmentMetadata, secondEncryptedData, "modified attachment 2 content".getBytes()); + + // When + final ValidationResult validationResult = underTest.validateAttachments(List.of(firstAttachment, secondAttachment), authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems(), hasSize(2)); + assertThat(validationResult.getProblems().stream().map(Problem::getClass).collect(Collectors.toList()), hasItems(AttachmentHashMismatch.class)); + assertThat(validationResult.getProblems().get(0).getInstance(), containsString("attachment:" + firstAttachmentId)); + assertThat(validationResult.getProblems().get(1).getInstance(), containsString("attachment:" + secondAttachmentId)); + } + + @Test + void testValidData() { + + // Given + final var decryptedData = "{ \"fachdaten\" : \"test\"}".getBytes(); + + final var submission = new Submission(); + submission.setEncryptedData("part1.part2.part3.part4.dataAuthTag"); + + final var submissionSchema = new SubmissionSchema(); + submissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")); + submissionSchema.setMimeType(MimeType.APPLICATION_JSON); + + final var hash = new Hash(); + hash.setContent(hashService.toHexString(hashService.createHash(decryptedData))); + hash.setSignatureType(SignatureType.SHA_512); + + final var data = new Data(); + data.setHash(hash); + data.setSubmissionSchema(submissionSchema); + + final var contentStructure = new ContentStructure(); + contentStructure.setData(data); + contentStructure.setAttachments(Collections.emptyList()); + + final var metadata = new Metadata(); + metadata.setContentStructure(contentStructure); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setData("dataAuthTag"); + + // When + final ValidationResult validationResult = underTest.validateData(decryptedData, submission, metadata, authenticationTags); + + // Then + assertTrue(validationResult.isValid()); + } + + @Test + void testIncorrectDataAuthenticationTag() { + + // Given + final var decryptedData = "{ \"fachdaten\" : \"test\"}".getBytes(); + + final var submission = new Submission(); + submission.setEncryptedData("part1.part2.part3.part4.wrongDataAuthTag"); + + final var hash = new Hash(); + hash.setContent(hashService.toHexString(hashService.createHash(decryptedData))); + hash.setSignatureType(SignatureType.SHA_512); + + final var data = new Data(); + data.setHash(hash); + + final var contentStructure = new ContentStructure(); + contentStructure.setData(data); + + final var metadata = new Metadata(); + metadata.setContentStructure(contentStructure); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setData("dataAuthTag"); + + // When + final ValidationResult validationResult = underTest.validateData(decryptedData, submission, metadata, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(IncorrectDataAuthenticationTag.class)); + } + + @Test + void testDataHashMismatch() { + + // Given + final var decryptedData = "{ \"fachdaten\" : \"test\"}".getBytes(); + + final var submission = new Submission(); + submission.setEncryptedData("part1.part2.part3.part4.dataAuthTag"); + + final var hash = new Hash(); + hash.setContent(hashService.toHexString(hashService.createHash("manipulated data".getBytes()))); + hash.setSignatureType(SignatureType.SHA_512); + + final var data = new Data(); + data.setHash(hash); + + final var contentStructure = new ContentStructure(); + contentStructure.setData(data); + + final var metadata = new Metadata(); + metadata.setContentStructure(contentStructure); + + final var authenticationTags = new AuthenticationTags(); + authenticationTags.setData("dataAuthTag"); + + // When + final ValidationResult validationResult = underTest.validateData(decryptedData, submission, metadata, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(DataHashMismatch.class)); + } + + @Test + void testDataJsonSyntaxViolation() { + + // Given + final var decryptedData = "{ invalid_json }".getBytes(); + + final var submission = new Submission(); + submission.setEncryptedData("part1.part2.part3.part4.dataAuthTag"); + + final var hash = new Hash(); + hash.setContent(hashService.toHexString(hashService.createHash(decryptedData))); + hash.setSignatureType(SignatureType.SHA_512); + + final var submissionSchema = new SubmissionSchema(); + submissionSchema.setMimeType(MimeType.APPLICATION_JSON); + + 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 var authenticationTags = new AuthenticationTags(); + authenticationTags.setData("dataAuthTag"); + + // When + final ValidationResult validationResult = underTest.validateData(decryptedData, submission, metadata, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(DataJsonSyntaxViolation.class)); + } + + @Test + void testDataXmlSyntaxViolation() { + + // Given + final var decryptedData = "<xmehl>invalid xml</xml> }".getBytes(); + + final var submission = new Submission(); + submission.setEncryptedData("part1.part2.part3.part4.dataAuthTag"); + + final var hash = new Hash(); + hash.setContent(hashService.toHexString(hashService.createHash(decryptedData))); + hash.setSignatureType(SignatureType.SHA_512); + + final var submissionSchema = new SubmissionSchema(); + submissionSchema.setMimeType(MimeType.APPLICATION_XML); + + 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 var authenticationTags = new AuthenticationTags(); + authenticationTags.setData("dataAuthTag"); + + // When + final ValidationResult validationResult = underTest.validateData(decryptedData, submission, metadata, authenticationTags); + + // Then + assertTrue(validationResult.hasProblems()); + assertThat(validationResult.getProblems().get(0), instanceOf(DataXmlSyntaxViolation.class)); + } + @Test void validateMatchingHash() { @@ -306,7 +1065,7 @@ class DefaultValidationServiceTest { // Then assertTrue(validationResult.hasError()); - assertThat(validationResult.getError().getMessage(), containsString("transmitted data does not equal the hash of the sender")); + assertThat(validationResult.getError().getMessage(), containsString("Metadata contains invalid hash value")); } @Test @@ -370,7 +1129,7 @@ class DefaultValidationServiceTest { void testValidDestinationPayload() { // Given - final Map<String,Object> claims = Map.of( + final Map<String, Object> claims = Map.of( "iss", "submission-service", "jti", UUID.randomUUID().toString(), "iat", new Date().getTime(), @@ -390,7 +1149,7 @@ class DefaultValidationServiceTest { void testDestinationPayloadIsMissingMandatoryClaims() { // Given - final Map<String,Object> claims = Map.of("test", "claim"); + final Map<String, Object> claims = Map.of("test", "claim"); // When final ValidationResult validationResult = underTest.validateDestinationSchema(claims); @@ -441,7 +1200,8 @@ class DefaultValidationServiceTest { when(mockedMessageDigestService.calculateHMAC(anyString(), anyString())).thenReturn("valid"); final DefaultValidationService defaultValidationService = new DefaultValidationService( - new ApplicationConfig(), mockedMessageDigestService, mock(SchemaProvider.class)); + new ApplicationConfig(), mockedMessageDigestService, mock(SchemaProvider.class), + FileUtil.loadContentOfFilesInDirectory("trusted-test-root-certificates")); final ValidationResult validationResult = defaultValidationService.validateCallback( "valid", ZonedDateTime.now().toInstant().toEpochMilli(), "body", "secret"); @@ -472,6 +1232,69 @@ class DefaultValidationServiceTest { assertThat(validationResult.getError().getMessage(), CoreMatchers.is("HMAC provided by callback does not match the expected result.")); } + @Test + public void productiveTransmissionServiceCertificateIsValidAccordingToRootCertificates() throws JWKValidationException, ParseException { + + DefaultValidationService defaultValidationService = new DefaultValidationService( + getApplicationConfig(false), hashService, schemaProvider, + FileUtil.loadContentOfFilesInDirectory("trusted-root-certificates")); + + RSAKey rsaKey = RSAKey.parse(FileUtil.loadContentOfFile("certificates/grp-fit-connect-zustelldienst-produktivumgebung.json")); + + defaultValidationService.validateCertChain(rsaKey, KeyOperation.VERIFY); + } + + @Test + public void fitConnectTestCertificateIsValidAccordingToTestRootCertificates() throws JWKValidationException, ParseException { + + DefaultValidationService defaultValidationService = new DefaultValidationService( + getApplicationConfig(false), hashService, schemaProvider, + FileUtil.loadContentOfFilesInDirectory("trusted-test-root-certificates")); + + RSAKey rsaKey = RSAKey.parse(FileUtil.loadContentOfFile("certificates/grp-fitko-testzertifikat-fit-connect-1.json")); + + defaultValidationService.validateCertChain(rsaKey, KeyOperation.VERIFY); + } + + @Test + public void revokedFitConnectTestCertificateIsInvalidAccordingToTestRootCertificates() throws ParseException, JWKValidationException { + + DefaultValidationService defaultValidationService = new DefaultValidationService( + getApplicationConfig(false), hashService, schemaProvider, + FileUtil.loadContentOfFilesInDirectory("trusted-test-root-certificates")); + + RSAKey rsaKey = RSAKey.parse(FileUtil.loadContentOfFile("certificates/grp-fitko-testzertifikat-fit-connect-2.json")); + + Exception exception = assertThrows(JWKValidationException.class, + () -> defaultValidationService.validateCertChain(rsaKey, KeyOperation.VERIFY)); + + /** + * ATTENTION! + * + * The term "UNKNOWN" in the expected error message is derived from the response, that our certificate provider + * is retrieving when checking the list of revoked certificates. Currently, the text is misleading and the + * provider may fix this in the future. Then the expected error text in this test has to be adjusted. + * + * @see https://git.fitko.de/fit-connect/planning/-/issues/964 + */ + assertThat(exception.getMessage(), is("JWK with id rRaGt35McGycD4MeRyHZSLFo7dda2PEJscBuPT1KJdE-wrapKey returned certificate status UNKNOWN for OCSP check")); + } + + @Test + public void fitConnectTestCertificateIsInvalidAccordingToRootCertificates() throws ParseException, JWKValidationException { + + DefaultValidationService defaultValidationService = new DefaultValidationService( + getApplicationConfig(false), hashService, schemaProvider, + FileUtil.loadContentOfFilesInDirectory("trusted-root-certificates")); + + RSAKey rsaKey = RSAKey.parse(FileUtil.loadContentOfFile("certificates/grp-fitko-testzertifikat-fit-connect-1.json")); + + Exception exception = assertThrows(JWKValidationException.class, + () -> defaultValidationService.validateCertChain(rsaKey, KeyOperation.VERIFY)); + + assertThat(exception.getMessage(), is("JWK with id LM0FPR9i-Yg1Cks-f_HG4dHocwLc2MU3rigME-Uc1Lc-wrapKey has invalid certificate chain")); + } + private String getResource(final String filename) throws IOException { return new String(DefaultValidationServiceTest.class.getResourceAsStream(filename).readAllBytes()); } diff --git a/core/src/test/resources/certificates/grp-fit-connect-zustelldienst-produktivumgebung.json b/core/src/test/resources/certificates/grp-fit-connect-zustelldienst-produktivumgebung.json new file mode 100644 index 0000000000000000000000000000000000000000..7fc4f9f25401367e5d5a15210d90518b63e96782 --- /dev/null +++ b/core/src/test/resources/certificates/grp-fit-connect-zustelldienst-produktivumgebung.json @@ -0,0 +1,15 @@ +{ + "alg": "PS512", + "e": "AQAB", + "key_ops": [ + "verify" + ], + "kid": "u1ZTGUSSqcNoqGfAhN2lV1viizqNs3pbKloXzbrXlHg-verify", + "kty": "RSA", + "n": "stC7Pvf_zSLUaC134eYuNVylUf7DHwDCaB4Av4j1If5phSRu0uaIZdLIxF6yvfaPQybza4U3Eh1NSl9Ur5ymF0oL7rzxzDIByCneo3yEz4Z0r5hBVYkzz3YSqqT8fXeWJ7E9WbPVqm0QBXzR4coAncGTfDJb17ALYMNhUKGPYKdfFqY7KDYxvJkXrH2CxW_PQGIbonic3SB22JRgqfGzyWkVWaQHyOac9mewU1JPnExwtkZEZ6Z2g9LnBCooV9jprD3AdQ9ZyfsFannYSM4Ai6uW6qMdru5fQx5QebTy2uDkdb7M77rxgl80nvYLgEuL0TckMlKceSb3bja3gWkQlWSWfjBXHhbSUQ2VvxugyLLB7JLVmLiLvKTc3jStvzWHQEjjkg8iGw1uyJYblYXdZkO55iQE7c4sBoGBqI3bGNm0qe1RTzXaNH9y0hzr5IGjAWSksqQ7GwC6SiK3h4HbiIWkXKwST6Jl9R4px8QVF_POzxlLGCLLKVyM02imuzVpDRHpxWYTRSH7Xw72NlsNqOHJKjJnckJIwEI2vHHv7vZaqioU7EHdIpj9x00wPHqJ-x3yGI_1uFzXQb-B9Y1rgrX8xSkYunU5AN2EfREH5WE9bk80sCjGCMs7vefyfZjQymJw7yG9QLAt_o7Fuv8x0pxprNMT5RqDXmwPQQpQJ-8", + "x5c": [ + "MIIH6jCCBdKgAwIBAgIHAtxuen8uEzANBgkqhkiG9w0BAQsFADBLMQswCQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEMMAoGA1UECxMDRE9JMRMwEQYDVQQDEwpET0kgQ0EgMTBhMB4XDTIyMDMxMDEyNTEwNloXDTI1MDMxMDIzNTk1OVowgecxCzAJBgNVBAYTAkRFMRYwFAYDVQQKEw1OaWVkZXJzYWNoc2VuMRgwFgYDVQQLEw9ORFMtZUdvdmVybm1lbnQxGTAXBgNVBAsTEElULk5pZWRlcnNhY2hzZW4xETAPBgNVBAcTCEhhbm5vdmVyMS0wKwYJKoZIhvcNAQkBFh5maXRjb25uZWN0QGl0Lm5pZWRlcnNhY2hzZW4uZGUxPTA7BgNVBAMMNEdSUDogRklULUNvbm5lY3QgWnVzdGVsbGRpZW5zdCDigJMgUHJvZHVrdGl2dW1nZWJ1bmcxCjAIBgNVBAUTATEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCy0Ls+9//NItRoLXfh5i41XKVR/sMfAMJoHgC/iPUh/mmFJG7S5ohl0sjEXrK99o9DJvNrhTcSHU1KX1SvnKYXSgvuvPHMMgHIKd6jfITPhnSvmEFViTPPdhKqpPx9d5YnsT1Zs9WqbRAFfNHhygCdwZN8MlvXsAtgw2FQoY9gp18WpjsoNjG8mResfYLFb89AYhuieJzdIHbYlGCp8bPJaRVZpAfI5pz2Z7BTUk+cTHC2RkRnpnaD0ucEKihX2OmsPcB1D1nJ+wVqedhIzgCLq5bqox2u7l9DHlB5tPLa4OR1vszvuvGCXzSe9guAS4vRNyQyUpx5JvduNreBaRCVZJZ+MFceFtJRDZW/G6DIssHsktWYuIu8pNzeNK2/NYdASOOSDyIbDW7IlhuVhd1mQ7nmJATtziwGgYGojdsY2bSp7VFPNdo0f3LSHOvkgaMBZKSypDsbALpKIreHgduIhaRcrBJPomX1HinHxBUX887PGUsYIsspXIzTaKa7NWkNEenFZhNFIftfDvY2Ww2o4ckqMmdyQkjAQja8ce/u9lqqKhTsQd0imP3HTTA8eon7HfIYj/W4XNdBv4H1jWuCtfzFKRi6dTkA3YR9EQflYT1uTzSwKMYIyzu95/J9mNDKYnDvIb1AsC3+jsW6/zHSnGms0xPlGoNebA9BClAn7wIDAQABo4ICNDCCAjAwcgYDVR0jBGswaYAU3azVDLOULGbUkL/0d3nlgdXDaT+hSqRIMEYxCzAJBgNVBAYTAkRFMRkwFwYDVQQKExBQS0ktMS1WZXJ3YWx0dW5nMRwwGgYDVQQDExNQQ0EtMS1WZXJ3YWx0dW5nLTIwggUA6YHTTjApBgNVHREEIjAggR5maXRjb25uZWN0QGl0Lm5pZWRlcnNhY2hzZW4uZGUwggEnBgNVHR8EggEeMIIBGjCCARagggESoIIBDoZebGRhcDovL3g1MDAuYnVuZC5kZS9DTj1ET0klMjBDQSUyMDEwYSxPVT1ET0ksTz1QS0ktMS1WZXJ3YWx0dW5nLEM9REU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdIZqbGRhcDovL3BraS1kaXJlY3RvcnkuZG9pLWRlLm5ldC9DTj1ET0klMjBDQSUyMDEwYSxPVT1ET0ksTz1QS0ktMS1WZXJ3YWx0dW5nLEM9REU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdIZAaHR0cDovL3g1MDAuYnVuZC5kZS9jZ2ktYmluL3Nob3dfYXR0cj9jbj1ET0klMjBDQSUyMDEwYSZhdHRyPWNybDAWBgNVHSAEDzANMAsGCSsGAQQBvXQBATAOBgNVHQ8BAf8EBAMCBeAwPAYIKwYBBQUHAQEEMDAuMCwGCCsGAQUFBzABhiBodHRwOi8vb2NzcC5kb2kudGVsZXNlYy5kZS9vY3NwcjANBgkqhkiG9w0BAQsFAAOCAgEAbswxXyr99ElgKLiXcBmf9h54m6VfhLZZG+byHv0uCJyxQR02/a+3VP+iOqYwEKZ3jwP5CY+afKMcRQcPuVxojOD1sg4FtLCFpexZb9bvKMw8Lr+EEPOvY9aDUCGOJ1E7x3JuTC8UA17S4m/DbOnIhjIXj1kfodL+LLMBLC6244Y1xHJltFj1Eb18zkCVH5MvNBYOvJ13WezSlXVu9Q4UQ7PWfFQnW1evzHu0O7hpM2MQNhKiUUd8GMsIo+foDAtC9KhXPUSkV/vocX4rcjrj7qhhkROE4ZCqLXO27zHu+QMst/L0fhvyNJq+gOPPALoQy3YDH2wgX/KtONQaVZO+gn6EBmfTefN5bKRxKGZPWfZ6W58KhjDptCZ8zQPqES5BQiPPgzjG9BgCwP7hccv3YIojaw5gXmd4Y4dFZZn+cEGuZXwkpvp27rBow9fuTV5yyIWOyXlv/VUtdvN6t9FZs4eecLR+6yWP0UCDvPUNL+8Npc5e+C2lB5RtOEn0FYSBzY52o8UC3EgYedp19qdmA62AcVRAe/6FnMQ9RrhcYLCkFFVL1CJQT5cs7fm8zxnqyiPGT7RfWLKxm2fCYqulmIj87Fhim308CxZxAxxhDYlrMVfT8aJ67FPZTUAw3cmrJER3bCQqtReFeUucXGEmvwV+VV+8OkgJ4kZ4w7x2PRo=", + "MIIGzTCCBLWgAwIBAgIFAOmB004wDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMCREUxGTAXBgNVBAoTEFBLSS0xLVZlcndhbHR1bmcxHDAaBgNVBAMTE1BDQS0xLVZlcndhbHR1bmctMjAwHhcNMjExMTI5MDAwMDAwWhcNMjcxMTI5MjM1OTU5WjBLMQswCQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEMMAoGA1UECxMDRE9JMRMwEQYDVQQDEwpET0kgQ0EgMTBhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3bjSdHDkxROAmEXgN0FuFkwjmfpZDB3YBDPwuCPq3qwbicxvbFnxAOCJWazORAHMxjHNWg+IxrMHJlHg87y6FSMRIljpP45DlBcH+2ZyWLxnXdrTR6L7uv0aABnNS9FCcNzpha0cftOTh37KOeHJ0yFulNXHmCO/avL+oxY0T+t+eDWcdn3U+31S8mLhJcXaeD/VMmfMPpILLyJwiIVOhELoFKrZ69Y2KhwUj/ENAibAb3hg9ZP7NZvSvtt2u3eIbhLUv/SWgpuxUP3v3NeHgG9qD6hcSTYb+OvEnGXtI5gd38KTgAYjTil+gt21PkljgrluH8nM3jRbiGMmGlJP2Lv5rHEFII7Spz1GFMCiOuZYRtqX17YnbFUQsHzd1J9E6U1ZqXCu2q4IAywp4AI5YACcxt0NSpFHtSnTIAYD4vWRXV8s4ZNjr1MgEXFskxKoPkuJbxwMbj1iQ/d8ljrAIgbpw3gszzxs4+KHltE3M2ClyppmthwzFPJYmTSLcUqsmO5+AGeTTPjrDbAJnlH5fOYDksgdE+VBcpxBH8PIEKTq7+684brOQYh2BDXNxsfYdk645464mo/ZMDSOM5fZg1T14d15CDCjse38LDVLE273RgHt4t9VWq9A/VNANd2U9UFU4vOwq+GlqQyE5GbxUNLDFkU2UcaM55bLKPRKYmECAwEAAaOCAbswggG3MBIGA1UdEwEB/wQIMAYBAf8CAQMwPwYDVR0gBDgwNjA0BgsrBgEEAb10AQIFAzAlMCMGCCsGAQUFBwIBFhdodHRwczovL3d3dy5ic2kuYnVuZC5kZTAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFN2s1QyzlCxm1JC/9Hd55YHVw2k/MHIGA1UdIwRrMGmAFPOPHzb4BN01+d4qhA1jggfppHFCoUqkSDBGMQswCQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEcMBoGA1UEAxMTUENBLTEtVmVyd2FsdHVuZy0yMIIFANoFi14wgbwGA1UdHwSBtDCBsTBioGCgXoZcbGRhcDovL3g1MDAuYnVuZC5kZS9DTj1QQ0EtMS1WZXJ3YWx0dW5nLTIwLE89UEtJLTEtVmVyd2FsdHVuZyxDPURFP2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3QwS6BJoEeGRWh0dHA6Ly94NTAwLmJ1bmQuZGUvY2dpLWJpbi9zaG93X2F0dHI/Y249UENBLTEtVmVyd2FsdHVuZy0yMCZhdHRyPWNybDANBgkqhkiG9w0BAQsFAAOCAgEAIqxlaZE3PjR0DiQ4LcT3WmwZYSLYhaGuIqysAmt/hukJiifyrGZ9oqX9PfpcJZQVxc9sYsnLEvRpy95UXSa/WhGjzsxYiscMp+ZJ0t/447ui6it3pstI0GJNj2RYR4uR+4kvogNih/3L8QLBv65Wbqpy9L1Gn42vD8buubuQrMqgM6S1OVY1YeTlhstnVlykQXcHrN1App5ro1c2mtpb8WvuxjOOvnTNAHk++bqqiS7/3A3rvwXYPOOt+r4kdu4N/O+5u8HVJ9iFEBPNS7ze1aLqPBbuIJKJKEuiNYUleyH+/CqNJ02/Sw1t4tBc8NxNnHY8hnvzZ8FUwSKmnA8/rbMQaI7Ct+FEtmaetNqv0IoFEHwwma7+qftATuBg7LsTLvBHB/kx9NVZMOgjHFt+eMukhfDq6EN2PW7ZFw5lZkFfptfg7iUApeevOhzo4D/erwog7aDxHI0Q+M/5w2qGDyf5meVv+MrLMnOpxzgnkAS3ZcuouTvmIz1WPRDjaemFDOPArqYRn4CesmOA6a2TchuUVl72D/WF9MaxoLTBFEgINNs8zCpwEyOFryUMHcZ0+rpSbebbiCOf7Wcs8qZp0DszMTOXyXJwc8HkbK133ULAXbuLR++xSN3BqFArueziGq4JZ0SekCFwJQ0DgvbDK8EFsa4zHnhoAwAI8P7N2gs=", + "MIIGiDCCBHCgAwIBAgIFANoFi14wDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMCREUxGTAXBgNVBAoTEFBLSS0xLVZlcndhbHR1bmcxHDAaBgNVBAMTE1BDQS0xLVZlcndhbHR1bmctMjAwHhcNMTkxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBGMQswCQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEcMBoGA1UEAxMTUENBLTEtVmVyd2FsdHVuZy0yMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIEgWIZDMqr9t0pxV6TJ/dVSxv7UQPItIL9nY2D1PExxuPR2knOGBdWCWxXXZdhz3C4XsD8o9INUz3BFDYzvo48PtZitzAawNhgQ09WaBJpqbQkrGrpATAsXSRELdSxi1vx7yeaMLMc1zH6iwo/Xd7pcQLxiJkgN2lhw4ORoNk4HBYwGcPjz/eEctrRyEbN1R3WSarg6+J58AKoatNVxw+XTaLmnYthHoJHeCI67nl/Ecrs6rmTivGvVi5PVRAQ0fCAsR702if0SL1F+YdDd2no+HX/2Kf2HR+/zuvIpctY/iRDRth/vf1IZL8BxKHg1NY7i/7wmR3JxyQHnWWL6puOofDLq8i947e4aVHHBP7o6KbOAwau72Goa6XArttzVvDkKuI1wdka+wjNOsXsFJ+D6fSYCKC6x+nX/VCtfm2A0+JeetZV3ovUz387El1MGRvxlHcuFWukj/pB6t0lvbJqnkxyq0ZGVweeFHyK6/TxFW2u+2fD/wJOKH+5yQuo4vKJxvrIK1UeFgRP5LCro+EYCC0kWYfyw9/wMZJDA3h7GbPR4FtNWH6deXGqroY+HO3PPFM9o3Jz+xMkrla3kcEeRDjVMGFnSJhr61SvTqhrqYrj9nqx3du3e/mux+7FxF6m3sYX8ICYppeuGxO1CGMVbC1uqh1gsnVsKv9RRUEw9AgMBAAGjggF7MIIBdzAPBgNVHRMBAf8EBTADAQH/MD8GA1UdIAQ4MDYwNAYLKwYBBAG9dAECAQMwJTAjBggrBgEFBQcCARYXaHR0cHM6Ly93d3cuYnNpLmJ1bmQuZGUwgbwGA1UdHwSBtDCBsTBioGCgXoZcbGRhcDovL3g1MDAuYnVuZC5kZS9DTj1QQ0EtMS1WZXJ3YWx0dW5nLTIwLE89UEtJLTEtVmVyd2FsdHVuZyxDPURFP2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3QwS6BJoEeGRWh0dHA6Ly94NTAwLmJ1bmQuZGUvY2dpLWJpbi9zaG93X2F0dHI/Y249UENBLTEtVmVyd2FsdHVuZy0yMCZhdHRyPWNybDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPOPHzb4BN01+d4qhA1jggfppHFCMDUGA1UdEQQuMCyBEVYtUEtJQGJzaS5idW5kLmRlhhdodHRwczovL3d3dy5ic2kuYnVuZC5kZTANBgkqhkiG9w0BAQsFAAOCAgEARNpaiEE3gm/gLIfLn0d8TQF8DxHmRFszIhb8NVCXJWlMJn2J/UW+XzARsJiB4J0X6tvJcV9AjPwmvlgqAdlo/fce3UwLLPHqrSMuF/xS0Do2CeV1OjgXC9OZmuJshPCthSqReAh9h5cllKMsMaS/gM0i1o5QOCpa/0bTDxlTZ0exQw+4I4O7gi5SABaYyWJhYzLr1EIROaGcvJk78olGQMYnX4CNq3PDXy9HZiKPh3K07V2J8GM36iGxF1haTnjGopmtcUQ2lP/Ng2P0qwCFZ1UFIxIOTX5//O/rRhAroFdP+NwTcdJ4S4xPHNffUWPFCrT8ghEj9Hgy7H/uAtgw5PpnYieevKFjRlOvd/vp9tKjvGreGnU7xxS3xDbuRuNinQyJWVV/29KReumysi27qTS2cJxIYk2WLvt/1eMZt52xzK32G5Pn8ocCK2gMFz7ldyH3jTa5BQFuzFSJ05P1tnVkUpeKlU4aSt8GtW884ifvwhkwOKyAFAQRXvQSFkE505Kzy/6JZcanoy7e/pRlDBtN70MfBOQl8ucVzAVs/tuuTJAg3cVCmHv/hlEgVRVPaUJXhtX1u+5yUWMEm2380uW9w9/PAbdtm0kfRb19p32YMkDihREyKz/Kpgl5NvoU71RRVqISeJhqJ/k2qYfyYRkPRzUiP9qzyyFS7WE8sXs=" + ] +} \ No newline at end of file diff --git a/core/src/test/resources/certificates/grp-fitko-testzertifikat-fit-connect-1.json b/core/src/test/resources/certificates/grp-fitko-testzertifikat-fit-connect-1.json new file mode 100644 index 0000000000000000000000000000000000000000..c26e9bddf01acd396b7f971c76a90b403ca2a714 --- /dev/null +++ b/core/src/test/resources/certificates/grp-fitko-testzertifikat-fit-connect-1.json @@ -0,0 +1,15 @@ +{ + "alg": "RSA-OAEP-256", + "e": "AQAB", + "key_ops": [ + "wrapKey" + ], + "kid": "LM0FPR9i-Yg1Cks-f_HG4dHocwLc2MU3rigME-Uc1Lc-wrapKey", + "kty": "RSA", + "n": "oSi83-NRxxJ2LG7cWt623K8d1TTc32b2zMOEeBYTvYY9fbPOo-qUpM00e_Q23GajPWkQkZSHllvFXz0E4e9LWc2LVkewDqQc3Usy34NjLLQ6zBr6TZoBwZQv2X65ll8e7nSL9JZyfX_Gde37_wtK5waO483Pwk7cg_zA4XwuxpdKaexErRay7Kd7W3v9Gn61BplFV3zaQ3FIWHMLyj8GyH33UVYnCw7iIUuvGrQG2vEac3Ivx7ObdQ-gRS75n6G3AEouerayFdNkXW12Hz2MDR9QvlA7ZZtQ0_Dq8YgHYqh6A-hXgcXtqrW87l-bph18Zi5RtIPv39VgKgELYm9nnZbVavnfhIt2mJRFjfYnW1GD1E8rkC58-CV4lgzZ5ntQahGOhpsCAckJGlEviefe2HpQ0JEb-pfp-LTe1bycRmkcqaBhfl0Acoayfu48wsZgL9EAUG-1RzFK07EgyMFkpppYpa62Np47gbPJI5vlLY6zRcVGR6y6V2b67X_F-tCLte4XaCHbD0EH59t7ITp44q5qdSP6fMcsG3pSGTU5VoTkpc8BJ43dvufhVJaBL73U32VMBzOTVg6w0nSAuXx8FL4v6lSLfPCDqSEzQl0QKIgF9F2g8sVzhaRKPapRXEcAI9Y2_6WPjVCe7QBcWmI6jGhLQGYy42n1fgI7NyNHkF8", + "x5c": [ + "MIIHqzCCBZOgAwIBAgIBTzANBgkqhkiG9w0BAQ0FADBMMQswCQYDVQQGEwJERTERMA8GA1UEChMIVEVTVC1QS0kxETAPBgNVBAsTCFRFU1QtUEtJMRcwFQYDVQQDEw5ET0kgVGVzdC1DQSAxMDAeFw0yMTA2MDIxMjQ4MDdaFw0yNDA2MDIyMzU5NTlaMIHMMQswCQYDVQQGEwJERTEgMB4GA1UEChMXT2VmZmVudGxpY2hlIFZlcndhbHR1bmcxETAPBgNVBAsTCERPSS1PU0NJMS4wLAYDVQQLDCVGSVRLTyBBw7ZSIChGw7ZkZXJhbGUgSVQtS29vcGVyYXRpb24pMRowGAYDVQQHExFGcmFua2Z1cnQgYW0gTWFpbjEwMC4GA1UEAxMnR1JQOiBGSVRLTyBUZXN0emVydGlmaWthdCBGSVQtQ29ubmVjdCAxMQowCAYDVQQFEwExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoSi83+NRxxJ2LG7cWt623K8d1TTc32b2zMOEeBYTvYY9fbPOo+qUpM00e/Q23GajPWkQkZSHllvFXz0E4e9LWc2LVkewDqQc3Usy34NjLLQ6zBr6TZoBwZQv2X65ll8e7nSL9JZyfX/Gde37/wtK5waO483Pwk7cg/zA4XwuxpdKaexErRay7Kd7W3v9Gn61BplFV3zaQ3FIWHMLyj8GyH33UVYnCw7iIUuvGrQG2vEac3Ivx7ObdQ+gRS75n6G3AEouerayFdNkXW12Hz2MDR9QvlA7ZZtQ0/Dq8YgHYqh6A+hXgcXtqrW87l+bph18Zi5RtIPv39VgKgELYm9nnZbVavnfhIt2mJRFjfYnW1GD1E8rkC58+CV4lgzZ5ntQahGOhpsCAckJGlEviefe2HpQ0JEb+pfp+LTe1bycRmkcqaBhfl0Acoayfu48wsZgL9EAUG+1RzFK07EgyMFkpppYpa62Np47gbPJI5vlLY6zRcVGR6y6V2b67X/F+tCLte4XaCHbD0EH59t7ITp44q5qdSP6fMcsG3pSGTU5VoTkpc8BJ43dvufhVJaBL73U32VMBzOTVg6w0nSAuXx8FL4v6lSLfPCDqSEzQl0QKIgF9F2g8sVzhaRKPapRXEcAI9Y2/6WPjVCe7QBcWmI6jGhLQGYy42n1fgI7NyNHkF8CAwEAAaOCAhUwggIRMGEGA1UdIwRaMFiAFOo1H0iCdCR6kxJc5bttXQ/DnxohoTmkNzA1MQswCQYDVQQGEwJERTERMA8GA1UEChMIVEVTVC1QS0kxEzARBgNVBAMTClRFU1QtUENBMjCCBQDr3GGZMB8GA1UdEQQYMBaBFGZpdC1jb25uZWN0QGZpdGtvLmRlMIIBHgYDVR0fBIIBFTCCAREwggENoIIBCaCCAQWGa2xkYXA6Ly9sZGFwLmRvaS50ZXN0LnRlbGVzZWMuZGUvQ049RE9JJTIwVGVzdC1DQSUyMDEwLE9VPVRFU1QtUEtJLE89VEVTVC1QS0ksQz1ERT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0hoGVaHR0cDovL2RvaS50ZXN0LnRlbGVzZWMuZGUvZG9pL2Rvd25sb2FkL2Rvd25sb2FkLmNybD9wYXRoPUNOJTNERE9JJTIwVGVzdC1DQSUyMDEwJTJDT1UlM0RURVNULVBLSSUyQ08lM0RURVNULVBLSSUyQ0MlM0RERSUzRmNlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3QwFgYDVR0gBA8wDTALBgkrBgEEAb10AQEwDgYDVR0PAQH/BAQDAgXgMEEGCCsGAQUFBwEBBDUwMzAxBggrBgEFBQcwAYYlaHR0cDovL29jc3AuZG9pLnRlc3QudGVsZXNlYy5kZS9vY3NwcjANBgkqhkiG9w0BAQ0FAAOCAgEAlMsjiIqQ1/P/eWq6n+hREwWUsnQf/pMp5PL1JahgdN2te1HS3h4GF2Fg2TImucMhTPEx1BAmDppb21LtoefWBcS9+p+aV3sWxIpmdjx8RTXiCaq58h+b8XWhf5LJqfzCK9eyTso9fZ4+qqjt1vo5WeYxulwVAPmkjgglVSuuLZlB8/hKbu2aaKIq4eEQgxn2m5GzfywXKMHr5Q7p2VcGxrap03dJiN5+eaYcQ1ugYSuN/GJwIQgvHK2N8lXhHbmSc5KatxzCTwiEzghsxzcxx8BxIwL0djhFp7o4g4VCIDmEbRGoq9RuPrGtSmznRNIYVk2607UUm99Je89zsORP1ANG+TjzeH1vtByWva+Q+uJ3ca0ItrmyYEhCiysfgg9RSvwDx10kW0vTQI9gRfatSRqRGFKE2QOc2MwS3lVsTbjbr/VWvJI5OO9UWUcO5LDcr6dXEhvAOy4gGD7BPMd5nY35lKtCDdfR6/Cn+0lW5M7mXlxsz8CfsK/QslVcv8PNH6agwrL0tOUgUp2fW1QKQMZ5mmkUllxxJjfKSLL8E14AsEeZm9doTdzp17Vq4JOVM3bFe8tmike2F4HlHEDh/kzAicg0f4oSckNj/enF+slPPCvwXcuSx6NP2I0HxYBlnQeiR2VHiRsEJeC7n1s8peOrNWn8uylntn36Tj1COvU=", + "MIIGnDCCBISgAwIBAgIFAOvcYZkwDQYJKoZIhvcNAQELBQAwNTELMAkGA1UEBhMCREUxETAPBgNVBAoTCFRFU1QtUEtJMRMwEQYDVQQDEwpURVNULVBDQTIwMB4XDTIwMTAwMTAwMDAwMFoXDTI2MTAwMTIzNTk1OVowTDELMAkGA1UEBhMCREUxETAPBgNVBAoTCFRFU1QtUEtJMREwDwYDVQQLEwhURVNULVBLSTEXMBUGA1UEAxMORE9JIFRlc3QtQ0EgMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDd05QR+vF4k6uujeL95TGxEej8bdh4eDS5TnaXq6JvP5rDhP/ropCr5Tzb/R6HNINin8rH6BScTA5Jqy4UnLs5oRX+Ofa0aN5HlFyo1Hcu5snbT1klVVq+/GYsSaL0V6C2n0wFNapdogi4Wt+tyCRKsEQINKR7zb1zIuxj2yPc4tUsXA2EwOlSMHAdtfzZL3ohTHAWkkY12wKGzpQwMDdUK6WKcf3vzLMk4rkYbd8bOBH1E7u4SX1IXTE8XJu4AJtsUkTZywZE54excobKPdWK0gwhXdBdPfWMJ6I55U2cxsOH2mMhifKS32vQnfSqA48pL72JswsXa/owBN4BhmqjBAfb5rJAYkNFUvDx1kJr9Uj9V3fN0PdNDc2V0SMHusUNsiUgePe+Sknh/KKZhqEXOqbIk0AEeLi0pdVAv5mm2aKCHI8G3jWY/5c/9/aiNwITO4Zg80fyWlIenxOX+OelRsDV64txY9rRC7VclL9mAC1eBEnRz3gQSAuZ434CDJ+xnYQA4AQ2jJXTd3a/7Sey/zb7HPve4ONkeQZoL9JVfhF6nvpG5E7VRQB1rDRF6iX411CA3/1Sa0zVWO/r2qbg7w45QBmj2rikNb1oY2ewrVm8pYQKxAyUHmKhD0r5c4tihA4MzDdhhHRMjT3HFmZrUxeO4SzcXQu3/iXpi5Ob2QIDAQABo4IBmjCCAZYwEgYDVR0TAQH/BAgwBgEB/wIBAzA/BgNVHSAEODA2MDQGCysGAQQBswEBAgUDMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vd3d3LmJzaS5idW5kLmRlMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU6jUfSIJ0JHqTElzlu21dD8OfGiEwYQYDVR0jBFowWIAU2Erw8gRwgq+js4JuK5TuLHKjLMGhOaQ3MDUxCzAJBgNVBAYTAkRFMREwDwYDVQQKEwhURVNULVBLSTETMBEGA1UEAxMKVEVTVC1QQ0EyMIIFAJhujJQwgawGA1UdHwSBpDCBoTBWoFSgUoZQbGRhcDovL3Rlc3QteDUwMC5idW5kLmRlL0NOPVRFU1QtUENBMjAsTz1URVNULVBLSSxDPURFP2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3QwR6BFoEOGQWh0dHA6Ly90ZXN0LXg1MDAuYnVuZC5kZS9jZ2ktYmluL3Nob3dfYXR0cj9jbj1URVNULVBDQTIwJmF0dHI9Y3JsMA0GCSqGSIb3DQEBCwUAA4ICAQCLxvm6QwYZT5vLYNs7hPiq/DbWZb4yzVHKkxh+GpwzujHDBATK15Xz6zAVlbvkwF2tdxUk0pugIY26XoIRDMBsedX/lQTug1THx7jNGTgMXmVUuKCpx9Dl4HfQyvQlOxvwPTt92VEOhhuRAulB7KsP2K3TZsLY6JPAyI3ULl59S++VuTxvhkA40jU53n1R3rSaC/MKiWgDa9pWvSsxo3PZcFK31mw8kY5DriXgjl05DyQ2h85PVxis/Y1WgLirkKBmuyU2+nZPy+KIsWldE2ISb+NkxxJ22/aKYDjo5d9H/60ivYYNMzXlIsRN1nz/iqGqy4LJoSUN+sIM5Eh7Xgq7N7O0Zh6NErFxy/XI7gIOUygR2fFWoMromwA+DXAOIBMB7pVuGQXmyL7URFFslP82xNOHkbz/rQD42wtA5POPPa2XpRCZPTTp1DN/NksNG7YDyMtypjO+CAk+z52hvWQ2cSXdS/Z4TYeZx9gKUPd0VB2HMQkjzBR4BL9jR4RHOJxmNKVD9+beB9Itso6nW76FskS6BPLwVPCU1R9FK9hRUWs8Lf1/4k4E/rrkbktY79rpWSiSljaD+kBpo9yd8tiqOW/Zy9tfWiups+3DF3Crykv9jBd/OyCmd5u3n+WUYWEmYgsM8yOunfy3mRlJXgdJ0jTJW5M1RJwh8F+a3t149Q==", + "MIIGVjCCBD6gAwIBAgIFAJhujJQwDQYJKoZIhvcNAQELBQAwNTELMAkGA1UEBhMCREUxETAPBgNVBAoTCFRFU1QtUEtJMRMwEQYDVQQDEwpURVNULVBDQTIwMB4XDTE5MTIwMTAwMDAwMFoXDTI5MTIzMTIzNTk1OVowNTELMAkGA1UEBhMCREUxETAPBgNVBAoTCFRFU1QtUEtJMRMwEQYDVQQDEwpURVNULVBDQTIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoAo7xDpvHSqSOJGW1rtalhZayl0IZGAtmF1f7Ssvlq9DsJxEu6lRhTwGHPzPEXZ4EMdHwHGnw3Zv4XL4Uc/FcsBOzVKFb6D2WLWXoJ/9D6rHpG+iC9148JcYTPLIrBIfPKpLdC2vKqVubqSUvLZl8fjmME1elAuqwu6qDmelbTa5iO+fV0awZkx0KrlCA88a6amldCqFfeert/XKqsrk74ihVzaUiTCW9aKNBxucQbKHhU1W8jgzpQi3x758dRtoDqChbPFkTLNoQhLP8pPa3MynbXUC0XfF7h63zQNpNBvqnUjBl5oTbimoUFLdh8Wo0Bs0ifzu6WC0fEkI7ZQVOBjHEvMvN+rqAecsG/BvCDVLVK27T+HC3zAZcusKa6/X73Sa3uO1EySOG6jwSmjTctCTx4qDjJrZZqZEZgSmzQqWTyWyo5LCIx3cPce+kafAJqufasT/WZS5vQ/65fUt/tEzZUFG34Pl6sFhtfe+91adDlOYioLnPC8EcWcSDP4DRh8CbEEqu/oLEj/UhAmcJGWutHtNKmr59j/SO06LbZpSGgy4OU+aCiWn3N8S1wwBYpWqn0S1r876OQIofKdHshWPhg8/KXh/yjgx6a5/HMuKPQVH1FmFeWlnsbkpdMNPSDHM5LjjIwEm6dy2TwP35jSoYw/leIB4izaz6pEOMKUCAwEAAaOCAWswggFnMA8GA1UdEwEB/wQFMAMBAf8wPwYDVR0gBDgwNjA0BgsrBgEEAbMBAQIBAzAlMCMGCCsGAQUFBwIBFhdodHRwczovL3d3dy5ic2kuYnVuZC5kZTCBrAYDVR0fBIGkMIGhMFagVKBShlBsZGFwOi8vdGVzdC14NTAwLmJ1bmQuZGUvQ049VEVTVC1QQ0EyMCxPPVRFU1QtUEtJLEM9REU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDBHoEWgQ4ZBaHR0cDovL3Rlc3QteDUwMC5idW5kLmRlL2NnaS1iaW4vc2hvd19hdHRyP2NuPVRFU1QtUENBMjAmYXR0cj1jcmwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTYSvDyBHCCr6Ozgm4rlO4scqMswTA1BgNVHREELjAsgRFWLVBLSUBic2kuYnVuZC5kZYYXaHR0cHM6Ly93d3cuYnNpLmJ1bmQuZGUwDQYJKoZIhvcNAQELBQADggIBADK29j9Tkm6SjI0PNmYFbOl/8FsjVsN4pZ0g67z4Ro1egbldCbV9pst7PB9TSjaKUBClS9fJUU5izOpxj4LxSehpJeJZNs47sXuMkePPCQdKv7WmwvWLYY2wiMJ7qF4PyvtIqX6Wyy8y0nhuAb0KpO4jBG0kbuuxvt5oe6PAVLfvXiVJSIjlOmjxNV50ryVWb0/nEISvrOdAno9vY5jIAfBQQauCAy4XgMR/6uyEaB/aGcb5Qbc8HlDS8tqRGQklRJ4XX6mDU2w8tU2szHQoSJt41p6UyQt0BN0bLKtVmZr8RlsZERUk4m8x37VmoltNxgkUV6SARORNmqKPrvSgu/FgbKayJ/+8h0qEKhvhCQmbqsjlDxvAcv7jPelmEmNwKEdvToxpMzPpQKCvXelgxANDHPdVqnQpBeqb53VFr5oKCIfYK4KaTPIzntmaLnS8JbM3Bb36tyCvh+HX5IcWCskRAmh10k5FmB4xBcz394gjJDYrOEqVeuNduSFVLwxZq8K/0MC6gacrxytnfnChjBdd+7gE8TDbHjA2xd8mbHDC5c1VrrHxs9coPr+nSZP2dltg/OMCPRBuOXL/vwjfh7Wc6Rl4LBn+H1Ql/J52s3b1aukMiL3pSJOElvThgBKsPlf6ftwIANzr/v6m3iOt0ifNLVMsPpYM2c9nj8QcvFcu" + ] +} \ No newline at end of file diff --git a/core/src/test/resources/certificates/grp-fitko-testzertifikat-fit-connect-2.json b/core/src/test/resources/certificates/grp-fitko-testzertifikat-fit-connect-2.json new file mode 100644 index 0000000000000000000000000000000000000000..b4f001045a83fcddae2cc0d4f2b796354dbf33ce --- /dev/null +++ b/core/src/test/resources/certificates/grp-fitko-testzertifikat-fit-connect-2.json @@ -0,0 +1,15 @@ +{ + "alg": "RSA-OAEP-256", + "e": "AQAB", + "key_ops": [ + "wrapKey" + ], + "kid": "rRaGt35McGycD4MeRyHZSLFo7dda2PEJscBuPT1KJdE-wrapKey", + "kty": "RSA", + "n": "q3HY4MdeI9x1_R3nxqqtwFvkp5P3aWWEIIPet1OWsJNBNtbG-Z0LhM3RX0oJne93m2bMFqwvo5akdJmpeLg-Sl0WBUZt_0UMI55I7S0qWVYOnqQncVJcHIp8FU0SuHNuSDWTPXIxrLuJFm8TkYfpWOvwo1wXZcqCI2vMKLTB5L8D_3z8CqZgSeB_7tXM3QESIt7vRoSCn66Ws5NpvAwJiBCvHJAMJ7D2y2lAFPCweB3c1v6RaR8pX8xFLSxRbKlUEo9sW0HFoMfA8Vq15wgWdVGugopxKe3ySXErCYo1rP4qtS9n8jlyxUHD307KUcKoLBM-qxaIL4xZEFBb2fQ7NF5H1BeyrYQbZp0l5whobo_XuSaW8HwnlHqVna4pLS4o2QSp--5g-IVeO0heEjcQPvWvKy38hYu5AKv1HLhK-SHw0XplrIRYzXc31j5PlE0rBAWhQlVUZr-MtjY19-Oaxlo0D56_Xzrct3PUp8f9pOh1OwzeitO0n4m3bVGcQKa8J6ulB24HndzQBiCB9hxofvvdYNJZB6YKlQzogYKevTVSN24p4Y-CD4AVmDnmDiLe6eFReGRNgXQ5taCmRgQ7Jx1duA18jHQoi9dSlB6JBScuOemwFOYxvUFI-ISBhHv7NQttfCZzb3LL9bsn7ze6Kn2wFuZvsHGT5eZBMDTt-2c", + "x5c": [ + "MIIHqzCCBZOgAwIBAgIBUDANBgkqhkiG9w0BAQ0FADBMMQswCQYDVQQGEwJERTERMA8GA1UEChMIVEVTVC1QS0kxETAPBgNVBAsTCFRFU1QtUEtJMRcwFQYDVQQDEw5ET0kgVGVzdC1DQSAxMDAeFw0yMTA2MDIxMjQ4MzdaFw0yNDA2MDIyMzU5NTlaMIHMMQswCQYDVQQGEwJERTEgMB4GA1UEChMXT2VmZmVudGxpY2hlIFZlcndhbHR1bmcxETAPBgNVBAsTCERPSS1PU0NJMS4wLAYDVQQLDCVGSVRLTyBBw7ZSIChGw7ZkZXJhbGUgSVQtS29vcGVyYXRpb24pMRowGAYDVQQHExFGcmFua2Z1cnQgYW0gTWFpbjEwMC4GA1UEAxMnR1JQOiBGSVRLTyBUZXN0emVydGlmaWthdCBGSVQtQ29ubmVjdCAyMQowCAYDVQQFEwExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3HY4MdeI9x1/R3nxqqtwFvkp5P3aWWEIIPet1OWsJNBNtbG+Z0LhM3RX0oJne93m2bMFqwvo5akdJmpeLg+Sl0WBUZt/0UMI55I7S0qWVYOnqQncVJcHIp8FU0SuHNuSDWTPXIxrLuJFm8TkYfpWOvwo1wXZcqCI2vMKLTB5L8D/3z8CqZgSeB/7tXM3QESIt7vRoSCn66Ws5NpvAwJiBCvHJAMJ7D2y2lAFPCweB3c1v6RaR8pX8xFLSxRbKlUEo9sW0HFoMfA8Vq15wgWdVGugopxKe3ySXErCYo1rP4qtS9n8jlyxUHD307KUcKoLBM+qxaIL4xZEFBb2fQ7NF5H1BeyrYQbZp0l5whobo/XuSaW8HwnlHqVna4pLS4o2QSp++5g+IVeO0heEjcQPvWvKy38hYu5AKv1HLhK+SHw0XplrIRYzXc31j5PlE0rBAWhQlVUZr+MtjY19+Oaxlo0D56/Xzrct3PUp8f9pOh1OwzeitO0n4m3bVGcQKa8J6ulB24HndzQBiCB9hxofvvdYNJZB6YKlQzogYKevTVSN24p4Y+CD4AVmDnmDiLe6eFReGRNgXQ5taCmRgQ7Jx1duA18jHQoi9dSlB6JBScuOemwFOYxvUFI+ISBhHv7NQttfCZzb3LL9bsn7ze6Kn2wFuZvsHGT5eZBMDTt+2cCAwEAAaOCAhUwggIRMGEGA1UdIwRaMFiAFOo1H0iCdCR6kxJc5bttXQ/DnxohoTmkNzA1MQswCQYDVQQGEwJERTERMA8GA1UEChMIVEVTVC1QS0kxEzARBgNVBAMTClRFU1QtUENBMjCCBQDr3GGZMB8GA1UdEQQYMBaBFGZpdC1jb25uZWN0QGZpdGtvLmRlMIIBHgYDVR0fBIIBFTCCAREwggENoIIBCaCCAQWGa2xkYXA6Ly9sZGFwLmRvaS50ZXN0LnRlbGVzZWMuZGUvQ049RE9JJTIwVGVzdC1DQSUyMDEwLE9VPVRFU1QtUEtJLE89VEVTVC1QS0ksQz1ERT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0hoGVaHR0cDovL2RvaS50ZXN0LnRlbGVzZWMuZGUvZG9pL2Rvd25sb2FkL2Rvd25sb2FkLmNybD9wYXRoPUNOJTNERE9JJTIwVGVzdC1DQSUyMDEwJTJDT1UlM0RURVNULVBLSSUyQ08lM0RURVNULVBLSSUyQ0MlM0RERSUzRmNlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3QwFgYDVR0gBA8wDTALBgkrBgEEAb10AQEwDgYDVR0PAQH/BAQDAgXgMEEGCCsGAQUFBwEBBDUwMzAxBggrBgEFBQcwAYYlaHR0cDovL29jc3AuZG9pLnRlc3QudGVsZXNlYy5kZS9vY3NwcjANBgkqhkiG9w0BAQ0FAAOCAgEAtqVwGbsvXQI4/yctLKAW2AFf8vXkB2UrWsnEasYGjWzrDIDuI5+9mP/idR2W0+Vi0E8xzyWtosYZwK+gWYSrtoKlMuNLBzHPy/kdlZFHgTyvPcfwNF6c67//tb++5g2EYd1ZsMfdBMA/O4zATOyROtsyqt8GjuoYcKr9/aNHiRhvdVf1BaPCfxd1O+asvajzGeLqiGCGQUuiQnYtotfbnvLWaebPprt/TNkX7JWWD9GkkR20/fZt8EgoGRtbEL/Y8tOTsbZ0RSjSETanFHtNMMo8IxmIj0p2y5dHlVPUyHHWLipr8HZhX6fMfYFk+EOBvgSAYX18VXAfBqmRQuunuC+WNzyJL42aUAjX/VYWvpMjoshR2V+TdY5wcr5soMoXWFhpGKnOpOZHEQ+Tst7H369OV7e1eZWyiQbMCA+LK73SlZnzizQ8tftVRDxaPWSuayHwa8nbrDxXbClNiNDYYT6i5LqTYVJOvd4V3iTkTOESpRFoq7zwOjrv7ny38qV8BeKrNP7P4FOtvzi0qT1FwdzSpq5yHTAETai27uaJ9rGyPCQzZmMnn7A4PsIKr/MrEh8ywlXSdWHaKLoPfLnOT6cuqy5OdYakHOcCKwEvmvy9tstHHu2vf1gKm41E/q3W03kmGTwbVzxMe2qQdYbsPTez9qImnwUyfp7P5uoRaPI=", + "MIIGnDCCBISgAwIBAgIFAOvcYZkwDQYJKoZIhvcNAQELBQAwNTELMAkGA1UEBhMCREUxETAPBgNVBAoTCFRFU1QtUEtJMRMwEQYDVQQDEwpURVNULVBDQTIwMB4XDTIwMTAwMTAwMDAwMFoXDTI2MTAwMTIzNTk1OVowTDELMAkGA1UEBhMCREUxETAPBgNVBAoTCFRFU1QtUEtJMREwDwYDVQQLEwhURVNULVBLSTEXMBUGA1UEAxMORE9JIFRlc3QtQ0EgMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDd05QR+vF4k6uujeL95TGxEej8bdh4eDS5TnaXq6JvP5rDhP/ropCr5Tzb/R6HNINin8rH6BScTA5Jqy4UnLs5oRX+Ofa0aN5HlFyo1Hcu5snbT1klVVq+/GYsSaL0V6C2n0wFNapdogi4Wt+tyCRKsEQINKR7zb1zIuxj2yPc4tUsXA2EwOlSMHAdtfzZL3ohTHAWkkY12wKGzpQwMDdUK6WKcf3vzLMk4rkYbd8bOBH1E7u4SX1IXTE8XJu4AJtsUkTZywZE54excobKPdWK0gwhXdBdPfWMJ6I55U2cxsOH2mMhifKS32vQnfSqA48pL72JswsXa/owBN4BhmqjBAfb5rJAYkNFUvDx1kJr9Uj9V3fN0PdNDc2V0SMHusUNsiUgePe+Sknh/KKZhqEXOqbIk0AEeLi0pdVAv5mm2aKCHI8G3jWY/5c/9/aiNwITO4Zg80fyWlIenxOX+OelRsDV64txY9rRC7VclL9mAC1eBEnRz3gQSAuZ434CDJ+xnYQA4AQ2jJXTd3a/7Sey/zb7HPve4ONkeQZoL9JVfhF6nvpG5E7VRQB1rDRF6iX411CA3/1Sa0zVWO/r2qbg7w45QBmj2rikNb1oY2ewrVm8pYQKxAyUHmKhD0r5c4tihA4MzDdhhHRMjT3HFmZrUxeO4SzcXQu3/iXpi5Ob2QIDAQABo4IBmjCCAZYwEgYDVR0TAQH/BAgwBgEB/wIBAzA/BgNVHSAEODA2MDQGCysGAQQBswEBAgUDMCUwIwYIKwYBBQUHAgEWF2h0dHBzOi8vd3d3LmJzaS5idW5kLmRlMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU6jUfSIJ0JHqTElzlu21dD8OfGiEwYQYDVR0jBFowWIAU2Erw8gRwgq+js4JuK5TuLHKjLMGhOaQ3MDUxCzAJBgNVBAYTAkRFMREwDwYDVQQKEwhURVNULVBLSTETMBEGA1UEAxMKVEVTVC1QQ0EyMIIFAJhujJQwgawGA1UdHwSBpDCBoTBWoFSgUoZQbGRhcDovL3Rlc3QteDUwMC5idW5kLmRlL0NOPVRFU1QtUENBMjAsTz1URVNULVBLSSxDPURFP2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3QwR6BFoEOGQWh0dHA6Ly90ZXN0LXg1MDAuYnVuZC5kZS9jZ2ktYmluL3Nob3dfYXR0cj9jbj1URVNULVBDQTIwJmF0dHI9Y3JsMA0GCSqGSIb3DQEBCwUAA4ICAQCLxvm6QwYZT5vLYNs7hPiq/DbWZb4yzVHKkxh+GpwzujHDBATK15Xz6zAVlbvkwF2tdxUk0pugIY26XoIRDMBsedX/lQTug1THx7jNGTgMXmVUuKCpx9Dl4HfQyvQlOxvwPTt92VEOhhuRAulB7KsP2K3TZsLY6JPAyI3ULl59S++VuTxvhkA40jU53n1R3rSaC/MKiWgDa9pWvSsxo3PZcFK31mw8kY5DriXgjl05DyQ2h85PVxis/Y1WgLirkKBmuyU2+nZPy+KIsWldE2ISb+NkxxJ22/aKYDjo5d9H/60ivYYNMzXlIsRN1nz/iqGqy4LJoSUN+sIM5Eh7Xgq7N7O0Zh6NErFxy/XI7gIOUygR2fFWoMromwA+DXAOIBMB7pVuGQXmyL7URFFslP82xNOHkbz/rQD42wtA5POPPa2XpRCZPTTp1DN/NksNG7YDyMtypjO+CAk+z52hvWQ2cSXdS/Z4TYeZx9gKUPd0VB2HMQkjzBR4BL9jR4RHOJxmNKVD9+beB9Itso6nW76FskS6BPLwVPCU1R9FK9hRUWs8Lf1/4k4E/rrkbktY79rpWSiSljaD+kBpo9yd8tiqOW/Zy9tfWiups+3DF3Crykv9jBd/OyCmd5u3n+WUYWEmYgsM8yOunfy3mRlJXgdJ0jTJW5M1RJwh8F+a3t149Q==", + "MIIGVjCCBD6gAwIBAgIFAJhujJQwDQYJKoZIhvcNAQELBQAwNTELMAkGA1UEBhMCREUxETAPBgNVBAoTCFRFU1QtUEtJMRMwEQYDVQQDEwpURVNULVBDQTIwMB4XDTE5MTIwMTAwMDAwMFoXDTI5MTIzMTIzNTk1OVowNTELMAkGA1UEBhMCREUxETAPBgNVBAoTCFRFU1QtUEtJMRMwEQYDVQQDEwpURVNULVBDQTIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoAo7xDpvHSqSOJGW1rtalhZayl0IZGAtmF1f7Ssvlq9DsJxEu6lRhTwGHPzPEXZ4EMdHwHGnw3Zv4XL4Uc/FcsBOzVKFb6D2WLWXoJ/9D6rHpG+iC9148JcYTPLIrBIfPKpLdC2vKqVubqSUvLZl8fjmME1elAuqwu6qDmelbTa5iO+fV0awZkx0KrlCA88a6amldCqFfeert/XKqsrk74ihVzaUiTCW9aKNBxucQbKHhU1W8jgzpQi3x758dRtoDqChbPFkTLNoQhLP8pPa3MynbXUC0XfF7h63zQNpNBvqnUjBl5oTbimoUFLdh8Wo0Bs0ifzu6WC0fEkI7ZQVOBjHEvMvN+rqAecsG/BvCDVLVK27T+HC3zAZcusKa6/X73Sa3uO1EySOG6jwSmjTctCTx4qDjJrZZqZEZgSmzQqWTyWyo5LCIx3cPce+kafAJqufasT/WZS5vQ/65fUt/tEzZUFG34Pl6sFhtfe+91adDlOYioLnPC8EcWcSDP4DRh8CbEEqu/oLEj/UhAmcJGWutHtNKmr59j/SO06LbZpSGgy4OU+aCiWn3N8S1wwBYpWqn0S1r876OQIofKdHshWPhg8/KXh/yjgx6a5/HMuKPQVH1FmFeWlnsbkpdMNPSDHM5LjjIwEm6dy2TwP35jSoYw/leIB4izaz6pEOMKUCAwEAAaOCAWswggFnMA8GA1UdEwEB/wQFMAMBAf8wPwYDVR0gBDgwNjA0BgsrBgEEAbMBAQIBAzAlMCMGCCsGAQUFBwIBFhdodHRwczovL3d3dy5ic2kuYnVuZC5kZTCBrAYDVR0fBIGkMIGhMFagVKBShlBsZGFwOi8vdGVzdC14NTAwLmJ1bmQuZGUvQ049VEVTVC1QQ0EyMCxPPVRFU1QtUEtJLEM9REU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDBHoEWgQ4ZBaHR0cDovL3Rlc3QteDUwMC5idW5kLmRlL2NnaS1iaW4vc2hvd19hdHRyP2NuPVRFU1QtUENBMjAmYXR0cj1jcmwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTYSvDyBHCCr6Ozgm4rlO4scqMswTA1BgNVHREELjAsgRFWLVBLSUBic2kuYnVuZC5kZYYXaHR0cHM6Ly93d3cuYnNpLmJ1bmQuZGUwDQYJKoZIhvcNAQELBQADggIBADK29j9Tkm6SjI0PNmYFbOl/8FsjVsN4pZ0g67z4Ro1egbldCbV9pst7PB9TSjaKUBClS9fJUU5izOpxj4LxSehpJeJZNs47sXuMkePPCQdKv7WmwvWLYY2wiMJ7qF4PyvtIqX6Wyy8y0nhuAb0KpO4jBG0kbuuxvt5oe6PAVLfvXiVJSIjlOmjxNV50ryVWb0/nEISvrOdAno9vY5jIAfBQQauCAy4XgMR/6uyEaB/aGcb5Qbc8HlDS8tqRGQklRJ4XX6mDU2w8tU2szHQoSJt41p6UyQt0BN0bLKtVmZr8RlsZERUk4m8x37VmoltNxgkUV6SARORNmqKPrvSgu/FgbKayJ/+8h0qEKhvhCQmbqsjlDxvAcv7jPelmEmNwKEdvToxpMzPpQKCvXelgxANDHPdVqnQpBeqb53VFr5oKCIfYK4KaTPIzntmaLnS8JbM3Bb36tyCvh+HX5IcWCskRAmh10k5FmB4xBcz394gjJDYrOEqVeuNduSFVLwxZq8K/0MC6gacrxytnfnChjBdd+7gE8TDbHjA2xd8mbHDC5c1VrrHxs9coPr+nSZP2dltg/OMCPRBuOXL/vwjfh7Wc6Rl4LBn+H1Ql/J52s3b1aukMiL3pSJOElvThgBKsPlf6ftwIANzr/v6m3iOt0ifNLVMsPpYM2c9nj8QcvFcu" + ] +} \ No newline at end of file diff --git a/core/src/test/resources/trusted-test-root-certificates/TEST-PCA20.pem b/core/src/test/resources/trusted-test-root-certificates/TEST-PCA20.pem new file mode 100644 index 0000000000000000000000000000000000000000..0c5c8b31bfcf8050ba3737dde617dc642f53a13c --- /dev/null +++ b/core/src/test/resources/trusted-test-root-certificates/TEST-PCA20.pem @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGVjCCBD6gAwIBAgIFAJhujJQwDQYJKoZIhvcNAQELBQAwNTELMAkGA1UEBhMC +REUxETAPBgNVBAoTCFRFU1QtUEtJMRMwEQYDVQQDEwpURVNULVBDQTIwMB4XDTE5 +MTIwMTAwMDAwMFoXDTI5MTIzMTIzNTk1OVowNTELMAkGA1UEBhMCREUxETAPBgNV +BAoTCFRFU1QtUEtJMRMwEQYDVQQDEwpURVNULVBDQTIwMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAoAo7xDpvHSqSOJGW1rtalhZayl0IZGAtmF1f7Ssv +lq9DsJxEu6lRhTwGHPzPEXZ4EMdHwHGnw3Zv4XL4Uc/FcsBOzVKFb6D2WLWXoJ/9 +D6rHpG+iC9148JcYTPLIrBIfPKpLdC2vKqVubqSUvLZl8fjmME1elAuqwu6qDmel +bTa5iO+fV0awZkx0KrlCA88a6amldCqFfeert/XKqsrk74ihVzaUiTCW9aKNBxuc +QbKHhU1W8jgzpQi3x758dRtoDqChbPFkTLNoQhLP8pPa3MynbXUC0XfF7h63zQNp +NBvqnUjBl5oTbimoUFLdh8Wo0Bs0ifzu6WC0fEkI7ZQVOBjHEvMvN+rqAecsG/Bv +CDVLVK27T+HC3zAZcusKa6/X73Sa3uO1EySOG6jwSmjTctCTx4qDjJrZZqZEZgSm +zQqWTyWyo5LCIx3cPce+kafAJqufasT/WZS5vQ/65fUt/tEzZUFG34Pl6sFhtfe+ +91adDlOYioLnPC8EcWcSDP4DRh8CbEEqu/oLEj/UhAmcJGWutHtNKmr59j/SO06L +bZpSGgy4OU+aCiWn3N8S1wwBYpWqn0S1r876OQIofKdHshWPhg8/KXh/yjgx6a5/ +HMuKPQVH1FmFeWlnsbkpdMNPSDHM5LjjIwEm6dy2TwP35jSoYw/leIB4izaz6pEO +MKUCAwEAAaOCAWswggFnMA8GA1UdEwEB/wQFMAMBAf8wPwYDVR0gBDgwNjA0Bgsr +BgEEAbMBAQIBAzAlMCMGCCsGAQUFBwIBFhdodHRwczovL3d3dy5ic2kuYnVuZC5k +ZTCBrAYDVR0fBIGkMIGhMFagVKBShlBsZGFwOi8vdGVzdC14NTAwLmJ1bmQuZGUv +Q049VEVTVC1QQ0EyMCxPPVRFU1QtUEtJLEM9REU/Y2VydGlmaWNhdGVSZXZvY2F0 +aW9uTGlzdDBHoEWgQ4ZBaHR0cDovL3Rlc3QteDUwMC5idW5kLmRlL2NnaS1iaW4v +c2hvd19hdHRyP2NuPVRFU1QtUENBMjAmYXR0cj1jcmwwDgYDVR0PAQH/BAQDAgEG +MB0GA1UdDgQWBBTYSvDyBHCCr6Ozgm4rlO4scqMswTA1BgNVHREELjAsgRFWLVBL +SUBic2kuYnVuZC5kZYYXaHR0cHM6Ly93d3cuYnNpLmJ1bmQuZGUwDQYJKoZIhvcN +AQELBQADggIBADK29j9Tkm6SjI0PNmYFbOl/8FsjVsN4pZ0g67z4Ro1egbldCbV9 +pst7PB9TSjaKUBClS9fJUU5izOpxj4LxSehpJeJZNs47sXuMkePPCQdKv7WmwvWL +YY2wiMJ7qF4PyvtIqX6Wyy8y0nhuAb0KpO4jBG0kbuuxvt5oe6PAVLfvXiVJSIjl +OmjxNV50ryVWb0/nEISvrOdAno9vY5jIAfBQQauCAy4XgMR/6uyEaB/aGcb5Qbc8 +HlDS8tqRGQklRJ4XX6mDU2w8tU2szHQoSJt41p6UyQt0BN0bLKtVmZr8RlsZERUk +4m8x37VmoltNxgkUV6SARORNmqKPrvSgu/FgbKayJ/+8h0qEKhvhCQmbqsjlDxvA +cv7jPelmEmNwKEdvToxpMzPpQKCvXelgxANDHPdVqnQpBeqb53VFr5oKCIfYK4Ka +TPIzntmaLnS8JbM3Bb36tyCvh+HX5IcWCskRAmh10k5FmB4xBcz394gjJDYrOEqV +euNduSFVLwxZq8K/0MC6gacrxytnfnChjBdd+7gE8TDbHjA2xd8mbHDC5c1VrrHx +s9coPr+nSZP2dltg/OMCPRBuOXL/vwjfh7Wc6Rl4LBn+H1Ql/J52s3b1aukMiL3p +SJOElvThgBKsPlf6ftwIANzr/v6m3iOt0ifNLVMsPpYM2c9nj8QcvFcu +-----END CERTIFICATE----- diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..d7038a87e182814074b2fa1ab7c84563b6e1d8ba --- /dev/null +++ b/integration-tests/pom.xml @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>sdk-java</artifactId> + <groupId>dev.fitko.fitconnect.sdk</groupId> + <version>1.0.0-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + <packaging>jar</packaging> + + <artifactId>integration-tests</artifactId> + <name>FIT-Connect Java SDK - Integration Tests</name> + + <properties> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + + <dependencies> + <dependency> + <groupId>dev.fitko.fitconnect.sdk</groupId> + <artifactId>api</artifactId> + </dependency> + <dependency> + <groupId>dev.fitko.fitconnect.sdk</groupId> + <artifactId>client</artifactId> + </dependency> + + <!-- Tests --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-all</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.awaitility</groupId> + <artifactId>awaitility</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <skipIfEmpty>true</skipIfEmpty> + </configuration> + <!-- <executions>--> +<!-- <execution>--> +<!-- <id>default-jar</id>--> +<!-- <phase>none</phase>--> +<!-- </execution>--> +<!-- </executions>--> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-install-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>integration-test</goal> + <goal>verify</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/AuthenticationIT.java b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/AuthenticationIT.java new file mode 100644 index 0000000000000000000000000000000000000000..7c865d2390eee22757fa779d7a802f57f3d2a2dd --- /dev/null +++ b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/AuthenticationIT.java @@ -0,0 +1,35 @@ +package dev.fitko.fitconnect.integrationtests; + +import dev.fitko.fitconnect.integrationtests.condition.EnableIfEnvironmentVariablesAreSet; +import dev.fitko.fitconnect.api.config.BuildInfo; +import dev.fitko.fitconnect.api.domain.auth.OAuthToken; +import dev.fitko.fitconnect.core.auth.DefaultOAuthService; +import dev.fitko.fitconnect.core.http.RestService; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +@EnableIfEnvironmentVariablesAreSet +public class AuthenticationIT { + + @Test + void retrieveAuthenticationToken() { + + // Given + final var tokenUrl = "https://auth-testing.fit-connect.fitko.dev/token"; + final var clientId = System.getenv("SUBSCRIBER_CLIENT_ID"); + final var secret = System.getenv("SUBSCRIBER_CLIENT_SECRET"); + + final RestService restService = new RestService(new BuildInfo()); + + final var authService = new DefaultOAuthService(restService.getRestTemplate(), clientId, secret, tokenUrl); + + // When + final OAuthToken token = authService.getCurrentToken(); + + // Then + Assertions.assertNotNull(token); + Assertions.assertNull(token.getError()); + Assertions.assertNotNull(token.getAccessToken()); + Assertions.assertEquals(1800, token.getExpiresIn()); + } +} diff --git a/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/EventLogIT.java b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/EventLogIT.java new file mode 100644 index 0000000000000000000000000000000000000000..299736cb0d46579ca952fbb98f7f37f59f9c4fc4 --- /dev/null +++ b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/EventLogIT.java @@ -0,0 +1,303 @@ +package dev.fitko.fitconnect.integrationtests; + +import dev.fitko.fitconnect.api.config.ApplicationConfig; +import dev.fitko.fitconnect.api.domain.model.event.Event; +import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry; +import dev.fitko.fitconnect.api.domain.model.event.SubmissionState; +import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataEncryptionIssue; +import dev.fitko.fitconnect.api.domain.model.event.problems.data.IncorrectDataAuthenticationTag; +import dev.fitko.fitconnect.api.domain.model.event.problems.submission.InvalidEventLog; +import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import dev.fitko.fitconnect.api.exceptions.SubmissionRequestException; +import dev.fitko.fitconnect.client.SenderClient; +import dev.fitko.fitconnect.client.SubscriberClient; +import dev.fitko.fitconnect.client.factory.ClientFactory; +import dev.fitko.fitconnect.client.sender.model.Attachment; +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; +import dev.fitko.fitconnect.integrationtests.condition.EnableIfEnvironmentVariablesAreSet; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.awaitility.Awaitility.await; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.collection.IsEmptyCollection.empty; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnableIfEnvironmentVariablesAreSet +public class EventLogIT extends IntegrationTestBase { + + @BeforeEach + public void cleanup(){ + cleanupTestSubmissions(); + } + + @Test + void testRejectEvent() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final SenderClient senderClient = ClientFactory.getSenderClient(config); + + final var submission = SendableSubmission.Builder() + .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") + .setJsonData("{ \"data\": \"Beispiel Fachdaten\" }") + .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text")) + .build(); + + final var sentSubmission = senderClient.send(submission); + + assertNotNull(sentSubmission); + + // When + final var subscriberClient = ClientFactory.getSubscriberClient(config); + + final var sentSubmissionId = sentSubmission.getSubmissionId(); + + // reject and remove + subscriberClient.requestSubmission(sentSubmissionId).rejectSubmission(List.of(new DataEncryptionIssue())); + + // check event log if reject event was sent + final SubmissionStatus status = senderClient.getStatusForSubmission(sentSubmission); + assertThat(status.getStatus(), is(SubmissionState.REJECTED)); + + // second attempt to receive and reject the submission should return an empty result + // since the submission is gone after being rejected + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> subscriberClient.requestSubmission(sentSubmissionId)); + assertThat(exception.getMessage(), containsString("Submission not found")); + } + + @Test + void testAcceptEvent() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final SenderClient senderClient = ClientFactory.getSenderClient(config); + final var subscriberClient = ClientFactory.getSubscriberClient(config); + + final var submission = SendableSubmission.Builder() + .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") + .setJsonData("{ \"data\": \"Beispiel Fachdaten\" }") + .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text")) + .build(); + + final var sentSubmission = senderClient.send(submission); + + Assertions.assertNotNull(sentSubmission); + + // When + + final var sentSubmissionId = sentSubmission.getSubmissionId(); + + // accept and remove + subscriberClient.requestSubmission(sentSubmissionId).acceptSubmission(); + + // check event log if accept event was sent + final SubmissionStatus status = senderClient.getStatusForSubmission(sentSubmission); + assertThat(status.getStatus(), is(SubmissionState.ACCEPTED)); + + // second attempt to receive the submission should return an empty result + // since the submission is gone after being accepted + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> subscriberClient.requestSubmission(sentSubmissionId)); + assertThat(exception.getMessage(), containsString("Submission not found")); + } + + @Test + void testAcceptEventWithProblem() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final var submission = SendableSubmission.Builder() + .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") + .setJsonData("{ \"data\": \"Beispiel Fachdaten\" }") + .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text")) + .build(); + + final var sentSubmission = ClientFactory.getSenderClient(config).send(submission); + + assertNotNull(sentSubmission); + + // When + final var subscriberClient = ClientFactory.getSubscriberClient(config); + + final var sentSubmissionId = sentSubmission.getSubmissionId(); + + // accept and remove + subscriberClient.requestSubmission(sentSubmissionId).acceptSubmission(new IncorrectDataAuthenticationTag()); + + // check event log if accept event was sent and contains a problem + final Optional<EventLogEntry> acceptEvent = subscriberClient + .getEventLog(sentSubmission.getCaseId(), sentSubmission.getDestinationId()) + .stream().filter(e -> e.getEvent().equals(Event.ACCEPT)) + .findFirst(); + + assertTrue(acceptEvent.isPresent()); + assertThat(acceptEvent.get().getProblems(), is(not(empty()))); + + // second attempt to receive the submission should return an empty result + // since the submission is gone after being accepted + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> subscriberClient.requestSubmission(sentSubmissionId)); + assertThat(exception.getMessage(), containsString("Submission not found")); + } + + @Test + void testRejectEventViaSubscriber() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final SenderClient senderClient = ClientFactory.getSenderClient(config); + final var subscriberClient = ClientFactory.getSubscriberClient(config); + + final var submission = SendableSubmission.Builder() + .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") + .setJsonData("{ \"data\": \"Beispiel Fachdaten\" }") + .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "text/plain")) + .build(); + + final var sentSubmission = senderClient.send(submission); + + assertNotNull(sentSubmission); + + // When + final Optional<SubmissionForPickup> submissionForPickup = subscriberClient.getAvailableSubmissionsForDestination(submission.getDestinationId()) + .stream().filter(s -> s.getSubmissionId().equals(sentSubmission.getSubmissionId())) + .findFirst(); + + assertTrue(submissionForPickup.isPresent()); + + subscriberClient.rejectSubmission(submissionForPickup.get(), List.of(new InvalidEventLog())); + + // simulate some waiting time for e.g. persistence + await().atLeast(Duration.ofSeconds(2)); + + // Then + assertThat(senderClient.getStatusForSubmission(sentSubmission).getStatus(), is(SubmissionState.REJECTED)); + + // second attempt to receive and reject the submission should return an empty result since the submission is gone after being rejected + final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> subscriberClient.requestSubmission(sentSubmission.getSubmissionId())); + assertThat(exception.getMessage(), containsString("Submission not found")); + } + + @Test + void testReadEventLogFromSender() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + final SenderClient senderClient = ClientFactory.getSenderClient(config); + + final var submission = SendableSubmission.Builder() + .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") + .setJsonData("{ \"data\": \"Beispiel Fachdaten\" }") + .build(); + + final var sentSubmission = senderClient.send(submission); + + Assertions.assertNotNull(sentSubmission); + + // When + Awaitility.await().atMost(Duration.ofSeconds(30)).until(() -> + { + final UUID destinationId = sentSubmission.getDestinationId(); + final UUID caseId = sentSubmission.getCaseId(); + + final List<EventLogEntry> senderEventLog = senderClient.getEventLog(caseId, destinationId); + final List<Event> senderEvents = senderEventLog.stream().map(EventLogEntry::getEvent).collect(Collectors.toList()); + + // Then + return senderEvents.equals(List.of(Event.CREATE, Event.SUBMIT)); + }); + } + + @Test + void testReadEventLogFromSubscriber() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final SenderClient senderClient = ClientFactory.getSenderClient(config); + final SubscriberClient subscriberClient = ClientFactory.getSubscriberClient(config); + + final var submission = SendableSubmission.Builder() + .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") + .setJsonData("{ \"data\": \"Beispiel Fachdaten\" }") + .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text")) + .build(); + + final var sentSubmission = senderClient.send(submission); + + Assertions.assertNotNull(sentSubmission); + + final var receivedSubmission = subscriberClient + .requestSubmission(sentSubmission.getSubmissionId()); + + Assertions.assertNotNull(receivedSubmission); + + + // When + Awaitility.await().atMost(Duration.ofSeconds(30)).until(() -> + { + final UUID destinationId = receivedSubmission.getDestinationId(); + final UUID caseId = receivedSubmission.getCaseId(); + + final List<EventLogEntry> subscriberEventLog = subscriberClient.getEventLog(caseId, destinationId); + final List<Event> subscriberEvents = subscriberEventLog.stream().map(EventLogEntry::getEvent).collect(Collectors.toList()); + + // Then + return subscriberEvents.equals(List.of(Event.CREATE, Event.SUBMIT)); + }); + } + + @Test + void testReadSubmissionStatus() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final SenderClient senderClient = ClientFactory.getSenderClient(config); + + final var submission = SendableSubmission.Builder() + .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") + .setJsonData("{ \"data\": \"Beispiel Fachdaten\" }") + .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text")) + .build(); + + final var sentSubmission = ClientFactory.getSenderClient(config).send(submission); + + Assertions.assertNotNull(sentSubmission); + + Awaitility.await().atMost(Duration.ofSeconds(30)).until(() -> + { + // When + final SubmissionStatus statusForSubmission = senderClient.getStatusForSubmission(sentSubmission); + + // Then + return statusForSubmission.getStatus().equals(Event.SUBMIT.getState()); + }); + } + +} diff --git a/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/IntegrationTestBase.java b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/IntegrationTestBase.java new file mode 100644 index 0000000000000000000000000000000000000000..3e890f51f841946c7a79af91cd053041daa444bd --- /dev/null +++ b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/IntegrationTestBase.java @@ -0,0 +1,61 @@ +package dev.fitko.fitconnect.integrationtests; + +import dev.fitko.fitconnect.api.config.ApplicationConfig; +import dev.fitko.fitconnect.api.config.Environment; +import dev.fitko.fitconnect.api.config.EnvironmentName; +import dev.fitko.fitconnect.api.config.SenderConfig; +import dev.fitko.fitconnect.api.config.SubscriberConfig; +import dev.fitko.fitconnect.api.domain.model.event.problems.Problem; +import dev.fitko.fitconnect.client.SubscriberClient; +import dev.fitko.fitconnect.client.factory.ClientFactory; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static dev.fitko.fitconnect.api.domain.model.event.problems.Problem.SCHEMA_URL; + +public class IntegrationTestBase { + + private static final String authBaseUrl = "https://auth-testing.fit-connect.fitko.dev"; + private static final String routingBaseUrl = "https://routing-api-testing.fit-connect.fitko.dev"; + private static final String selfServicePortalUrl = "https://portal.auth-testing.fit-connect.fitko.dev"; + private static final String submissionBaseUrl = "https://submission-api-testing.fit-connect.fitko.dev"; + + public static void cleanupTestSubmissions() { + + final UUID destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID")); + final Problem problem = new Problem(SCHEMA_URL + "technical-error", "cleanup", "submission-cleanup", "other"); + final SubscriberClient subscriberClient = ClientFactory.getSubscriberClient(getConfigWithCredentialsFromEnvironment("TEST", true)); + + subscriberClient.getAvailableSubmissionsForDestination(destinationId).forEach((s -> { + try { + subscriberClient.rejectSubmission(s, List.of(problem)); + } catch (final Exception e) { + //continue + } + })); + } + + public static ApplicationConfig getConfigWithCredentialsFromEnvironment(final String environmentName, final boolean allowInsecurePublicKey) { + + final var sender = new SenderConfig(System.getenv("SENDER_CLIENT_ID"), System.getenv("SENDER_CLIENT_SECRET")); + + final var subscriber = SubscriberConfig.builder() + .clientId(System.getenv("SUBSCRIBER_CLIENT_ID")) + .clientSecret(System.getenv("SUBSCRIBER_CLIENT_SECRET")) + .privateDecryptionKeyPaths(List.of("src/test/resources/private_decryption_test_key.json")) + .privateSigningKeyPath("src/test/resources/private_test_signing_key.json") + .build(); + + final EnvironmentName envName = new EnvironmentName(environmentName); + final Environment env = new Environment(authBaseUrl, routingBaseUrl, submissionBaseUrl, selfServicePortalUrl, allowInsecurePublicKey); + + return ApplicationConfig.builder() + .senderConfig(sender) + .subscriberConfig(subscriber) + .environments(Map.of(envName, env)) + .activeEnvironment(envName) + .build(); + } +} diff --git a/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/RoutingIT.java b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/RoutingIT.java new file mode 100644 index 0000000000000000000000000000000000000000..1a69a264e1bfcce12b3ce4dbf5fd7b75576d67d6 --- /dev/null +++ b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/RoutingIT.java @@ -0,0 +1,80 @@ +package dev.fitko.fitconnect.integrationtests; + +import dev.fitko.fitconnect.integrationtests.condition.EnableIfEnvironmentVariablesAreSet; +import dev.fitko.fitconnect.api.config.ApplicationConfig; +import dev.fitko.fitconnect.api.domain.model.route.Area; +import dev.fitko.fitconnect.api.domain.model.route.Route; +import dev.fitko.fitconnect.client.RoutingClient; +import dev.fitko.fitconnect.client.factory.ClientFactory; +import dev.fitko.fitconnect.client.router.DestinationSearch; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.UUID; + +@EnableIfEnvironmentVariablesAreSet +public class RoutingIT extends IntegrationTestBase { + + @Test + void testFindDestinationsWithRegionalKey() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final RoutingClient routingClient = ClientFactory.getRoutingClient(config); + + final DestinationSearch search = DestinationSearch.Builder() + .withLeikaKey("99123456760610") + .withArs("064350014014") + .build(); + + // When + final List<Route> routes = routingClient.findDestinations(search); + + // Then + MatcherAssert.assertThat(routes, Matchers.hasSize(1)); + MatcherAssert.assertThat(routes.get(0).getDestinationId(), Matchers.is(UUID.fromString("d40e7b13-da98-4b09-9e16-bbd61ca81510"))); + } + + @Test + void testFindDestinationsWithAreaId() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final RoutingClient routingClient = ClientFactory.getRoutingClient(config); + + final DestinationSearch search = DestinationSearch.Builder() + .withLeikaKey("99123456760610") + .withAreaId("931") + .build(); + + // When + final List<Route> routes = routingClient.findDestinations(search); + + // Then + MatcherAssert.assertThat(routes, Matchers.hasSize(1)); + MatcherAssert.assertThat(routes.get(0).getDestinationId(), Matchers.is(UUID.fromString("d40e7b13-da98-4b09-9e16-bbd61ca81510"))); + } + + @Test + void testFindAreaWithMultipleSearchCriteria() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final RoutingClient routingClient = ClientFactory.getRoutingClient(config); + + // When + final List<Area> areas = routingClient.findAreas(List.of("Leip*", "04229"), 0, 10); + + // Then + MatcherAssert.assertThat(areas, Matchers.is(Matchers.not(Matchers.empty()))); + MatcherAssert.assertThat(areas.size(), Matchers.is(Matchers.lessThanOrEqualTo(10))); + Assertions.assertTrue(areas.stream().anyMatch(area -> area.getName().equals("Leipzig"))); + } + +} diff --git a/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/SenderClientIT.java b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/SenderClientIT.java new file mode 100644 index 0000000000000000000000000000000000000000..0be4adb0af94dbd8949bee321fead0bc93014419 --- /dev/null +++ b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/SenderClientIT.java @@ -0,0 +1,194 @@ +package dev.fitko.fitconnect.integrationtests; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.nimbusds.jose.jwk.RSAKey; +import dev.fitko.fitconnect.api.config.ApplicationConfig; +import dev.fitko.fitconnect.api.config.SchemaConfig; +import dev.fitko.fitconnect.api.domain.model.metadata.ContentStructure; +import dev.fitko.fitconnect.api.domain.model.metadata.Hash; +import dev.fitko.fitconnect.api.domain.model.metadata.Metadata; +import dev.fitko.fitconnect.api.domain.model.metadata.PublicServiceType; +import dev.fitko.fitconnect.api.domain.model.metadata.SignatureType; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment; +import dev.fitko.fitconnect.api.domain.model.metadata.attachment.Purpose; +import dev.fitko.fitconnect.api.domain.model.metadata.data.Data; +import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType; +import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema; +import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel; +import dev.fitko.fitconnect.api.services.crypto.CryptoService; +import dev.fitko.fitconnect.client.SenderClient; +import dev.fitko.fitconnect.client.factory.ClientFactory; +import dev.fitko.fitconnect.client.sender.model.Attachment; +import dev.fitko.fitconnect.client.sender.model.EncryptedAttachment; +import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission; +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; +import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission; +import dev.fitko.fitconnect.core.crypto.HashService; +import dev.fitko.fitconnect.core.crypto.JWECryptoService; +import dev.fitko.fitconnect.integrationtests.condition.EnableIfEnvironmentVariablesAreSet; +import org.apache.tika.mime.MimeTypes; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.text.ParseException; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@EnableIfEnvironmentVariablesAreSet +public class SenderClientIT extends IntegrationTestBase { + + @BeforeEach + public void cleanup(){ + cleanupTestSubmissions(); + } + + @Test + void testSendAndConfirmCycle() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final var submission = SendableSubmission.Builder() + .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") + .setJsonData("{ \"data\": \"Beispiel Fachdaten\" }") + .addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "text/plain")) + .addAttachment(Attachment.fromByteArray("attachment data".getBytes(), "text/plain")) + .addAttachment(Attachment.fromString("attachment data", "text/plain")) + .setReplyChannel(ReplyChannel.fromDeMail("test@mail.org")) + .build(); + + final var sentSubmission = ClientFactory.getSenderClient(config).send(submission); + + assertNotNull(sentSubmission); + + // When + final ReceivedSubmission receivedSubmission = + ClientFactory.getSubscriberClient(config) + .requestSubmission(sentSubmission.getSubmissionId()); + + // Then + assertNotNull(receivedSubmission); + assertThat(receivedSubmission.getDataAsString(), is("{ \"data\": \"Beispiel Fachdaten\" }")); + assertThat(receivedSubmission.getDataSchemaUri(), is(URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json"))); + assertThat(receivedSubmission.getDataMimeType(), is("application/json")); + assertThat(receivedSubmission.getAttachments(), hasSize(3)); + assertThat(receivedSubmission.getMetadata().getReplyChannel(), is(ReplyChannel.fromDeMail("test@mail.org"))); + assertThat(new String(receivedSubmission.getAttachments().get(0).getDataAsBytes()), is("Test attachment")); + } + + @Test + void testSendAndConfirmCycleWithEncryptedData() throws ParseException, IOException { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final UUID destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID")); + + final CryptoService cryptoService = new JWECryptoService(new HashService()); + final SenderClient client = ClientFactory.getSenderClient(config); + + final String publicKey = client.getPublicKeyForDestination(destinationId); + + final RSAKey encryptionKey = RSAKey.parse(publicKey); + + final String jsonData = "{ \"data\": \"Beispiel Fachdaten\" }"; + final File attachmentFile = new File("src/test/resources/attachment.txt"); + final String attachmentData = Files.readString(attachmentFile.toPath()); + + final String encryptedData = cryptoService.encryptBytes(encryptionKey, jsonData.getBytes(StandardCharsets.UTF_8)); + final String encryptedAttachment = cryptoService.encryptBytes(encryptionKey, attachmentData.getBytes(StandardCharsets.UTF_8)); + + final var submissionSchema = new SubmissionSchema(); + submissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")); + submissionSchema.setMimeType(MimeType.APPLICATION_JSON); + + final var dataHash = new Hash(); + dataHash.setSignatureType(SignatureType.SHA_512); + dataHash.setContent(cryptoService.hashBytes(jsonData.getBytes(StandardCharsets.UTF_8))); + + final var data = new Data(); + data.setHash(dataHash); + data.setSubmissionSchema(submissionSchema); + + final var publicServiceType = new PublicServiceType(); + publicServiceType.setName("Test Service"); + publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000"); + + final var attachment = new ApiAttachment(); + attachment.setAttachmentId(UUID.randomUUID()); + attachment.setPurpose(Purpose.ATTACHMENT); + attachment.setFilename(attachmentFile.getName()); + attachment.setMimeType(MimeTypes.PLAIN_TEXT); + + final var attachmentHash = new Hash(); + attachmentHash.setContent(cryptoService.hashBytes(attachmentData.getBytes(StandardCharsets.UTF_8))); + attachmentHash.setSignatureType(SignatureType.SHA_512); + attachment.setHash(attachmentHash); + + final var contentStructure = new ContentStructure(); + contentStructure.setAttachments(List.of(attachment)); + contentStructure.setData(data); + + final var metadata = new Metadata(); + metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString()); + metadata.setContentStructure(contentStructure); + metadata.setPublicServiceType(publicServiceType); + + final String encryptedMetadata = cryptoService.encryptBytes(encryptionKey, new ObjectMapper().writeValueAsBytes(metadata)); + + // When + final var submission = SendableEncryptedSubmission.Builder() + .setDestination(destinationId) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") + .setEncryptedMetadata(encryptedMetadata) + .setEncryptedData(encryptedData) + .addEncryptedAttachment(new EncryptedAttachment(attachment.getAttachmentId(), encryptedAttachment)) + .build(); + + final var sentSubmission = ClientFactory.getSenderClient(config).send(submission); + + Assertions.assertNotNull(sentSubmission); + + final ReceivedSubmission receivedSubmission = ClientFactory.getSubscriberClient(config).requestSubmission(sentSubmission.getSubmissionId()); + + // Then + Assertions.assertNotNull(receivedSubmission); + MatcherAssert.assertThat(receivedSubmission.getDataAsString(), Matchers.is(jsonData)); + MatcherAssert.assertThat(receivedSubmission.getDataMimeType(), Matchers.is("application/json")); + MatcherAssert.assertThat(receivedSubmission.getAttachments().get(0).getDataAString(StandardCharsets.UTF_8), Matchers.is("Test attachment")); + } + + @Test + void testAbortedSendSubmissionWithKeyValidationNotSilent() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("PROD", false); + + // When + final var submission = SendableSubmission.Builder() + .setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID"))) + .setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service") + .setJsonData("{ \"data\": \"Beispiel Fachdaten\" }") + .build(); + + final var sentSubmission = ClientFactory.getSenderClient(config).send(submission); + + // Then + Assertions.assertNull(sentSubmission); + } +} diff --git a/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/SubscriberClientIT.java b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/SubscriberClientIT.java new file mode 100644 index 0000000000000000000000000000000000000000..2c8dd6bda70482d4c11fb259538759da9b200387 --- /dev/null +++ b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/SubscriberClientIT.java @@ -0,0 +1,113 @@ +package dev.fitko.fitconnect.integrationtests; + +import dev.fitko.fitconnect.api.config.ApplicationConfig; +import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import dev.fitko.fitconnect.client.factory.ClientFactory; +import dev.fitko.fitconnect.client.sender.model.Attachment; +import dev.fitko.fitconnect.client.sender.model.SendableSubmission; +import dev.fitko.fitconnect.client.subscriber.ReceivedSubmission; +import dev.fitko.fitconnect.integrationtests.condition.EnableIfEnvironmentVariablesAreSet; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@EnableIfEnvironmentVariablesAreSet +public class SubscriberClientIT extends IntegrationTestBase { + + @BeforeEach + public void cleanup(){ + cleanupTestSubmissions(); + } + + @Test + void testListSubmissionsForDestination() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final var senderClient = ClientFactory.getSenderClient(config); + final var subscriberClient = ClientFactory.getSubscriberClient(config); + + final UUID destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID")); + final String leikaKey = "urn:de:fim:leika:leistung:99400048079000"; + final String serviceName = "Test Service"; + + final var submissionOne = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType(leikaKey, serviceName) + .setJsonData("{ \"data\": \"Beispiel Fachdaten 1\" }") + .build(); + + final var submissionTwo = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType(leikaKey, serviceName) + .setJsonData("{ \"data\": \"Beispiel Fachdaten 2\" }") + .build(); + + final var sentSubmissionOne = senderClient.send(submissionOne); + final var sentSubmissionTwo = senderClient.send(submissionTwo); + + assertNotNull(sentSubmissionOne); + assertNotNull(sentSubmissionTwo); + + // When + final Set<SubmissionForPickup> submissions = subscriberClient.getAvailableSubmissionsForDestination(destinationId); + + // Then + Assertions.assertFalse(submissions.isEmpty()); + + final List<UUID> submissionIds = submissions.stream().map(SubmissionForPickup::getSubmissionId).collect(Collectors.toList()); + + assertThat(submissionIds, Matchers.hasItems(sentSubmissionOne.getSubmissionId(), sentSubmissionTwo.getSubmissionId())); + + // remove by confirming + subscriberClient.requestSubmission(sentSubmissionOne.getSubmissionId()).acceptSubmission(); + subscriberClient.requestSubmission(sentSubmissionTwo.getSubmissionId()).acceptSubmission(); + } + + @Test + void testReceiveSingleSubmission() { + + // Given + final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true); + + final var senderClient = ClientFactory.getSenderClient(config); + final var subscriberClient = ClientFactory.getSubscriberClient(config); + + final UUID destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID")); + final String leikaKey = "urn:de:fim:leika:leistung:99400048079000"; + final String serviceName = "Test Service"; + + final var submission = SendableSubmission.Builder() + .setDestination(destinationId) + .setServiceType(leikaKey, serviceName) + .setJsonData("{ \"data\": \"Beispiel Fachdaten 1\" }") + .addAttachment(Attachment.fromString("foo", "plain/text")) + .build(); + + final var sentSubmission = senderClient.send(submission); + + assertNotNull(sentSubmission); + + // When + final ReceivedSubmission receivedSubmission = subscriberClient.requestSubmission(sentSubmission.getSubmissionId()); + + // Then + assertNotNull(receivedSubmission); + assertThat(receivedSubmission.getAttachments(), hasSize(1)); + assertThat(receivedSubmission.getAttachments().get(0).getDataAString(StandardCharsets.UTF_8), is("foo")); + assertThat(receivedSubmission.getDataAsString(), is("{ \"data\": \"Beispiel Fachdaten 1\" }")); + } +} diff --git a/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/condition/EnableIfEnvironmentVariablesAreSet.java b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/condition/EnableIfEnvironmentVariablesAreSet.java new file mode 100644 index 0000000000000000000000000000000000000000..60d244c34a571020b971662e4bba180e3131c67f --- /dev/null +++ b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/condition/EnableIfEnvironmentVariablesAreSet.java @@ -0,0 +1,16 @@ +package dev.fitko.fitconnect.integrationtests.condition; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ExtendWith(EnableIfEnvironmentVariablesAreSetCondition.class) +public @interface EnableIfEnvironmentVariablesAreSet { +} diff --git a/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/condition/EnableIfEnvironmentVariablesAreSetCondition.java b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/condition/EnableIfEnvironmentVariablesAreSetCondition.java new file mode 100644 index 0000000000000000000000000000000000000000..117755f10985a2629f803356f0ae2ad552413307 --- /dev/null +++ b/integration-tests/src/test/java/dev/fitko/fitconnect/integrationtests/condition/EnableIfEnvironmentVariablesAreSetCondition.java @@ -0,0 +1,27 @@ +package dev.fitko.fitconnect.integrationtests.condition; + +import dev.fitko.fitconnect.core.util.Strings; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.extension.ConditionEvaluationResult.*; + +public class EnableIfEnvironmentVariablesAreSetCondition implements ExecutionCondition { + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(final ExtensionContext extensionContext) { + return allVariablesSet() ? enabled("") : disabled("environment variables are not set"); + } + + private boolean allVariablesSet() { + final String destinationId = System.getenv("TEST_DESTINATION_ID"); + final String senderClientId = System.getenv("SENDER_CLIENT_ID"); + final String senderClientSecret = System.getenv("SENDER_CLIENT_SECRET"); + final String subscriberClientId = System.getenv("SUBSCRIBER_CLIENT_ID"); + final String subscriberClientSecret = System.getenv("SUBSCRIBER_CLIENT_SECRET"); + return Stream.of(destinationId, senderClientId, senderClientSecret, subscriberClientId, subscriberClientSecret).allMatch(Strings::isNotNullOrEmpty); + } +} diff --git a/integration-tests/src/test/resources/attachment.txt b/integration-tests/src/test/resources/attachment.txt new file mode 100644 index 0000000000000000000000000000000000000000..ab85fcdb1b9acda0a1e8e48bf2b80423716ccf6e --- /dev/null +++ b/integration-tests/src/test/resources/attachment.txt @@ -0,0 +1 @@ +Test attachment \ No newline at end of file diff --git a/integration-tests/src/test/resources/private_decryption_test_key.json b/integration-tests/src/test/resources/private_decryption_test_key.json new file mode 100644 index 0000000000000000000000000000000000000000..9be02d7261d9283ac546d8e3eaee54deb84693e9 --- /dev/null +++ b/integration-tests/src/test/resources/private_decryption_test_key.json @@ -0,0 +1 @@ +{"alg":"RSA-OAEP-256","d":"UuZ7DtiI46pxCeACoP6cDxWBT2PJea6Td5eTU2sm-jkGH1O_ShgBtqHI6KwOLenO0Z1GlHoVNzIx9NeazUpVdN1Oan42ximA6LXqP6GaaWD7qJmzuSER968F4StW7hv5aI1N35mFLESY2ENISfhmRGBsCalRrQVFU5lRwjH22m_JTdeMeRbCCZHxynH2GcOwei8lZgWTiu3cIz7giEwc3GrZHZMxhdBLBCJ3gxDer9O_V0UCr7W7VpLpFuaOZmKaB2io-Rgz5W71IIo-09IghWnV5VZWpXr1Dl1_UbhE9UK_N6gQ9HcFopBQR6huD2svUO2aVig3RLO1KG8AL6vQsp8uIa_7_TuVYnuo7S_zVRlungjs2UzABjRqsdUED9C-FmwpnbKcHA1V99U8Ag22VHXm0zcT58M09-_yUl9s4VG6y-yQEhfxTjL0LMtSL8XScRwf38mpgX54LFGOeImo8d6jFGbNMzMViStejmXU8nCBeRmEtHpDU-GxJSPhO0ZJNgNWH_RU-tmujodXxXvyp7yA3MFqvcKxUQGf73lz_y10lLSmAJSlG-3KYyHY1N5AlXF4m0N3yENcim3VPVZarpOgHfEF3gg3slthAFZqTeY2WKMBp2O_QBvyf3mylVKHECzY4XzScBiPA9qlbeRDoa-NxcQwT2bS_1cyk8_IYG8","dp":"-sRGGYORpYDRglqvK9RX6FCQUpjCwbyAeq3yU4sEnC2XWt37-bw-p0KzSs7OuTTSPNH5I9HrfAzVHPzGXWE_CJmkdvkGqLkmvFTFiuGY1yk3psYvLwzDPJjyKUzdI-GMdioL_IyjEfna2TxdiYLyzGCDtGJxrSVyunc2xcMx_k1u_HQcylog00P61kCnUVtGsDqMbYsShCQaZXwQK3wPL6KkjgVLtEYYYeP8NiJRL_XP8SCD4ebU_cJMLDWgq1DgxpfqIBz0fCNe0ty9LwEhMhmP6OpmU5mRAGzXNKOp_lYYEVmvRoEwcRyFdM1W4N61eu8EYGyfzTrH1gfG8FMLfQ","dq":"uC9tAhQI7vRE2isCeoxY3YgJd2RBOdRrIjowUoS7WuEho_aLKWgoJDSzZoXad9pindjaSRj9CRKZ7oRW1Dp0NQwTQdUsyZSZt4cB5yxg50ty5IE81XDX0pgyRCJOXNpKxSZYtcBUzqlId4dCRd2codqaKJkP77K0TmWlQve_CoCPuaSP4CGqt8GpQj1fKzI_NYYqNVk-1jSAa9PZ2E-gW0iJwQoUNkDRTBgIVLx1qu7aYieYeif8oTFjKGKkP3EXCTGXKSdLd729-37NgWR0YktLFQsWfiNHcy1_-eWI6HKc8onOv5IuchKQ9nmQIE1mQUZBATCtx-vdzQqF_alTKw","e":"AQAB","key_ops":["unwrapKey"],"kid":"jsMQHFiA2uMGtiG3Ro8RJnYdEhp5W5KjGW-Vcf3-YMk","kty":"RSA","n":"zEi7NpGkzGyG2-PDWTy72hvti-pGBZLidxbk10_fenjzOavKcO5yGXSCfX_Xl0-WYaJfb7Kz6CRRtnwBGx8mrsftodtxt3kdrFnf6chQ2JJ5dmnz_ErIbHjHaFlxXvEqv3ivqIQSZvuns8QJip8RGQ63g7nmPDyvBcvLMFUtnXy7Y6rzUI4HO2eeg8htMPCWN5-L7Ol5_IT11NuZK6J3_UN1B0P7fFGdVMhsNR5D_jHOD-U4jZQijyVgzIXxwN_vnf90e5_ZUgsLypwh2DT7qzhqES8hzIIk_Cjs5mCUwDjfiBh0g7LNjXaj0b7rAn2X6yPuMK6zYi_FXJYSCQ7LH3THi_h5r2--xQ2h40He23JjfNpEGu98uMgB_dvzH0Dco8OKh4Aj2wjLpZVFOS3Zpu-WUalP-ecEiZ6nzmCMoIpWM0U4t4tpGwC1X-MWNj75zYLI8-yd3ELVBx3PQSzDukGOVLBdtpF0txf7Np0i9RFRW-nxf3xPVFCbZYbc87GMZy1CeDT2NBJJHtQaPKBismnEXtXtiM7aJDRM74F5JXjDVR62eHGxFCy46oyI_NS3FmvkoVtV8hvbXBRMSU8sNeOrVKnbWpfjBGFGD8VbssKoAAdMfn6usOiiH71SBg-L9KoBwdRfu7XFE-WbSA2d03hMvEPJWk9srh5qGdtQ_QE","p":"_RRumCEIUoM1gipAOIeQZ_td0E2qx20bFHArBf1V7ev4v4S22tt1Bt9oa20KPfA6mokycaB8MBCX7LTRvBT1XwSh4sU2TptuibElrarQvFoJIVGqugqG4GxsBpZpy-Dvx9NJtMTjiiGXSn87-_8iSWrPlc4Vdbgb8S9Vz2x_Mp-uSwZaFOO3Gx4miD29y1ugE26hFF0RdgBDJnJEk71W9NG2D_MbnBBa3DrPesdU8rX08RurpV8C1TVUgelDxUBQUxrRXRaHOPbrwPBBRDOgzNlFiuzUdkw6qwlfhp9W1tn3j7uAnqMHxnaPhJJ51tFjd_7MgSawmmM415c4NWa5xw","q":"zqQpmsvtjeU486C2DZ_NjsDHanYXgfxiY3X25XpM0ojLrtIQWKUaFxlggJGSQzQ6QJqB-5sJu9EEE4DGZ3UYoakY_hzxv0uHOHrI20ohFgF5K3YQdqGRhC3Bo66ezpmD3FT1EPXQM2OLEAi82lUb7UrbP_DSU0BmYOd6kPd1a3FD2qm0DjA15IYW5vUHNisx79W3kt_8s7ZICjiVpvJTSa1TVIX3wPM0qLHOWbHEOswLENC4fZ7EFYkNr3MvtAOxLcJ_mi4zzhAkyGdKJbTQXt6bhD_gWL5K1iOFM5Vg_NRTjUMqndDK33KDYZhC-MexSR-IaN7MbP1Y787uFL5S9w","qi":"DHzvGmAC4OZIfvDBkqXrvrvBeqURfioiEN72SL6oPcBqof899UL50BVohU0a2IeydwLSSff1i0U0jfX8ss_YKnCw2MxH3eNdQ-JXAQp7sefYLiFKPlGQnaxCNL0Z3kDatPhJnfTHaJA_UKtuPtcIINK5lJH80Iqqg3rl1p5G84ujbIghcgJXGoRGjJfkpUQgarTwPzdScR8Xo-YmN4dNav2X76s5toTJbSc4wLh3z_vdgL4GTGv9jzRKBPJcv07SDOFz92VA2r2DiIPQxg1eAKq8MPwEQ_bkaqh6Sk57_-JT8CRN0vfZbHw_3jCc8ijqRQE-4HaKcVCAbgnTzgr8cw"} \ No newline at end of file diff --git a/integration-tests/src/test/resources/private_test_signing_key.json b/integration-tests/src/test/resources/private_test_signing_key.json new file mode 100644 index 0000000000000000000000000000000000000000..e5394e578e8ed25d5caa1ea67f1ec1f00c3b24ab --- /dev/null +++ b/integration-tests/src/test/resources/private_test_signing_key.json @@ -0,0 +1 @@ +{"alg":"PS512","d":"Dr6lFZFGBjdM3sC7S80ZNpaT3MPeGE4jWls258SIBf_OOcRXU15yclFLgkstAELARUh6QZ92OkVxz61Ah0V13KTCewf8jp38vPji9KP5oudwklduXscApQtwBzornIdUv70BNe8bkH21LyJqNtzYHeZ5pQr-N-CrJW5ja8fZmUCXLTbGUob7VOkiqg6erns-xiA0BezKzS-3LT4URH7j7WVz8F6XjuPwa1lNonc2YruYXf9lD1raI0hMHkEPw3ZJM2lxNGiSFOjdzAVy1QaUgIBqKLnx5eQpUMaVjxcFz0WfsB5Ac6Y7et4jYSIBFySHO4IHVWqlJanXDBvNeV3vi-BFxVGRZXItmHOgU4bxlGKtr_JSxS6W4mA3ItlfEkN7_43sixn1VbQRk-AhgQdKsIgq7lBGV5aAw8bYyrX3H-D5SFp8NUtO8VaeKg9JHk-3qee8R5sgzwznSNlfP93Mc5la3eI9iBJzj0jpycg3q7n2sdvhgLUvFQMSBFFXnTziiRZpgO9SnqH2dZmmyeGIDbwTwwxmqM35IK_ZLykXSC4lQmrdAQgNPuCkr8Ta0xeKqDR7TNnKDjb4xafrX3E0zANnvUYODjaclf24Jllz4L9YPeQUKnLdWM3G4eSj0bBwagCuCYIJEMHnx1-ioWDnYHn2mz2KuwlpwS3VSDRo7CE","dp":"oj5brYcZBIe_jicVOsC6ehg36qKDycudEJakXGQcSquZBqqtpBUwpGIs2nFJYKKvyNIcy0WNpmI1fkiUGvartJxNIH9HgeAwlrP6I13SpsE3bl8ePGIDIsmyoVoXA9CkVtq65hSvePCBt_RMmy5f06bdZ7aaawi816xmS7_ifX9F78SI1ngGUwOePduXNsZPLYRFqCg4ihpSy-0EvkEFJRdFYsTE85Y4mYyniIhcmTa1x8N0KDwRak8vC__2rtxxq2E_cda6qySIwLn8ZojtRr30-epJM1mbT-dmbD1Kd7sGrk3UYeIEskLoyduDCnhCCHT6Z7c1B4TWpMljWFhKNw","dq":"tHs21oBS0Uo0fsCf24IplzWVc6DQP7GnV5oLk4WZqkHHat0zaUbFuFpnVt3Kz2aT-Q06936WvFIzoY1qjHKV8A7KtGFTz2VV3nw_760E2ZvvbETqSrppjP9OD7A0Dee8-kluMXqnZ-8d7u7sUXjysTmyuImRa25DdWfPBFxDVvhutfwbY0OuN8sV9e6iWdnNn1Gfxmnx78mMOcMATC2RiBEaxrHSEQx2zABJ5T-VAmWD-wePDQu5dyps0fbwiIscifx3LshFqFfEuI0rm-bjJFerqNO0ufZIXlNo7Q7eTuOf4s1aTEF7Hj5XqDaSDcasEvlZJPuiEzspXHNtKTNVyQ","e":"AQAB","key_ops":["sign"],"kid":"7lDr-7iUCRWaBUFXLoJr5V3t2iYO6YhPLFSN6P4vwZw","kty":"RSA","n":"q6Np9NSaNqAD6bOFQNvxQNTqDaEBXznqA8EgPOe1iwqmfoZ2B1sdaKlqeUGJYwYeAyBDZGLv-3AibUTaGer0AB6EOCHPvNGueB3TU4bJqDMV0OR6JM2AUghqVzjJGmp1osYdDZCS8WqRi2UY4pVHg4c1iuWAoXYWpiD1o-8lynGyPHlLcxmjmUHpxgmsYk3nJPd1UfmqyXVr_bFQPIdXCz-g9UsF-oVJ5ykPLTyDxYYY_YikXXvWz0ZzyyAxrYS2sAA1vVRqd_6rAppdyDnoftBgcyLzK3kBxT9Pd8vXLkNuZB-sa51hRDBNFcxDtmk8ekhWL-zWRZ1zrNPIlDUmvSr409TTgh071Nx7gBffjAD5ByCXJ8pPY2DQRqDdU_k6SriNrMKO-Ew6qS9Za-g2jy-dCCZ2eTZxkFgIN7hNY6TFfhxQ6SP7qEFSjWUyGpFuNSt7e9ncMdoT9iqyxrCHTWwm0Vhgoh4n3QSlc1nibhrGERVJXAZbgGs5_RN1AnSu1kq_kkZR2iZ3kqGxJVlGQYQSL5X0gxecp4ldaNBXWkI-uwnw812yMbi_J-8YgYXbZkBGp7WXuW_T9Wdrmzi_X_iWJz5dReq185FBsqcI45TeacCIawmz_LupauIskSIBdysVVvcc_NSyJuWPh2FXq1qXyFdzzzWib0yniK2ZBz8","p":"8mbgyzSXmJbIG3HsM_nYIfVa4-_QK1LZKXmxnZ4z7CIgrwLJHbRnTtUzE0EKylxjQd8PEi5y74Aka4lC34Db4FJed7NWUQ--HaOSrLCKsTHM6JJBg1S4PQDvq8Eiw9hupjfQnZ5fDlbSTN-AiWwYl-rbLvmHTr3M78bvPtaVKrOGxGYWoGR0k3pEfUDN6TaPsfTlGp4ghUQqOD9EYJAB_yCEADkBWaetf-20UBgHJw6RHHtB-aPVntshMl2RciqST0hjyrG2OylfFUPi2I_G2eFJ8sVoBrFfzuY5UehnKiavGiGSvC5gftnVEAKYsf8gpTm9z6bOOm4dwwlSLShTJw","q":"tURN08A-1xTJxADLfRVLo_112n7ODku0ugsNZWA-lQoz9BNvwwbHfagIcpvT8Hw8tr85aTDXONTDJ320D-q63zl_Uuyz24zZ-Mgjq1tijJIuocEnFdncHb27zZqNmKOHqP45hH5QhZo3WTyBIycgawKv6rS_xP50Lho7a9uDXXQVAaOhQqO8fNdPxjS0jBMAeqbVhhTxgLTD187LqlMnonM4NvFVjHDIhiNe_438N8w1H0UbefqekC1GCM9_4dpRbRyfz-pMcyCyNVLWvHTUMc3elY6yTMXhFG1cJVUQZuOsdhPE9FXiUOjnXlOAk3vDqvNnMWhldwfSPQY_hNhaKQ","qi":"rXl32BT2a4x2to4LKfDgM2D5xJlh-4bPpxIyivt-adb63uf6NzzZ1EFNG5j1oveHqDIVSHaWhwIJMjQNLHoVOoIo6ZQWWTlkbRy0Do99bMkJnJjsPEPpEtiyT_J6OrNrBclQ-3uTPYoylHJddwUQAJfOu6DuAnUMz4QfRlNpeDfte4WGN7Q6vVbkagwtW4AA0IXGTD1r3mTWd66qu2J5v8EUS2WoNbWZEehlSOuwnGgNN9hnnOjkXtZtUl7hpwp2ONp0aXdMmRzpx8yYQ1eNhYX_PxaIOKqsCUiDf5DwVz5-Y1tLScs7OM8rkQbKXLbNidfNVNvq78BBfKI0emiCrw"} \ No newline at end of file diff --git a/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-14.pem b/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-14.pem new file mode 100644 index 0000000000000000000000000000000000000000..f4182592f83967c0c55b74ad915d00093675fd56 --- /dev/null +++ b/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-14.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEhjCCA26gAwIBAgIFAMZqKTUwDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMC +REUxGTAXBgNVBAoTEFBLSS0xLVZlcndhbHR1bmcxHDAaBgNVBAMTE1BDQS0xLVZl +cndhbHR1bmctMTQwHhcNMTMxMjAxMDAwMDAwWhcNMjMxMjMxMjM1OTU5WjBGMQsw +CQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEcMBoGA1UEAxMT +UENBLTEtVmVyd2FsdHVuZy0xNDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgC +ggEBAIJoqiA4T2iN/qq+0g/xdJ1rpsB/yozONjC0mmlSeBoBKtArrxg51GYFl/Ep +WzEF3QffcE5jg4xEgcNk8CXJDKPKHAldLsYEohx5So8EJsuGRLrjkt6SC5Kf1+aM +Vq7mt9qUPIuvloaQWiVkNvDFNSeam+K0I/zjK++0xHuQRACIaUbtmShuahKHlJR3 +DUM6CT9uWQB6FWv+qRAo6xsmHP/bYSMAv5/Z0bOnLZ6cruZ+QPGPksP0VX9M2pXY +cecYAlbYzuNYWAUhmx1P/ZhbBa0SKhDTO+JpHeCnKRiLt7MKPnUPyAtib7600CsI +TvFAjem9JanXKV0wK4sIsAMvg00CARGjggF7MIIBdzAPBgNVHRMBAf8EBTADAQH/ +MD8GA1UdIAQ4MDYwNAYLKwYBBAG9dAECAQMwJTAjBggrBgEFBQcCARYXaHR0cHM6 +Ly93d3cuYnNpLmJ1bmQuZGUwgbwGA1UdHwSBtDCBsTBioGCgXoZcbGRhcDovL3g1 +MDAuYnVuZC5kZS9DTj1QQ0EtMS1WZXJ3YWx0dW5nLTE0LE89UEtJLTEtVmVyd2Fs +dHVuZyxDPURFP2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3QwS6BJoEeGRWh0dHA6 +Ly94NTAwLmJ1bmQuZGUvY2dpLWJpbi9zaG93X2F0dHI/Y249UENBLTEtVmVyd2Fs +dHVuZy0xNCZhdHRyPWNybDAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBPzjBBU +Oufi9p0vosZp9r10O+QYMDUGA1UdEQQuMCyBEVYtUEtJQGJzaS5idW5kLmRlhhdo +dHRwczovL3d3dy5ic2kuYnVuZC5kZTANBgkqhkiG9w0BAQsFAAOCAQEAFAHRjPm6 +D4Om2S5P1Gcya4W+AUr5bbMFxTvi+Jl1odoIDtBkKkYQ0rv6HOx8kzBaOzwM7InU +8/Cp3P4yHpPL6dSBgtopqkZ5xDCRoeLIzRtwKzctev2sQi9UgXu8HOW4N8A+abNB +dEj5ehviHztZXlHuhfXAEQA6Q/Gc0XR5CWd5yQkirWFFaIhtINHs3UckH8dsC+Se +5AVpq7mG+DtOMtaMQRTg3ElBBdNivQ0jRz4hIur1B4TrhNNZZxUhkzTjAZ7EoA6w +2nZwPs29xgPt39Qf+3s/EfdoWzrTflrJ8tlBBueJTkI+PXIufURzDC40ciFm8g2Q +XJpE4ye1vXCZgg== +-----END CERTIFICATE----- diff --git a/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-15.pem b/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-15.pem new file mode 100644 index 0000000000000000000000000000000000000000..1a2ced199c3faf6a95690f44acb3dfa381a33f43 --- /dev/null +++ b/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-15.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEiDCCA3CgAwIBAgIFAKSHzIkwDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMC +REUxGTAXBgNVBAoTEFBLSS0xLVZlcndhbHR1bmcxHDAaBgNVBAMTE1BDQS0xLVZl +cndhbHR1bmctMTUwHhcNMTUwODE4MDkwMTI4WhcNMjUwODE4MjM1OTU5WjBGMQsw +CQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEcMBoGA1UEAxMT +UENBLTEtVmVyd2FsdHVuZy0xNTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAJ7vMHkXf9JYOROTvOez3qM8c47Lv/wqIkauT0+m9IuPG/DSnzv8cTNqaOwH +Zt3ga9dwiL5GMbfIDfyx49j56HBPQafS8E8N6v5XOHYemzrmTeD6e09tVsugoaEt +pSN1VPNF3JKQM7vyl2IxWakSVgBtWffNrV1/X4199jDXrInsyNxMiyG20RLMEY3D +BwUVyiK/yJPoWob3vIF4gSxode/i1h6IvsS//sJYll85BGPivNtRQA20JkcRZc/l +wh+B+6WFIhEmNpoR5qN8nPVuWw7u2PEl9aptJgIUivSGuVueu9aikfQRiBZ0AqwI +OGg7PrLG3rg8OOrqzr4z38YxL28CAwEAAaOCAXswggF3MA8GA1UdEwEB/wQFMAMB +Af8wPwYDVR0gBDgwNjA0BgsrBgEEAb10AQIBAzAlMCMGCCsGAQUFBwIBFhdodHRw +czovL3d3dy5ic2kuYnVuZC5kZTCBvAYDVR0fBIG0MIGxMGKgYKBehlxsZGFwOi8v +eDUwMC5idW5kLmRlL0NOPVBDQS0xLVZlcndhbHR1bmctMTUsTz1QS0ktMS1WZXJ3 +YWx0dW5nLEM9REU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDBLoEmgR4ZFaHR0 +cDovL3g1MDAuYnVuZC5kZS9jZ2ktYmluL3Nob3dfYXR0cj9jbj1QQ0EtMS1WZXJ3 +YWx0dW5nLTE1JmF0dHI9Y3JsMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU1ikn +WlJeLd3JZ+o81or280pnKTwwNQYDVR0RBC4wLIERVi1QS0lAYnNpLmJ1bmQuZGWG +F2h0dHBzOi8vd3d3LmJzaS5idW5kLmRlMA0GCSqGSIb3DQEBCwUAA4IBAQBdqWrx +75QWqRUwsDyHiKQle99K7PgqnVY5YR9SOswl7MT7jzK7yGfdGK5gQzyHDpGsoPJ/ +uIR2IkMb/pFFDA5D+Csw9mComooqP0n9wQCx64B1Q7o5DBvWftf4SCElTkbzSnR0 +qKqcP6U0I3If7OvPIgCiV3fM2EqEqAsFwvMtsd5gymLloR2LzUSCTdQbgq+bleR+ +G7mzDLw2QUgeVCP58RsSYECAPnRcY1qP9r0zuw2vILQuWZrBs5SY8ml6an730wmV +3kUftPb2r8QDq2ZCRbNTcpuV5KYnXykEiUKPtBbyLdcV/LCPLk70zpe1dzAplr3X +GFI0v+/UY+SXm8fI +-----END CERTIFICATE----- diff --git a/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-17.pem b/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-17.pem new file mode 100644 index 0000000000000000000000000000000000000000..7bf9bd30ec484578e509afb36cb5f7a851dd60be --- /dev/null +++ b/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-17.pem @@ -0,0 +1,37 @@ +-----BEGIN CERTIFICATE----- +MIIGiDCCBHCgAwIBAgIFAKdNgZUwDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMC +REUxGTAXBgNVBAoTEFBLSS0xLVZlcndhbHR1bmcxHDAaBgNVBAMTE1BDQS0xLVZl +cndhbHR1bmctMTcwHhcNMTYxMjAxMDAwMDAwWhcNMjYxMjMxMjM1OTU5WjBGMQsw +CQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEcMBoGA1UEAxMT +UENBLTEtVmVyd2FsdHVuZy0xNzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAJYQHveQAaTZlo4uZqQB4E7NuiMFngaOwJHGL7cpAOdFYYe4pV828HWJkDVh +zKkeZbM229Weqs8DlPTIX34Ec/XPstJY3cQX4r93WeKoA1SMt2KX0CoPOAFz5BaG +C9KG+GSAwc052FGjxCr+Uf0NyhPHFCWoH3hN+H9rNrLV+CnKDWVQB+LjuZIiou4N +WLG1vSOQAPTSRcsghFScecab+HvfsnWkwNGrRJd0M0QxtZ+/1MnoeH4UfIazhYpM +gR3/+EdlLcGrzDvLaPcncXChJBL2bCDhVTsw2w1zlVQeGXZF9s2e0nXy4k4OvNmP +QgzFDNkQ2O6qAKJoTUnPgc2PGnvD1EopUKLuRM4orFOJZurTA1rHLJhNtfsgzx3B +/1clBBN2cSF339atYW/k4TGn9tVZi/1ecZdNjetvhlMuhq6dm7IwfpqIke0iRP6k +A8NdUckdalHyN3fVn+YTGJG/Mdb42KHdd3GTbPR5Eq3U3uydHCYkodT13jnQfVXH +PTFsMWzo++rz76Q0stqGpw/ZGkY4+L5uPCtShn+pOrE8bR67GhIwWsl6fVc97G30 +8Lg6q7O/6nFJMJ9bC/mwt273CoTntCFAI/FOTOCYSPdDGp2YBfEbld19u75XzY62 +w67uNSRYTbS0x1C1q7JltAZJDqAmBqC21ywUCm1rJLZDTGYhAgMBAAGjggF7MIIB +dzAPBgNVHRMBAf8EBTADAQH/MD8GA1UdIAQ4MDYwNAYLKwYBBAG9dAECAQMwJTAj +BggrBgEFBQcCARYXaHR0cHM6Ly93d3cuYnNpLmJ1bmQuZGUwgbwGA1UdHwSBtDCB +sTBioGCgXoZcbGRhcDovL3g1MDAuYnVuZC5kZS9DTj1QQ0EtMS1WZXJ3YWx0dW5n +LTE3LE89UEtJLTEtVmVyd2FsdHVuZyxDPURFP2NlcnRpZmljYXRlUmV2b2NhdGlv +bkxpc3QwS6BJoEeGRWh0dHA6Ly94NTAwLmJ1bmQuZGUvY2dpLWJpbi9zaG93X2F0 +dHI/Y249UENBLTEtVmVyd2FsdHVuZy0xNyZhdHRyPWNybDAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFHP4cgAoNWzXx+2NtTIUPcXrSsSrMDUGA1UdEQQuMCyBEVYt +UEtJQGJzaS5idW5kLmRlhhdodHRwczovL3d3dy5ic2kuYnVuZC5kZTANBgkqhkiG +9w0BAQsFAAOCAgEAf+FXmZcM5ZvaUl3P8tqwKhoG01iXLVXZjzljKbzyGOy4Ut56 +BfiepIjIOUtqMmSKxXm7/dVmmjeoHRgwGV9DTO9A4x/HTNPN76f77NmJzrGliXB1 +lFWfUwou4wdJUh/phqjK6gJ2dGGP2JdxjTE7FKifb4nuWL64zsLr5lxn+nZ6mtl0 +oqgsYDHtXXqhoBZrpyi9kzYWg9c3XWZS2iO2LNJzFQf+xjrlNtbV4Nw1kTBQwzJV +3+MWLOlV4TOSoFitY/A8ufxjUtHIuL7QvgWdIwZz5jU8W3rctDvoJmnUMPkj61As +5OEguVSQbf/opLdLNw6xKyOxrhfi+GZhcNdqmk1AplOIhNM5wX6/LuXOOn9u3Nqx +rxIqFk7EGapMvmtmI5vnzxWaRbVBj3gQwnB4sKtKMhm7+axebY0fRyXA3j9J17Iz +/4qjSDU4dCk4qwOg2TlnQzq2rwZs7CGuzTwXh05ofQnNSMpEma95/J7gmZ9R8rmN +LFUcP43QjINrX52Q9Tz3s4pvf2W041C3neusmnnaSCPlNJJIRe0qNqMHmZUKwsFM +XSzuM6TcY33eIf/aqfum5voXUgAx+f/G51r17dtBiewkYDqi2U38plHtzjLvPUy4 +m1vxNXN7p2wLFTtYw3OTKTHw9ejjuoPBHNHoLzAgatC1WjTgbFsfR7D3wAA= +-----END CERTIFICATE----- diff --git a/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-20.pem b/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-20.pem new file mode 100644 index 0000000000000000000000000000000000000000..1afb21031c97ee13ec769d01b4bcc02b40f4f877 --- /dev/null +++ b/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-20.pem @@ -0,0 +1,37 @@ +-----BEGIN CERTIFICATE----- +MIIGiDCCBHCgAwIBAgIFANoFi14wDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMC +REUxGTAXBgNVBAoTEFBLSS0xLVZlcndhbHR1bmcxHDAaBgNVBAMTE1BDQS0xLVZl +cndhbHR1bmctMjAwHhcNMTkxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBGMQsw +CQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEcMBoGA1UEAxMT +UENBLTEtVmVyd2FsdHVuZy0yMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAIEgWIZDMqr9t0pxV6TJ/dVSxv7UQPItIL9nY2D1PExxuPR2knOGBdWCWxXX +Zdhz3C4XsD8o9INUz3BFDYzvo48PtZitzAawNhgQ09WaBJpqbQkrGrpATAsXSREL +dSxi1vx7yeaMLMc1zH6iwo/Xd7pcQLxiJkgN2lhw4ORoNk4HBYwGcPjz/eEctrRy +EbN1R3WSarg6+J58AKoatNVxw+XTaLmnYthHoJHeCI67nl/Ecrs6rmTivGvVi5PV +RAQ0fCAsR702if0SL1F+YdDd2no+HX/2Kf2HR+/zuvIpctY/iRDRth/vf1IZL8Bx +KHg1NY7i/7wmR3JxyQHnWWL6puOofDLq8i947e4aVHHBP7o6KbOAwau72Goa6XAr +ttzVvDkKuI1wdka+wjNOsXsFJ+D6fSYCKC6x+nX/VCtfm2A0+JeetZV3ovUz387E +l1MGRvxlHcuFWukj/pB6t0lvbJqnkxyq0ZGVweeFHyK6/TxFW2u+2fD/wJOKH+5y +Quo4vKJxvrIK1UeFgRP5LCro+EYCC0kWYfyw9/wMZJDA3h7GbPR4FtNWH6deXGqr +oY+HO3PPFM9o3Jz+xMkrla3kcEeRDjVMGFnSJhr61SvTqhrqYrj9nqx3du3e/mux ++7FxF6m3sYX8ICYppeuGxO1CGMVbC1uqh1gsnVsKv9RRUEw9AgMBAAGjggF7MIIB +dzAPBgNVHRMBAf8EBTADAQH/MD8GA1UdIAQ4MDYwNAYLKwYBBAG9dAECAQMwJTAj +BggrBgEFBQcCARYXaHR0cHM6Ly93d3cuYnNpLmJ1bmQuZGUwgbwGA1UdHwSBtDCB +sTBioGCgXoZcbGRhcDovL3g1MDAuYnVuZC5kZS9DTj1QQ0EtMS1WZXJ3YWx0dW5n +LTIwLE89UEtJLTEtVmVyd2FsdHVuZyxDPURFP2NlcnRpZmljYXRlUmV2b2NhdGlv +bkxpc3QwS6BJoEeGRWh0dHA6Ly94NTAwLmJ1bmQuZGUvY2dpLWJpbi9zaG93X2F0 +dHI/Y249UENBLTEtVmVyd2FsdHVuZy0yMCZhdHRyPWNybDAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFPOPHzb4BN01+d4qhA1jggfppHFCMDUGA1UdEQQuMCyBEVYt +UEtJQGJzaS5idW5kLmRlhhdodHRwczovL3d3dy5ic2kuYnVuZC5kZTANBgkqhkiG +9w0BAQsFAAOCAgEARNpaiEE3gm/gLIfLn0d8TQF8DxHmRFszIhb8NVCXJWlMJn2J +/UW+XzARsJiB4J0X6tvJcV9AjPwmvlgqAdlo/fce3UwLLPHqrSMuF/xS0Do2CeV1 +OjgXC9OZmuJshPCthSqReAh9h5cllKMsMaS/gM0i1o5QOCpa/0bTDxlTZ0exQw+4 +I4O7gi5SABaYyWJhYzLr1EIROaGcvJk78olGQMYnX4CNq3PDXy9HZiKPh3K07V2J +8GM36iGxF1haTnjGopmtcUQ2lP/Ng2P0qwCFZ1UFIxIOTX5//O/rRhAroFdP+NwT +cdJ4S4xPHNffUWPFCrT8ghEj9Hgy7H/uAtgw5PpnYieevKFjRlOvd/vp9tKjvGre +GnU7xxS3xDbuRuNinQyJWVV/29KReumysi27qTS2cJxIYk2WLvt/1eMZt52xzK32 +G5Pn8ocCK2gMFz7ldyH3jTa5BQFuzFSJ05P1tnVkUpeKlU4aSt8GtW884ifvwhkw +OKyAFAQRXvQSFkE505Kzy/6JZcanoy7e/pRlDBtN70MfBOQl8ucVzAVs/tuuTJAg +3cVCmHv/hlEgVRVPaUJXhtX1u+5yUWMEm2380uW9w9/PAbdtm0kfRb19p32YMkDi +hREyKz/Kpgl5NvoU71RRVqISeJhqJ/k2qYfyYRkPRzUiP9qzyyFS7WE8sXs= +-----END CERTIFICATE----- diff --git a/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-23.pem b/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-23.pem new file mode 100644 index 0000000000000000000000000000000000000000..c4ff111515d238e5643906257f3fd6c276c36251 --- /dev/null +++ b/integration-tests/src/test/resources/trusted-root-certificates/PCA-1-Verwaltung-23.pem @@ -0,0 +1,37 @@ +-----BEGIN CERTIFICATE----- +MIIGiDCCBHCgAwIBAgIFAKXvkjAwDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMC +REUxGTAXBgNVBAoTEFBLSS0xLVZlcndhbHR1bmcxHDAaBgNVBAMTE1BDQS0xLVZl +cndhbHR1bmctMjMwHhcNMjIxMjAxMDAwMDAwWhcNMzIxMjMxMjM1OTU5WjBGMQsw +CQYDVQQGEwJERTEZMBcGA1UEChMQUEtJLTEtVmVyd2FsdHVuZzEcMBoGA1UEAxMT +UENBLTEtVmVyd2FsdHVuZy0yMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBAMXZeBfphlB9m/8J8wVxSjEtOqfdftHEiQeITx31Md9by7+ppxHKzhQcDrRU +3QxZnpNuoj+pIz73ZTPzBuz8PnW5BMnzPYK/0+TJiWDiUmWQ4NxwgHxV+S1KLX6S +gkwr3L6C3VuC54rVnGeDTV6RyVzzQBpaGv1H7E2HRJh7MP3adV+Wk9DyvR4m6AE7 +6R8rI8qCv99pso2y5mAR0ulfA9zyNKxBooYSefGxQgNn+Fwb+41TBMv3iDEro7x5 +3tGHsOC/8F+xOxBe093/P2GHtEdGdAKeo6hhEq8encG0+uHIj4SljiTzKZw7PYcK +LLZvvUyCytSuFA5Z4nRXLO2hsdqaJfT1+qecs1uJjYx0ZlzVGEsIWOpKtbB9vcvq +IkYspykCsWcBnf1tB9F+IERw59jsGD9nZKWIgHRgI5fqHzaf0A+LwbeIO4Fn1keY +wmYF8bNFtMCWW51ukrTQjoOc7EaE9S460E7pdmCG4NGau/bN8iQQf/cDBuRvlEpG +aN7NeaEXWFbOBHyJKWqpquJBABFVZFaaQpBxWlnnV+o1EZ5IZPtefHFSGMxycJRb +m6pVfGmuEo+z7xYClLH1gMDHiIDKy+RbLNMyVWUh5viu28EU29CXA7Nx6LmfLwvl +Gr1vyCu4UAXOTrRZwff0X19tk5TjHy/SK9jnXkKURe9WuKW1AgMBAAGjggF7MIIB +dzAPBgNVHRMBAf8EBTADAQH/MD8GA1UdIAQ4MDYwNAYLBAB/AAcDBgEBAQQwJTAj +BggrBgEFBQcCARYXaHR0cHM6Ly93d3cuYnNpLmJ1bmQuZGUwgbwGA1UdHwSBtDCB +sTBioGCgXoZcbGRhcDovL3g1MDAuYnVuZC5kZS9DTj1QQ0EtMS1WZXJ3YWx0dW5n +LTIzLE89UEtJLTEtVmVyd2FsdHVuZyxDPURFP2NlcnRpZmljYXRlUmV2b2NhdGlv +bkxpc3QwS6BJoEeGRWh0dHA6Ly94NTAwLmJ1bmQuZGUvY2dpLWJpbi9zaG93X2F0 +dHI/Y249UENBLTEtVmVyd2FsdHVuZy0yMyZhdHRyPWNybDAOBgNVHQ8BAf8EBAMC +AQYwHQYDVR0OBBYEFJ2BqFcYkLlZ9LOTo6q+LLqE9BnGMDUGA1UdEQQuMCyBEVYt +UEtJQGJzaS5idW5kLmRlhhdodHRwczovL3d3dy5ic2kuYnVuZC5kZTANBgkqhkiG +9w0BAQsFAAOCAgEAQiNGSGjBukYu5TRDaw2J7RPcDZltf/Nz6dmwzo1gXFb7ALnd +X+Kv9OLxqTKcpywwI017xgRjzxoLlk8PZNkxl+2fuY0vqvW/c8ys2/KjXGHTgwKm +xwvDov8vBfQxl4fXiFPxeixZujT0YZKVJ2L+6HcqzrNVNDpgNzpeLPVZxNZc0rOz +3jxnCGwGH0mskPAMjObn9KizDge1Zve9l10jaPyyKeZq+MDhHtbwxBEICC8yCx4W ++FcETRJpQSmfP7Yr33cbitb+IaQpUf9pPRqIRUfc8Zc/lOBv49q2N+iOrCNv5XHw +mS5HnuQ4yvSJBNgVqU+dTHvcZZVjSXCTpdwmzlmJpEtPFtJks09Ug1TYU0gQY1JU +KW3rfndKCcsXcYA0Frvm/EA3arwbXIQRdiz1cQchHSn+IVRiUY7H9Am7ktHhPpM9 +haNpg7rfLQJnYyKKZxEs2LI84oPqRNvHBVFMFO2rY+K9HG/HWV3SixQtLAvLQOAe +vIXuLdCZJMG8O/3Of0xLlGC/GhCXoP0g4lr7KluUzUTZj6K1q0b8NgVUqLtNiKfY +58BefTEwUv4kSMmyQQAH69sx2wLKVKTqkOP3c81jDhf7OQs41cMGu+HRXtgy4FEI +F3WfIOCHR3z1jJckcZwjh4WoaOgv8sGxubwjGLi2ZelCjt9ZKql+oF5LWvY= +-----END CERTIFICATE----- diff --git a/pom.xml b/pom.xml index 809ecb3d97044cb5c40c90bc746da2078109f40a..d32b872daaa04a464d10d5d17de1404e29b99ce0 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,7 @@ <properties> + <java.version>11</java.version> <maven.compiler.release>11</maven.compiler.release> @@ -56,7 +57,7 @@ <project.reporting.outputEncoding>${encoding}</project.reporting.outputEncoding> <!-- FIT-Connect dependencies --> - <jwk-validator.version>1.4.0</jwk-validator.version> + <jwk-validator.version>1.5.0</jwk-validator.version> <!-- 3rd party dependencies --> @@ -64,27 +65,37 @@ <jackson-databind.version>2.14.2</jackson-databind.version> <jackson-annotations.version>2.14.2</jackson-annotations.version> <lombock.version>1.18.26</lombock.version> - <logback.version>1.4.5</logback.version> - <slf4j.version>2.0.6</slf4j.version> + <logback.version>1.4.6</logback.version> + <slf4j.version>2.0.7</slf4j.version> <jcommander.version>1.82</jcommander.version> <apache-tika.version>2.7.0</apache-tika.version> - <spring-web.version>5.3.25</spring-web.version> + <spring-web.version>5.3.26</spring-web.version> <snakeyaml.version>2.0</snakeyaml.version> <open-csv.version>5.7.1</open-csv.version> <json-schema-validator.version>1.0.78</json-schema-validator.version> <junit.version>5.9.2</junit.version> - <mockito.version>5.1.1</mockito.version> + <mockito.version>5.2.0</mockito.version> + <junit-platform-version>1.9.2</junit-platform-version> + <mockito.version>5.2.0</mockito.version> <wiremock.version>2.27.2</wiremock.version> <hamcrest.version>1.3</hamcrest.version> <awaitility-version>4.2.0</awaitility-version> + <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version> - <maven-surefire-plugin.version>3.0.0-M9</maven-surefire-plugin.version> - <maven-failsafe-plugin.version>3.0.0-M9</maven-failsafe-plugin.version> + <maven-surefire-plugin.version>3.0.0</maven-surefire-plugin.version> + <maven-failsafe-plugin.version>3.0.0</maven-failsafe-plugin.version> + <maven-jar-plugin.version>3.3.0</maven-jar-plugin.version> + <maven-install-plugin.version>3.1.0</maven-install-plugin.version> + <maven-deploy-plugin.version>3.1.0</maven-deploy-plugin.version> <maven-checkstyle-plugin.version>3.2.1</maven-checkstyle-plugin.version> <maven-assembly-plugin.version>3.5.0</maven-assembly-plugin.version> <maven-javadoc-plugin.version>3.5.0</maven-javadoc-plugin.version> + <maven-source-plugin.version>3.2.1</maven-source-plugin.version> + <nexus-staging-maven-plugin.version>1.6.13</nexus-staging-maven-plugin.version> + <git-commit-id-maven-plugin.version>5.0.0</git-commit-id-maven-plugin.version> + <jacoco-maven-plugin.version>0.8.8</jacoco-maven-plugin.version> <local-version-suffix>-local</local-version-suffix> @@ -94,6 +105,7 @@ <module>api</module> <module>core</module> <module>client</module> + <module>integration-tests</module> </modules> <dependencyManagement> @@ -118,6 +130,12 @@ <version>${project.version}</version> </dependency> + <dependency> + <groupId>dev.fitko.fitconnect.sdk</groupId> + <artifactId>integration-tests</artifactId> + <version>${project.version}</version> + </dependency> + <!-- FIT-Connect dependencies --> <dependency> <groupId>dev.fitko.fitconnect</groupId> @@ -219,6 +237,11 @@ <version>${awaitility-version}</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-suite-engine</artifactId> + <version>${junit-platform-version}</version> + </dependency> </dependencies> </dependencyManagement> @@ -236,6 +259,21 @@ <build> <pluginManagement> <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>${maven-jar-plugin.version}</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-install-plugin</artifactId> + <version>${maven-install-plugin.version}</version> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <version>${maven-deploy-plugin.version}</version> + </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> @@ -245,7 +283,17 @@ <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${maven-surefire-plugin.version}</version> + <configuration> + <argLine> + --illegal-access=permit + </argLine> + <testFailureIgnore>true</testFailureIgnore> + <forkCount>2</forkCount> + <reuseForks>true</reuseForks> + <argLine>${surefireArgLine}</argLine> + </configuration> </plugin> + <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> @@ -282,7 +330,7 @@ <plugin> <groupId>org.sonatype.plugins</groupId> <artifactId>nexus-staging-maven-plugin</artifactId> - <version>1.6.13</version> + <version>${nexus-staging-maven-plugin.version}</version> <extensions>true</extensions> <executions> <execution> @@ -302,7 +350,7 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> - <version>3.2.1</version> + <version>${maven-source-plugin.version}</version> <executions> <execution> <id>attach-sources</id> @@ -326,7 +374,11 @@ </executions> <configuration> <failOnError>false</failOnError> - <additionalJOption>-Xdoclint:none</additionalJOption> + <additionalOptions>-Xdoclint:all -Xdoclint:-missing</additionalOptions> + <additionalJOptions> + <additionalJOption>-Xdoclint:all</additionalJOption> + <additionalJOption>-Xdoclint:-missing</additionalJOption> + </additionalJOptions> </configuration> </plugin> <plugin> @@ -334,6 +386,54 @@ <artifactId>git-commit-id-maven-plugin</artifactId> <version>${git-commit-id-maven-plugin.version}</version> </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>${jacoco-maven-plugin.version}</version> + <executions> + <execution> + <id>default-prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + <configuration> + <destFile>${project.build.directory}/coverage-reports/jacoco.exec</destFile> + <propertyName>surefireArgLine</propertyName> + </configuration> + </execution> + <execution> + <id>default-check</id> + <goals> + <goal>check</goal> + </goals> + <configuration> + <rules> + <rule> + <element>BUNDLE</element> + <limits> + <limit> + <counter>COMPLEXITY</counter> + <value>COVEREDRATIO</value> + <minimum>0.80</minimum> + </limit> + </limits> + </rule> + </rules> + </configuration> + </execution> + <execution> + <id>default-report</id> + <phase>test</phase> + <goals> + <goal>report</goal> + </goals> + <configuration> + <dataFile>${project.build.directory}/coverage-reports/jacoco.exec</dataFile> + <outputDirectory>${project.reporting.outputDirectory}/jacoco</outputDirectory> + </configuration> + </execution> + </executions> + </plugin> </plugins> </pluginManagement> </build>