diff --git a/README.md b/README.md index 9bdb7e5330add876fd370eb0940a444e8fbebaa7..1ccc415d325acce2628424d5b6aec1c940826678 100644 --- a/README.md +++ b/README.md @@ -1 +1,34 @@ -## FitCo Java SDK \ No newline at end of file +## FIT-Connect Java SDK + +### Project Setup Info + +- Java 11 (LTS) +- Maven +- Junit 5 +- (Guice DI) + +### Build + +To build the maven artifact, run: `mvn clean package` + +### Tests + +To run all tests, run: `mvn test` + +### External Configuration + +Configuration properties e.g. for REST-API urls and proxy settings can be found in +the [HOCON](https://github.com/lightbend/config/blob/main/HOCON.md) +config ``sdk.conf`` within the root folder of the project. + +### Documentation + +See the projects module documentation for more specific information: + +* [API Module](api/README.md) +* [Core Module](core/README.md) +* [Client Module](client/README.md) +* [Dependency Module](dependency/README.md) +* [Open-API Module](open-api/README.md) + +As well as the official API docs: [FIT-Connect Documentation](https://docs.fitko.de/fit-connect/docs/) diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e5ba47e0ed4de6004500bcce4d0ba11d82431eca --- /dev/null +++ b/api/README.md @@ -0,0 +1,104 @@ +## API Module + +The API-module contains interfaces and domain model value classes that provide the basic functionality to build an +SDK client. + +### Structure + +- **api.domain** - contains all model classes and domain related POJOs +- **api.exceptions** - all use case specific exceptions thrown by services +- **api.services** - all services to authenticate, encrypt, validate and perform REST-requests + +There are two service facade interfaces that provide a client centric wrapper around the underlying services, + +- **Sender** - create a submission, announce attachments, encrypt and send the submission including metadata +- **Subscriber** - poll, receive and decrypt submissions and confirm their valid transmission + +### Service Architecture + +Apart from the ClientFactory the overall service architecture focuses on composition and has no inherited dependencies. + +```mermaid +classDiagram + + class CryptoService{ + + decryptString + + decryptBytes + + encryptString + + encryptBytes + } + + + class CertificateValidator{ + + validatePublicKey + } + + class MetadataValidator{ + + validateMetadataSchema + + validateMetadataHashValues + } + + class MetadataService{ + + createMetadata + } + + class OAuthService{ + + authenticate + } + + class SenderFacade { + + retrieveOAuthToken + + validatePublicKey + + encryptSubmissionData + + encryptAttachment + + createMetadata + + createSubmission + + sendSubmission + + uploadAttachments + + createAttachment + + getEncryptionKeyForDestination + } + + class SubscriberFacade { + + retrieveOAuthToken + + decryptStringContent + + pollAvailableSubmissions + + getSubmission + + fetchAttachments + + validateEventLog + + validateMetadata + + validateData + + validateAttachments + + confirmValidSubmission + } + + class SubscriberClient{ + SubscriberFacade + } + + class SenderClient{ + SenderFacade + } + + class ClientFactory { + AppplicationConfig + } + + +ClientFactory ..> SenderClient : Constructs +ClientFactory ..> SubscriberClient : Constructs + +SenderClient ..> SenderFacade : Uses +SubscriberClient ..> SubscriberFacade : Uses + +SenderFacade ..> CertificateValidator : Uses + +SenderFacade ..> MetadataService : Uses +SenderFacade ..> OAuthService : Uses +SenderFacade ..> CryptoService : Uses + +SubscriberFacade ..> OAuthService : Uses +SubscriberFacade ..> CryptoService : Uses +SubscriberFacade ..> MetadataValidator : Uses + +``` diff --git a/api/pom.xml b/api/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..543e9c31bd3375e084aa6183e3ef68af47613aba --- /dev/null +++ b/api/pom.xml @@ -0,0 +1,33 @@ +<?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>de.fitko.fitconnect.sdk</groupId> + <version>1.0-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>api</artifactId> + + <dependencies> + <dependency> + <groupId>com.nimbusds</groupId> + <artifactId>nimbus-jose-jwt</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + </dependencies> + +</project> \ No newline at end of file diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/auth/OAuthToken.java b/api/src/main/java/de/fitko/fitconnect/api/domain/auth/OAuthToken.java new file mode 100644 index 0000000000000000000000000000000000000000..6a53a6fe93a4c944b273562aa6d4c4eecd68f367 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/auth/OAuthToken.java @@ -0,0 +1,22 @@ +package de.fitko.fitconnect.api.domain.auth; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class OAuthToken { + @JsonProperty("access_token") + String accessToken; + @JsonProperty("scope") + String scope; + @JsonProperty("token_type") + String tokenType; + @JsonProperty("error") + String error; + @JsonProperty("error_description") + String errorDescription; + @JsonProperty("expires_in") + Integer expiresIn; +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/destination/Destination.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/destination/Destination.java new file mode 100644 index 0000000000000000000000000000000000000000..21f58696efd1b89283e25d232d8136374df4cb8e --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/destination/Destination.java @@ -0,0 +1,11 @@ +package de.fitko.fitconnect.api.domain.model.destination; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.UUID; + +public class Destination { + + @JsonProperty("encryptionKid") + UUID encryptionKid; +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/AdditionalReferenceInfo.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/AdditionalReferenceInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..07e92aadc53f3af2c8d244ce1cbb9b7cde76bf0c --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/AdditionalReferenceInfo.java @@ -0,0 +1,30 @@ +package de.fitko.fitconnect.api.domain.model.metadata; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "senderReference", + "applicationDate" +}) +@Getter +@Setter +public class AdditionalReferenceInfo { + + @JsonProperty("senderReference") + String senderReference; + + @JsonProperty("applicationDate") + String applicationDate; + + @JsonIgnore + Map<String, Object> additionalProperties; +} + diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/AuthenticationInformation.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/AuthenticationInformation.java new file mode 100644 index 0000000000000000000000000000000000000000..bb48cfe5c989c97469cbdd8e0fcea1e4d4a7eaaf --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/AuthenticationInformation.java @@ -0,0 +1,9 @@ +package de.fitko.fitconnect.api.domain.model.metadata; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AuthenticationInformation { +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/ContentStructure.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/ContentStructure.java new file mode 100644 index 0000000000000000000000000000000000000000..1fbebed0cd06063788cd3eb553ccbc4d6854e17d --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/ContentStructure.java @@ -0,0 +1,34 @@ +package de.fitko.fitconnect.api.domain.model.metadata; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.metadata.data.Data; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "data", + "attachments" +}) +@Getter +@Setter +@Builder +public class ContentStructure { + + @JsonProperty("data") + Data data; + + @JsonProperty("attachments") + List<Attachment> attachments; + + @JsonIgnore + Map<String, Object> additionalProperties; +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/Metadata.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/Metadata.java new file mode 100644 index 0000000000000000000000000000000000000000..f1df6c5f3ee24f36eaa59d7d15fd8011e2cf9703 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/Metadata.java @@ -0,0 +1,48 @@ +package de.fitko.fitconnect.api.domain.model.metadata; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "contentStructure", + "publicServiceType", + "authenticationInformation", + "paymentInformation", + "replyChannel", + "additionalReferenceInfo" +}) +@Getter +@Setter +@Builder +public class Metadata { + @JsonProperty("contentStructure") + ContentStructure contentStructure; + + @JsonProperty("publicServiceType") + PublicServiceType publicServiceType; + + @JsonProperty("authenticationInformation") + List<AuthenticationInformation> authenticationInformation; + + @JsonProperty("paymentInformation") + PaymentInformation paymentInformation; + + @JsonProperty("replyChannel") + ReplyChannel replyChannel; + + @JsonProperty("additionalReferenceInfo") + AdditionalReferenceInfo additionalReferenceInfo; + + @JsonIgnore + Map<String, Object> additionalProperties; +} + diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/PaymentInformation.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/PaymentInformation.java new file mode 100644 index 0000000000000000000000000000000000000000..27b7c019e6f4f22b757bb7cde61f3bed4b11a49d --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/PaymentInformation.java @@ -0,0 +1,9 @@ +package de.fitko.fitconnect.api.domain.model.metadata; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class PaymentInformation { +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/PublicServiceType.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/PublicServiceType.java new file mode 100644 index 0000000000000000000000000000000000000000..fb29a6d1c50fe39fc0b8beb399d46b736a2ff7ea --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/PublicServiceType.java @@ -0,0 +1,34 @@ +package de.fitko.fitconnect.api.domain.model.metadata; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "name", + "description", + "identifier" +}) +@Getter +@Setter +public class PublicServiceType { + + @JsonProperty("name") + String name; + + @JsonProperty("description") + String description; + + @JsonProperty("identifier") + String identifier; + + @JsonIgnore + Map<String, Object> additionalProperties; +} + diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/ReplyChannel.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/ReplyChannel.java new file mode 100644 index 0000000000000000000000000000000000000000..233c7e6ba17ff6098dbf332eb033f1c70196e07b --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/ReplyChannel.java @@ -0,0 +1,9 @@ +package de.fitko.fitconnect.api.domain.model.metadata; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ReplyChannel { +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/Attachment.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/Attachment.java new file mode 100644 index 0000000000000000000000000000000000000000..c88f7b1ac64774e86aaedee57f2767889b0b3abb --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/Attachment.java @@ -0,0 +1,57 @@ +package de.fitko.fitconnect.api.domain.model.metadata.attachment; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.signature.Hash__1; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.signature.Signature__1; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.Value; + +import java.util.Map; +import java.util.UUID; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "hash", + "signature", + "purpose", + "filename", + "description", + "mimeType", + "attachmentId" +}) +@Getter +@Setter +@Builder +public class Attachment { + + @JsonProperty("hash") + Hash__1 hash; + + @JsonProperty("signature") + Signature__1 signature; + + @JsonProperty("purpose") + Purpose purpose; + + @JsonProperty("filename") + String filename; + + @JsonProperty("description") + String description; + + @JsonProperty("mimeType") + String mimeType; + + @JsonProperty("attachmentId") + UUID attachmentId; + + @JsonIgnore + Map<String, Object> additionalProperties; +} + + diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/Purpose.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/Purpose.java new file mode 100644 index 0000000000000000000000000000000000000000..a5726ea3875d4c751fbd77058fb39489350ffcb1 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/Purpose.java @@ -0,0 +1,46 @@ +package de.fitko.fitconnect.api.domain.model.metadata.attachment; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum Purpose { + + FORM("form"), + ATTACHMENT("attachment"), + REPORT("report"); + private final String value; + private final static Map<String, Purpose> CONSTANTS = new HashMap<>(); + + static { + for (Purpose c : values()) { + CONSTANTS.put(c.value, c); + } + } + + Purpose(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static Purpose fromValue(String value) { + Purpose constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/EidasAdesProfile.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/EidasAdesProfile.java new file mode 100644 index 0000000000000000000000000000000000000000..21199aef3400f361a95797a6c6b1ffe13307e63e --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/EidasAdesProfile.java @@ -0,0 +1,48 @@ +package de.fitko.fitconnect.api.domain.model.metadata.attachment.signature; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum EidasAdesProfile { + + HTTP_URI_ETSI_ORG_ADES_191_X_2_LEVEL_BASELINE_B_B("http://uri.etsi.org/ades/191x2/level/baseline/B-B#"), + HTTP_URI_ETSI_ORG_ADES_191_X_2_LEVEL_BASELINE_B_T("http://uri.etsi.org/ades/191x2/level/baseline/B-T#"), + HTTP_URI_ETSI_ORG_ADES_191_X_2_LEVEL_BASELINE_B_LT("http://uri.etsi.org/ades/191x2/level/baseline/B-LT#"), + HTTP_URI_ETSI_ORG_ADES_191_X_2_LEVEL_BASELINE_B_LTA("http://uri.etsi.org/ades/191x2/level/baseline/B-LTA#"); + private final String value; + private final static Map<String, EidasAdesProfile> CONSTANTS = new HashMap<>(); + + static { + for (EidasAdesProfile c : values()) { + CONSTANTS.put(c.value, c); + } + } + + EidasAdesProfile(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static EidasAdesProfile fromValue(String value) { + EidasAdesProfile constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/Hash__1.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/Hash__1.java new file mode 100644 index 0000000000000000000000000000000000000000..3522e8d5dfacbf4535829abe9df90b9932a87539 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/Hash__1.java @@ -0,0 +1,30 @@ +package de.fitko.fitconnect.api.domain.model.metadata.attachment.signature; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "type", + "content" +}) +@Getter +@Setter +public class Hash__1 { + + @JsonProperty("type") + Type type; + + @JsonProperty("content") + String content; + + @JsonIgnore + Map<String, Object> additionalProperties; +} + diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/SignatureFormat.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/SignatureFormat.java new file mode 100644 index 0000000000000000000000000000000000000000..b9c613efe91ad281f4ead385a3652a66d0287846 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/SignatureFormat.java @@ -0,0 +1,49 @@ +package de.fitko.fitconnect.api.domain.model.metadata.attachment.signature; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum SignatureFormat { + + CMS("cms"), + XML("xml"), + PDF("pdf"), + ASIC("asic"), + JSON("json"); + private final String value; + private final static Map<String, SignatureFormat> CONSTANTS = new HashMap<>(); + + static { + for (SignatureFormat c : values()) { + CONSTANTS.put(c.value, c); + } + } + + SignatureFormat(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static SignatureFormat fromValue(String value) { + SignatureFormat constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + +} \ No newline at end of file diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/Signature__1.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/Signature__1.java new file mode 100644 index 0000000000000000000000000000000000000000..b2288869acc3be85a2ec6d2b4bb5bdd2d4ad211a --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/Signature__1.java @@ -0,0 +1,39 @@ +package de.fitko.fitconnect.api.domain.model.metadata.attachment.signature; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "signatureFormat", + "eidasAdesProfile", + "detachedSignature", + "content" +}) +@Getter +@Setter +public class Signature__1 { + + @JsonProperty("signatureFormat") + SignatureFormat signatureFormat; + + @JsonProperty("eidasAdesProfile") + EidasAdesProfile eidasAdesProfile; + + @JsonProperty("detachedSignature") + Boolean detachedSignature; + + @JsonProperty("content") + String content; + + @JsonIgnore + Map<String, Object> additionalProperties; +} + diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/Type.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/Type.java new file mode 100644 index 0000000000000000000000000000000000000000..17b4660ee90a83fe8f0773d353b75b597f85c6fa --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/attachment/signature/Type.java @@ -0,0 +1,45 @@ +package de.fitko.fitconnect.api.domain.model.metadata.attachment.signature; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum Type { + + SHA_512("sha512"); + private final String value; + private final static Map<String, Type> CONSTANTS = new HashMap<>(); + + static { + for (Type c : values()) { + CONSTANTS.put(c.value, c); + } + } + + Type(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static Type fromValue(String value) { + Type constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/Data.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/Data.java new file mode 100644 index 0000000000000000000000000000000000000000..58e9dc5d86a6be92f44055ce3e1394759694b80e --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/Data.java @@ -0,0 +1,36 @@ +package de.fitko.fitconnect.api.domain.model.metadata.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "signature", + "hash", + "submissionSchema" +}) +@Getter +@Setter +@Builder +public class Data { + + @JsonProperty("signature") + Signature signature; + + @JsonProperty("hash") + Hash hash; + + @JsonProperty("submissionSchema") + SubmissionSchema submissionSchema; + + @JsonIgnore + Map<String, Object> additionalProperties; +} + diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/EidasAdesProfile.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/EidasAdesProfile.java new file mode 100644 index 0000000000000000000000000000000000000000..fc8843b28eb31a9b6e4ac09052f3ce3e12d35dfb --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/EidasAdesProfile.java @@ -0,0 +1,50 @@ +package de.fitko.fitconnect.api.domain.model.metadata.data; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum EidasAdesProfile { + + HTTP_URI_ETSI_ORG_ADES_191_X_2_LEVEL_BASELINE_B_B("http://uri.etsi.org/ades/191x2/level/baseline/B-B#"), + HTTP_URI_ETSI_ORG_ADES_191_X_2_LEVEL_BASELINE_B_T("http://uri.etsi.org/ades/191x2/level/baseline/B-T#"), + HTTP_URI_ETSI_ORG_ADES_191_X_2_LEVEL_BASELINE_B_LT("http://uri.etsi.org/ades/191x2/level/baseline/B-LT#"), + HTTP_URI_ETSI_ORG_ADES_191_X_2_LEVEL_BASELINE_B_LTA("http://uri.etsi.org/ades/191x2/level/baseline/B-LTA#"); + + private final String value; + private final static Map<String, EidasAdesProfile> CONSTANTS = new HashMap<>(); + + static { + for (EidasAdesProfile c : values()) { + CONSTANTS.put(c.value, c); + } + } + + EidasAdesProfile(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static EidasAdesProfile fromValue(String value) { + EidasAdesProfile constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + +} + diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/Hash.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/Hash.java new file mode 100644 index 0000000000000000000000000000000000000000..94475c30808ea346a22b1fa2dec4c7e0b6df61bf --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/Hash.java @@ -0,0 +1,29 @@ +package de.fitko.fitconnect.api.domain.model.metadata.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "type", + "content" +}) +@Getter +@Setter +public class Hash { + + @JsonProperty("type") + Type type; + + @JsonProperty("content") + String content; + + @JsonIgnore + Map<String, Object> additionalProperties; +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/MimeType.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/MimeType.java new file mode 100644 index 0000000000000000000000000000000000000000..cf210d4837c0b4114e00695a3f493123a15b1fa0 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/MimeType.java @@ -0,0 +1,46 @@ +package de.fitko.fitconnect.api.domain.model.metadata.data; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum MimeType { + + APPLICATION_JSON("application/json"), + APPLICATION_XML("application/xml"); + private final String value; + private final static Map<String, MimeType> CONSTANTS = new HashMap<>(); + + static { + for (MimeType c : values()) { + CONSTANTS.put(c.value, c); + } + } + + MimeType(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static MimeType fromValue(String value) { + MimeType constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/Signature.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/Signature.java new file mode 100644 index 0000000000000000000000000000000000000000..dba35defd74ff1ec7dfe8c1719fa8c87c01dce00 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/Signature.java @@ -0,0 +1,38 @@ +package de.fitko.fitconnect.api.domain.model.metadata.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "signatureFormat", + "eidasAdesProfile", + "detachedSignature", + "content" +}) +@Getter +@Setter +public class Signature { + + @JsonProperty("signatureFormat") + SignatureFormat signatureFormat; + + @JsonProperty("eidasAdesProfile") + EidasAdesProfile eidasAdesProfile; + + @JsonProperty("detachedSignature") + Boolean detachedSignature; + + @JsonProperty("content") + String content; + + @JsonIgnore + Map<String, Object> additionalProperties; +} + diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/SignatureFormat.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/SignatureFormat.java new file mode 100644 index 0000000000000000000000000000000000000000..e1676eb7bb87c606403680c7637f60d7d95b06fe --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/SignatureFormat.java @@ -0,0 +1,49 @@ +package de.fitko.fitconnect.api.domain.model.metadata.data; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum SignatureFormat { + + CMS("cms"), + XML("xml"), + PDF("pdf"), + ASIC("asic"), + JSON("json"); + private final String value; + private final static Map<String, SignatureFormat> CONSTANTS = new HashMap<>(); + + static { + for (SignatureFormat c : values()) { + CONSTANTS.put(c.value, c); + } + } + + SignatureFormat(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static SignatureFormat fromValue(String value) { + SignatureFormat constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + +} \ No newline at end of file diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/SubmissionSchema.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/SubmissionSchema.java new file mode 100644 index 0000000000000000000000000000000000000000..4d3b62bcdb7676e41bf967987fa80abefaab49ba --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/SubmissionSchema.java @@ -0,0 +1,30 @@ +package de.fitko.fitconnect.api.domain.model.metadata.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.Getter; +import lombok.Setter; + +import java.net.URI; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "schemaUri", + "mimeType" +}) +@Getter +@Setter +public class SubmissionSchema { + + @JsonProperty("schemaUri") + URI schemaUri; + + @JsonProperty("mimeType") + MimeType mimeType; + + @JsonIgnore + Map<String, Object> additionalProperties; +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/Type.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/Type.java new file mode 100644 index 0000000000000000000000000000000000000000..060804d68bd84425a334e5d890f1aa5afa497dda --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/metadata/data/Type.java @@ -0,0 +1,45 @@ +package de.fitko.fitconnect.api.domain.model.metadata.data; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.HashMap; +import java.util.Map; + +public enum Type { + + SHA_512("sha512"); + private final String value; + private final static Map<String, Type> CONSTANTS = new HashMap<>(); + + static { + for (Type c : values()) { + CONSTANTS.put(c.value, c); + } + } + + Type(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + @JsonValue + public String value() { + return this.value; + } + + @JsonCreator + public static Type fromValue(String value) { + Type constant = CONSTANTS.get(value); + if (constant == null) { + throw new IllegalArgumentException(value); + } else { + return constant; + } + } + +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/Callback.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/Callback.java new file mode 100644 index 0000000000000000000000000000000000000000..168430b42d770315cd25bd7d7926787729d26f78 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/Callback.java @@ -0,0 +1,20 @@ +package de.fitko.fitconnect.api.domain.model.submission; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.Value; + +import java.net.URI; + +@Getter +@Setter +@Value +public class Callback { + + @JsonProperty("uri") + URI uri; + + @JsonProperty("secret") + String secret; +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/ServiceType.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/ServiceType.java new file mode 100644 index 0000000000000000000000000000000000000000..fdd7cd2deec1ba260629284650ba20d1270bcac4 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/ServiceType.java @@ -0,0 +1,21 @@ +package de.fitko.fitconnect.api.domain.model.submission; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; +import lombok.Setter; +import lombok.Value; + +@Getter +@Setter +@Value +public class ServiceType { + + @JsonProperty("name") + String name; + + @JsonProperty("description") + String description; + + @JsonProperty("identifier") + String identifier; +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/Submission.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/Submission.java new file mode 100644 index 0000000000000000000000000000000000000000..1af6a01ac1e771fb19fd4b1c43e665f479bf794b --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/Submission.java @@ -0,0 +1,39 @@ +package de.fitko.fitconnect.api.domain.model.submission; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Value; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Value +@Builder +public class Submission { + + @JsonProperty("destinationId") + private UUID destinationId; + + @JsonProperty("submissionId") + private UUID submissionId; + + @JsonProperty("caseId") + private UUID caseId; + + @JsonProperty("attachments") + private List<UUID> attachments = new ArrayList<>(); + + @JsonProperty("serviceType") + private ServiceType serviceType; + + @JsonProperty("callback") + private Callback callback; + + @JsonProperty("encryptedMetadata") + private String encryptedMetadata; + + @JsonProperty("encryptedData") + private String encryptedData; + +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/SubmissionForPickup.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/SubmissionForPickup.java new file mode 100644 index 0000000000000000000000000000000000000000..4c179568b54a015dda2d6828f2e752b2825f4848 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/SubmissionForPickup.java @@ -0,0 +1,19 @@ +package de.fitko.fitconnect.api.domain.model.submission; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +import java.util.UUID; + +@Value +public class SubmissionForPickup { + + @JsonProperty("destinationId") + private final UUID destinationId; + + @JsonProperty("submissionId") + private final UUID submissionId; + + @JsonProperty("caseId") + private final UUID caseId; +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/SubmissionSubmit.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/SubmissionSubmit.java new file mode 100644 index 0000000000000000000000000000000000000000..a90e0398aaf566908a6e9605f6a313245e36ea17 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/SubmissionSubmit.java @@ -0,0 +1,34 @@ +package de.fitko.fitconnect.api.domain.model.submission; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; +import lombok.Value; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Getter +@Setter +@Value +@Builder +public class SubmissionSubmit { + + @JsonProperty("destinationId") + UUID destinationId; + + @JsonProperty("destinationId") + Optional<UUID> caseId; + + @JsonProperty("announcedAttachments") + List<UUID> announcedAttachments; + + @JsonProperty("serviceType") + ServiceType serviceType; + + @JsonProperty("callback") + Callback callback; + +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/SubmissionsForPickup.java b/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/SubmissionsForPickup.java new file mode 100644 index 0000000000000000000000000000000000000000..54f0f14a0ebd3e2d4295e9d958401ccfcd8d16dd --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/model/submission/SubmissionsForPickup.java @@ -0,0 +1,25 @@ +package de.fitko.fitconnect.api.domain.model.submission; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; +import lombok.Value; + +import java.util.Collections; +import java.util.List; + +@Value +@Builder +public class SubmissionsForPickup{ + @JsonProperty("count") + int count; + + @JsonProperty("offset") + int offset; + + @JsonProperty("totalCount") + int totalCount; + + @JsonProperty("submissions") + List<SubmissionForPickup> submissions = Collections.emptyList(); +} \ No newline at end of file diff --git a/api/src/main/java/de/fitko/fitconnect/api/domain/validation/ValidationResult.java b/api/src/main/java/de/fitko/fitconnect/api/domain/validation/ValidationResult.java new file mode 100644 index 0000000000000000000000000000000000000000..9c52d861485b04bf31e73c9ffb9fca61f4455b7d --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/domain/validation/ValidationResult.java @@ -0,0 +1,39 @@ +package de.fitko.fitconnect.api.domain.validation; + +/** + * Wrapper for validations including an exception + */ +public class ValidationResult { + + private final boolean isValid; + private Exception error; + + private ValidationResult(final boolean isValid) { + this.isValid = isValid; + } + + private ValidationResult(final boolean isValid, final Exception exception) { + this.isValid = isValid; + this.error = exception; + } + + public static ValidationResult ok() { + return new ValidationResult(true); + } + + public static ValidationResult error(final Exception exception) { + return new ValidationResult(false, exception); + } + + public boolean isValid() { + return this.isValid; + } + + public boolean hasError() { + return !isValid || error != null; + } + + public Exception getError() { + return this.error; + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/AttachmentCreationException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/AttachmentCreationException.java new file mode 100644 index 0000000000000000000000000000000000000000..64bb8ac02dac6afb5ea278e3e4d1e48cb54cb720 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/AttachmentCreationException.java @@ -0,0 +1,8 @@ +package de.fitko.fitconnect.api.exceptions; + +public class AttachmentCreationException extends RuntimeException { + + public AttachmentCreationException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/AttachmentUploadException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/AttachmentUploadException.java new file mode 100644 index 0000000000000000000000000000000000000000..f83dee7797b52217b6cea053de53d761e85fffd9 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/AttachmentUploadException.java @@ -0,0 +1,8 @@ +package de.fitko.fitconnect.api.exceptions; + +public class AttachmentUploadException extends RuntimeException { + + public AttachmentUploadException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/AuthenticationException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/AuthenticationException.java new file mode 100644 index 0000000000000000000000000000000000000000..560737265afc09302ab155f1746544604ca45ecc --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/AuthenticationException.java @@ -0,0 +1,8 @@ +package de.fitko.fitconnect.api.exceptions; + +public class AuthenticationException extends RuntimeException { + + public AuthenticationException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/ClientNotAuthenticatedException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/ClientNotAuthenticatedException.java new file mode 100644 index 0000000000000000000000000000000000000000..70ed2b4fb8731faf0c0a609db32f3f2da2b37771 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/ClientNotAuthenticatedException.java @@ -0,0 +1,13 @@ +package de.fitko.fitconnect.api.exceptions; + +public class ClientNotAuthenticatedException extends RuntimeException { + + + public ClientNotAuthenticatedException(final String errorMessage) { + super(errorMessage); + } + + public ClientNotAuthenticatedException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/DecryptionException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/DecryptionException.java new file mode 100644 index 0000000000000000000000000000000000000000..dcaadebe61442569ecec9d827b6c71b1611e7a28 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/DecryptionException.java @@ -0,0 +1,12 @@ +package de.fitko.fitconnect.api.exceptions; + +public class DecryptionException extends RuntimeException { + + public DecryptionException(final String errorMessage) { + super(errorMessage); + } + + public DecryptionException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/EncryptionException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/EncryptionException.java new file mode 100644 index 0000000000000000000000000000000000000000..664a3ba0a0b95cb90b53d5a60ea1ba50b00be512 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/EncryptionException.java @@ -0,0 +1,13 @@ +package de.fitko.fitconnect.api.exceptions; + +public class EncryptionException extends RuntimeException { + + public EncryptionException(final String errorMessage) { + super(errorMessage); + } + + public EncryptionException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} + diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/InitializationException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/InitializationException.java new file mode 100644 index 0000000000000000000000000000000000000000..cdb9be9134d90049d488b0bb4334c71d16f09699 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/InitializationException.java @@ -0,0 +1,12 @@ +package de.fitko.fitconnect.api.exceptions; + +public class InitializationException extends RuntimeException { + + public InitializationException(final String errorMessage) { + super(errorMessage); + } + + public InitializationException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/InvalidPublicKeyException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/InvalidPublicKeyException.java new file mode 100644 index 0000000000000000000000000000000000000000..0ab43a95394d59aa878c73dce249d5141c7f50bd --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/InvalidPublicKeyException.java @@ -0,0 +1,8 @@ +package de.fitko.fitconnect.api.exceptions; + +public class InvalidPublicKeyException extends RuntimeException { + + public InvalidPublicKeyException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/KeyNotRetrievedException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/KeyNotRetrievedException.java new file mode 100644 index 0000000000000000000000000000000000000000..15499532d2f081ae067a5b68c01ea8ada87a5ffe --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/KeyNotRetrievedException.java @@ -0,0 +1,8 @@ +package de.fitko.fitconnect.api.exceptions; + +public class KeyNotRetrievedException extends RuntimeException { + + public KeyNotRetrievedException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/MetadataNotCreatedException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/MetadataNotCreatedException.java new file mode 100644 index 0000000000000000000000000000000000000000..a045c1dac26dce205414d683ac886f9da8da68b4 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/MetadataNotCreatedException.java @@ -0,0 +1,8 @@ +package de.fitko.fitconnect.api.exceptions; + +public class MetadataNotCreatedException extends RuntimeException { + + public MetadataNotCreatedException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/SubmissionNotCreatedException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/SubmissionNotCreatedException.java new file mode 100644 index 0000000000000000000000000000000000000000..9487409e6b750a4ab5e8975d5f42d20b5bf667df --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/SubmissionNotCreatedException.java @@ -0,0 +1,8 @@ +package de.fitko.fitconnect.api.exceptions; + +public class SubmissionNotCreatedException extends RuntimeException { + + public SubmissionNotCreatedException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/SubmissionNotSentException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/SubmissionNotSentException.java new file mode 100644 index 0000000000000000000000000000000000000000..63dfd3aac77564fe8fe8f06c3183aefd9d8ee840 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/SubmissionNotSentException.java @@ -0,0 +1,8 @@ +package de.fitko.fitconnect.api.exceptions; + +public class SubmissionNotSentException extends RuntimeException { + + public SubmissionNotSentException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/exceptions/ValidationException.java b/api/src/main/java/de/fitko/fitconnect/api/exceptions/ValidationException.java new file mode 100644 index 0000000000000000000000000000000000000000..76c364c3b38e29ba5292eabb5b29002f98d2c787 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/exceptions/ValidationException.java @@ -0,0 +1,13 @@ +package de.fitko.fitconnect.api.exceptions; + +public class ValidationException extends RuntimeException { + + + public ValidationException(final String errorMessage) { + super(errorMessage); + } + + public ValidationException(final String errorMessage, final Throwable error) { + super(errorMessage, error); + } +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/package-info.java b/api/src/main/java/de/fitko/fitconnect/api/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..53cbfecdec53d0b908f555af59e30104254cd417 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/package-info.java @@ -0,0 +1,11 @@ +/** + * This module is providing the internal sdk api + * <p> + * + * </p> + * + * @since 1.0 + * @author FitConnect + * @version 1.0 + */ +package de.fitko.fitconnect.api; \ No newline at end of file diff --git a/api/src/main/java/de/fitko/fitconnect/api/services/Sender.java b/api/src/main/java/de/fitko/fitconnect/api/services/Sender.java new file mode 100644 index 0000000000000000000000000000000000000000..52cedd8f6497005e2a14ded257f8b496d007ceb5 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/services/Sender.java @@ -0,0 +1,148 @@ +package de.fitko.fitconnect.api.services; + +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.RSAKey; +import de.fitko.fitconnect.api.domain.auth.OAuthToken; +import de.fitko.fitconnect.api.domain.model.metadata.Metadata; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.metadata.data.Data; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionSubmit; +import de.fitko.fitconnect.api.domain.validation.ValidationResult; +import de.fitko.fitconnect.api.exceptions.*; + +import java.io.File; +import java.util.List; +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 SubmissionSubmit} including encrypted {@link Data} and {@link Attachment}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(final JWK publicKey); + + /** + * Encrypts the submission data payload (json or xml) with JWE (JSON-Web-Encryption). + * + * @param publicKey the public encryption key for encryption of {@link Data} + * @param data the {@link Data} payload that should be encrypted + * @return the JWE-encrypted {@link Data} + * + * @throws EncryptionException if the encryption fails due to an invalid key or corrupt data + */ + Data encryptSubmissionData(final RSAKey publicKey, final Data data) throws EncryptionException; + + /** + * Encrypts the submission attachment data with JWE (JSON-Web-Encryption). + * + * @param publicKey the public encryption key for encryption of {@link Attachment} + * @param attachment the {@link Attachment} that should be encrypted + * @return the JWE-encrypted {@link Attachment} + * + * @throws EncryptionException + */ + Attachment encryptAttachment(final RSAKey publicKey, final Attachment attachment) throws EncryptionException; + + /** + * Authenticates the sender against the FIT-Connect API and retrieves an authentication token + * + * @param clientId a unique identifier within the FIT-Connect platform environment + * @param clientSecret a secret that is only known by the application and the OAuth server + * @param scope OAuth scope(s) that determine if a submission is accepted by the client + * @return {@link OAuthToken} that holds the access-token + * + * @throws {@link AuthenticationException} if an error occurred, including the original cause + */ + OAuthToken retrieveOAuthToken(final String clientId, final String clientSecret, final String... scope) throws AuthenticationException; + + /** + * Creates and uploads a {@link Metadata} that contains {@link Data}, {@link Attachment} and their hashes so a subscriber + * can verify the integrity. + * + * @param data {@link Data} object of the json or xml payload + * @param attachments a list of {@link Attachment}s + * @return a valid {@link Metadata} object with hashed {@link Data} and {@link Attachment}s + */ + Metadata createMetadata(final Data data, final List<Attachment> attachments) throws MetadataNotCreatedException; + + /** + * Creates and uploads a {@link Metadata} that contains {@link Data}, {@link Attachment} and their hashes so a subscriber + * can verify the integrity. + * + * @param attachments a list of {@link Attachment}s + * @return a valid {@link Metadata} object with hashed {@link Data} and {@link Attachment}s + * + * @throws MetadataNotCreatedException if there was an error during the upload or on creation of the metadata object + */ + Metadata createMetadata(final List<Attachment> attachments) throws MetadataNotCreatedException; + + /** + * Creates and announces a new {@link SubmissionSubmit}. + * + * @param submission with a destinationId, a list of attachmentIds and a serviceType + * @return the announced submission + * + * @throws SubmissionNotCreatedException if the announcement of the submission failed, e.g. due to a technical problem + */ + SubmissionForPickup createSubmission(SubmissionSubmit submission) throws SubmissionNotCreatedException; + + /** + * Posts the announced {@link SubmissionSubmit} + * + * @param submissionId unique identifier of the announced submission + * @param encryptedMetadata the encrypted {@link Metadata} + * @return the submission that has been submitted + * + * @throws SubmissionNotSentException if the sending process failed + */ + SubmissionForPickup sendSubmission(UUID submissionId, Metadata encryptedMetadata) throws SubmissionNotSentException; + + /** + * Uploads encrypted {@link Attachment}s to for given submission id. + * + * @param submissionId unique identifier of the submission + * @param encryptedAttachments list of {@link Attachment}s + * + * @throws AttachmentUploadException + */ + void uploadAttachments(UUID submissionId, List<Attachment> encryptedAttachments) throws AttachmentUploadException; + + /** + * Creates a new {@link Attachment} from a given file. + * + * @param file file containing the {@link Attachment} payload. + * @return an {@link Attachment} that contains the hashed file content as string + * + * @throws AttachmentCreationException if the file could not be loaded + */ + Attachment createAttachment(File file) throws AttachmentCreationException; + + /** + * Retrieves the public encryption key for a destination. + * + * @param destinationId unique identifier of the destination + * @return the public key for encrypting {@link Metadata}, {@link Data} and {@link Attachment}s + * + * @throws KeyNotRetrievedException if a technical problem occurred or the destination id is not valid + */ + RSAKey getEncryptionKeyForDestination(UUID destinationId) throws KeyNotRetrievedException; +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/services/Subscriber.java b/api/src/main/java/de/fitko/fitconnect/api/services/Subscriber.java new file mode 100644 index 0000000000000000000000000000000000000000..e42e89c3b1ece3d162b2f47f0e6891be6687aed0 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/services/Subscriber.java @@ -0,0 +1,110 @@ +package de.fitko.fitconnect.api.services; + +import com.nimbusds.jose.jwk.RSAKey; +import de.fitko.fitconnect.api.domain.auth.OAuthToken; +import de.fitko.fitconnect.api.domain.model.metadata.Metadata; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.metadata.data.Data; +import de.fitko.fitconnect.api.domain.model.submission.Submission; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionSubmit; +import de.fitko.fitconnect.api.domain.validation.ValidationResult; +import de.fitko.fitconnect.api.exceptions.AuthenticationException; + +import java.util.List; +import java.util.UUID; + +/** + * A technical system that accepts submissions on the administration side. + * + * @see <a href="https://docs.fitko.de/fit-connect/docs/receiving/overview">Receiving Submissions</a> + */ +public interface Subscriber { + + /** + * Authenticates the subscriber against the FIT-Connect API and retrieves an authentication token + * + * @param clientId a unique identifier within the FIT-Connect platform environment + * @param clientSecret a secret that is only known by the application and the OAuth server + * @param scope OAuth scope(s) that determine if a submission is accepted by the client + * @return {@link OAuthToken} that holds the access-token + * + * @throws {@link AuthenticationException} if an error occurred, including the original cause + */ + OAuthToken retrieveOAuthToken(final String clientId, final String clientSecret, final String... scope); + + /** + * Decrypts JWE-encrypted string data. + * + * @param privateKey the private key to decrypt the JWE-encrypted string + * @param encryptedContent the content that should be decrypted + * @return the decrypted content as byte[] + */ + byte[] decryptStringContent(final RSAKey privateKey, final String encryptedContent); + + /** + * Polls available {@link SubmissionSubmit}s for a given destinationId. + * + * @param destinationId restricts the query to a specific destination + * @param limit number of submissions in result (max. is 500) + * @param offset position in the dataset + * @return list of found {@link SubmissionSubmit}s + */ + List<SubmissionForPickup> pollAvailableSubmissions(UUID destinationId, int limit, int offset); + + /** + * Gets a {@link SubmissionSubmit}. + * + * @param submissionId the unique identifier of a {@link SubmissionSubmit} + * @return the requested {@link Submission} + */ + Submission getSubmission(UUID submissionId); + + /** + * Loads a list of {@link Attachment}s for a {@link Submission}. + * + * @param submissionId the unique identifier of a {@link Submission} + * @param attachmentIds a list of {@link Attachment} UUIDs + * @return list of {@link Attachment} objects + */ + List<Attachment> fetchAttachments(UUID submissionId, List<UUID> attachmentIds); + + /** + * Validates the structure and signature of a Security-Event-Token (SET). + * + * @param caseId unique identifier of a {@link Submission}s case + * @returna {@link ValidationResult}, contains an error if the SET is invalid + */ + ValidationResult validateEventLog(UUID caseId); + + /** + * Validates the {@link Metadata} structure against a given JSON-schema to ensure its correctness. + * + * @param metadata the {@link Metadata} object that is validated + * @return a {@link ValidationResult}, contains an error if the {@link Metadata} is invalid or doesn't match the schema + */ + ValidationResult validateMetadata(Metadata metadata, String schema); + + /** + * Validates the {@link Data}. + * + * @param data the received data + * @return a {@link ValidationResult}, contains an error if the {@link Data} is invalid + */ + ValidationResult validateData(Data data); + + /** + * Validates the {@link Attachment}s. + * + * @param attachments the received list of attachments + * @return a {@link ValidationResult}, contains an error if the {@link Attachment}s are invalid + */ + ValidationResult validateAttachments(List<Attachment> attachments); + + /** + * Sends a confirmation event if the received submission matches all validations. + * + * @see <a href="https://docs.fitko.de/fit-connect/docs/receiving/process-and-acknowledge">Process And Acknowledge</a> + */ + void confirmValidSubmission(); +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/services/auth/OAuthService.java b/api/src/main/java/de/fitko/fitconnect/api/services/auth/OAuthService.java new file mode 100644 index 0000000000000000000000000000000000000000..680736e1684c0c901b78510a4e9102ae84d34941 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/services/auth/OAuthService.java @@ -0,0 +1,27 @@ +package de.fitko.fitconnect.api.services.auth; + +import de.fitko.fitconnect.api.domain.auth.OAuthToken; +import de.fitko.fitconnect.api.domain.model.submission.Submission; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionSubmit; +import de.fitko.fitconnect.api.exceptions.AuthenticationException; + +/** + * A service that provides an interface to authenticate against the Fit-Connect API in order + * to send a {@link SubmissionSubmit} or to receive a {@link Submission}. + * + * @see <a href="https://docs.fitko.de/fit-connect/docs/getting-started/authentication">Fit-Connect documentation on authentication</a> + */ +public interface OAuthService { + + /** + * Authenticates the sender/subscriber against the FIT-Connect API and retrieves an authentication token + * + * @param clientId a unique identifier within the FIT-Connect platform environment + * @param clientSecret a secret that is only known by the application and the OAuth server + * @param scope OAuth scope(s) that determine if a submission is accepted by the client + * @return {@link OAuthToken} that holds the access-token + * + * @throws {@link AuthenticationException} if an error occurred, including the original cause + */ + OAuthToken authenticate(String clientId, String clientSecret, String... scope) throws AuthenticationException; +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/services/crypto/CryptoService.java b/api/src/main/java/de/fitko/fitconnect/api/services/crypto/CryptoService.java new file mode 100644 index 0000000000000000000000000000000000000000..e66f3a1eb125cdab635d56efd43e51c82e392b2e --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/services/crypto/CryptoService.java @@ -0,0 +1,61 @@ +package de.fitko.fitconnect.api.services.crypto; + +import com.nimbusds.jose.jwk.RSAKey; +import de.fitko.fitconnect.api.domain.model.metadata.data.Data; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionSubmit; +import de.fitko.fitconnect.api.exceptions.DecryptionException; +import de.fitko.fitconnect.api.exceptions.EncryptionException; + +/** + * A service that allows to encrypt and decrypt {@link Data} and {@link Attachment}s of a {@link SubmissionSubmit} + * 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 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. + * + * @param privateKey RSA private key for decryption of JWE + * @param encryptedData serialized encrypted JWE string + * @return decrypted JWE byte array + * + * @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 publicKey RSA public key for encryption of string payload + * @param data json or xml data that should be encrypted + * @return string serialization of the encrypted JWE object + * + * @throws EncryptionException if the payload cannot be encrypted or there was an issue with the key + */ + String encryptString(RSAKey publicKey, String data) throws EncryptionException; + + /** + * Encrypts a byte[] payload with the given public key. + * + * @param publicKey RSA public key the payload is encrypted with + * @param bytes byte[] of the data that should be encrypted + * @return string serialization of the encrypted JWE object + * + * @throws EncryptionException if the payload cannot be encrypted or there was an issue with the key + */ + String encryptBytes(RSAKey publicKey, byte[] bytes) throws EncryptionException; +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/services/metadata/MetadataService.java b/api/src/main/java/de/fitko/fitconnect/api/services/metadata/MetadataService.java new file mode 100644 index 0000000000000000000000000000000000000000..ecd00d957d83c4fa3949208db4abbd8240f507c3 --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/services/metadata/MetadataService.java @@ -0,0 +1,34 @@ +package de.fitko.fitconnect.api.services.metadata; + +import de.fitko.fitconnect.api.domain.model.metadata.Metadata; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.metadata.data.Data; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionSubmit; + +import java.util.List; + +/** + * Provides the generation of correct and valid {@link Metadata} for a {@link SubmissionSubmit} + * + * @see + * <a href="https://docs.fitko.de/fit-connect/docs/metadataoverview">Metadata</a> and + * <a href="https://docs.fitko.de/fit-connect/docs/getting-started/submission/structure">Structure of a submission</a> + */ +public interface MetadataService { + + /** + * Generates valid metadata for a submission including its JWE-encrypted data and attachments. + * + * @param data the payload (json or xml) + * @param attachments list of 0..n attachments as binary data + * @return {@link Metadata} with hashes + */ + Metadata createMetadata(final Data data, final List<Attachment> attachments); + + /** + * Uploads metadata to FIT-Connect API. + * + * @param metadata generated with {@link #createMetadata(Data, List)} + */ + void uploadMetadata(final Metadata metadata); +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/services/validation/CertificateValidator.java b/api/src/main/java/de/fitko/fitconnect/api/services/validation/CertificateValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..004589da7112c2a71313620d66d05581e5fb414d --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/services/validation/CertificateValidator.java @@ -0,0 +1,28 @@ +package de.fitko.fitconnect.api.services.validation; + +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.RSAKey; +import de.fitko.fitconnect.api.domain.validation.ValidationResult; + +/** + * Validator for publicKeys and certificate chains. + * + * @see <a href="https://docs.fitko.de/fit-connect/docs/sending/encrypt/#certificateValidation">Certificate Validation</a> + */ +public interface CertificateValidator { + + /** + * 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(final JWK publicKey); +} diff --git a/api/src/main/java/de/fitko/fitconnect/api/services/validation/MetadataValidator.java b/api/src/main/java/de/fitko/fitconnect/api/services/validation/MetadataValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..d61f1fbbd6bb5ec233cc536ff87f087be2b069be --- /dev/null +++ b/api/src/main/java/de/fitko/fitconnect/api/services/validation/MetadataValidator.java @@ -0,0 +1,36 @@ +package de.fitko.fitconnect.api.services.validation; + +import de.fitko.fitconnect.api.domain.model.metadata.Metadata; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.metadata.data.Data; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionSubmit; +import de.fitko.fitconnect.api.domain.validation.ValidationResult; + +/** + * A validator that ensures the integrity of the transferred {@link Metadata} of a {@link SubmissionSubmit}. + * It provides the validation of schema and hash values. + * + * @see <a href="https://docs.fitko.de/fit-connect/docs/sending/metadata#integrity">Metadata Integrity</a> + */ +public interface MetadataValidator { + + /** + * Validates the metadata against a given schema. + * + * @param metadata the current metadata object that should be validated + * @param jsonSchema the schema the {@link Metadata} is validated against + * + * @return a {@link ValidationResult} with an optional error + */ + ValidationResult validateMetadataSchema(final Metadata metadata, final String jsonSchema); + + /** + * Checks if the message digest hashes of the metadata´s {@link Data} and {@link Attachment}s + * are correct. + * + * @param metadata the current {@link Metadata} object that should be validated + * + * @return a {@link ValidationResult} with an optional error + */ + ValidationResult validateMetadataHashValues(final Metadata metadata); +} diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 0000000000000000000000000000000000000000..de4e4420438c488bb0a6aefcd553346f169aa7e2 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,367 @@ +<?xml version="1.0"?> +<!DOCTYPE module PUBLIC + "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" + "https://checkstyle.org/dtds/configuration_1_3.dtd"> + +<!-- + Checkstyle configuration that checks the Google coding conventions from Google Java Style + that can be found at https://google.github.io/styleguide/javaguide.html + + Checkstyle is very configurable. Be sure to read the documentation at + http://checkstyle.org (or in your downloaded distribution). + + To completely disable a check, just comment it out or delete it from the file. + To suppress certain violations please review suppression filters. + + Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov. + --> + +<module name = "Checker"> + <property name="charset" value="UTF-8"/> + + <property name="severity" value="warning"/> + + <property name="fileExtensions" value="java, properties, xml"/> + <!-- Excludes all 'module-info.java' files --> + <!-- See https://checkstyle.org/config_filefilters.html --> + <module name="BeforeExecutionExclusionFileFilter"> + <property name="fileNamePattern" value="module\-info\.java$"/> + </module> + <!-- https://checkstyle.org/config_filters.html#SuppressionFilter --> + <module name="SuppressionFilter"> + <property name="file" value="${org.checkstyle.google.suppressionfilter.config}" + default="checkstyle-suppressions.xml" /> + <property name="optional" value="true"/> + </module> + + <!-- Checks for whitespace --> + <!-- See http://checkstyle.org/config_whitespace.html --> + <module name="FileTabCharacter"> + <property name="eachLine" value="true"/> + </module> + + <module name="LineLength"> + <property name="fileExtensions" value="java"/> + <property name="max" value="100"/> + <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/> + </module> + + <module name="TreeWalker"> + <module name="OuterTypeFilename"/> + <module name="IllegalTokenText"> + <property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/> + <property name="format" + value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/> + <property name="message" + value="Consider using special escape sequence instead of octal value or Unicode escaped value."/> + </module> + <module name="AvoidEscapedUnicodeCharacters"> + <property name="allowEscapesForControlCharacters" value="true"/> + <property name="allowByTailComment" value="true"/> + <property name="allowNonPrintableEscapes" value="true"/> + </module> + <module name="AvoidStarImport"/> + <module name="OneTopLevelClass"/> + <module name="NoLineWrap"> + <property name="tokens" value="PACKAGE_DEF, IMPORT, STATIC_IMPORT"/> + </module> + <module name="EmptyBlock"> + <property name="option" value="TEXT"/> + <property name="tokens" + value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/> + </module> + <module name="NeedBraces"> + <property name="tokens" + value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE"/> + </module> + <module name="LeftCurly"> + <property name="tokens" + value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF, + INTERFACE_DEF, LAMBDA, LITERAL_CASE, LITERAL_CATCH, LITERAL_DEFAULT, + LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, + LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF, + OBJBLOCK, STATIC_INIT, RECORD_DEF, COMPACT_CTOR_DEF"/> + </module> + <module name="RightCurly"> + <property name="id" value="RightCurlySame"/> + <property name="tokens" + value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, + LITERAL_DO"/> + </module> + <module name="RightCurly"> + <property name="id" value="RightCurlyAlone"/> + <property name="option" value="alone"/> + <property name="tokens" + value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, + INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, INTERFACE_DEF, RECORD_DEF, + COMPACT_CTOR_DEF"/> + </module> + <module name="SuppressionXpathSingleFilter"> + <!-- suppresion is required till https://github.com/checkstyle/checkstyle/issues/7541 --> + <property name="id" value="RightCurlyAlone"/> + <property name="query" value="//RCURLY[parent::SLIST[count(./*)=1] + or preceding-sibling::*[last()][self::LCURLY]]"/> + </module> + <module name="WhitespaceAfter"> + <property name="tokens" + value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE, LITERAL_RETURN, + LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, LITERAL_FINALLY, DO_WHILE, ELLIPSIS, + LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_CATCH, LAMBDA, + LITERAL_YIELD, LITERAL_CASE"/> + </module> + <module name="WhitespaceAround"> + <property name="allowEmptyConstructors" value="true"/> + <property name="allowEmptyLambdas" value="true"/> + <property name="allowEmptyMethods" value="true"/> + <property name="allowEmptyTypes" value="true"/> + <property name="allowEmptyLoops" value="true"/> + <property name="ignoreEnhancedForColon" value="false"/> + <property name="tokens" + value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, + BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND, + LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, + LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED, + LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, + NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR, + SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/> + <message key="ws.notFollowed" + value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks + may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/> + <message key="ws.notPreceded" + value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/> + </module> + <module name="OneStatementPerLine"/> + <module name="MultipleVariableDeclarations"/> + <module name="ArrayTypeStyle"/> + <module name="MissingSwitchDefault"/> + <module name="FallThrough"/> + <module name="UpperEll"/> + <module name="ModifierOrder"/> + <module name="EmptyLineSeparator"> + <property name="tokens" + value="PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF, + STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF, RECORD_DEF, + COMPACT_CTOR_DEF"/> + <property name="allowNoEmptyLineBetweenFields" value="true"/> + </module> + <module name="SeparatorWrap"> + <property name="id" value="SeparatorWrapDot"/> + <property name="tokens" value="DOT"/> + <property name="option" value="nl"/> + </module> + <module name="SeparatorWrap"> + <property name="id" value="SeparatorWrapComma"/> + <property name="tokens" value="COMMA"/> + <property name="option" value="EOL"/> + </module> + <module name="SeparatorWrap"> + <!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/259 --> + <property name="id" value="SeparatorWrapEllipsis"/> + <property name="tokens" value="ELLIPSIS"/> + <property name="option" value="EOL"/> + </module> + <module name="SeparatorWrap"> + <!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/258 --> + <property name="id" value="SeparatorWrapArrayDeclarator"/> + <property name="tokens" value="ARRAY_DECLARATOR"/> + <property name="option" value="EOL"/> + </module> + <module name="SeparatorWrap"> + <property name="id" value="SeparatorWrapMethodRef"/> + <property name="tokens" value="METHOD_REF"/> + <property name="option" value="nl"/> + </module> + <module name="PackageName"> + <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/> + <message key="name.invalidPattern" + value="Package name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="TypeName"> + <property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, + ANNOTATION_DEF, RECORD_DEF"/> + <message key="name.invalidPattern" + value="Type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="MemberName"> + <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/> + <message key="name.invalidPattern" + value="Member name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="ParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Parameter name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="LambdaParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="CatchParameterName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Catch parameter name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="LocalVariableName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Local variable name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="PatternVariableName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Pattern variable name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="ClassTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Class type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="RecordComponentName"> + <property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/> + <message key="name.invalidPattern" + value="Record component name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="RecordTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Record type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="MethodTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Method type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="InterfaceTypeParameterName"> + <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/> + <message key="name.invalidPattern" + value="Interface type name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="NoFinalizer"/> + <module name="GenericWhitespace"> + <message key="ws.followed" + value="GenericWhitespace ''{0}'' is followed by whitespace."/> + <message key="ws.preceded" + value="GenericWhitespace ''{0}'' is preceded with whitespace."/> + <message key="ws.illegalFollow" + value="GenericWhitespace ''{0}'' should followed by whitespace."/> + <message key="ws.notPreceded" + value="GenericWhitespace ''{0}'' is not preceded with whitespace."/> + </module> + <module name="Indentation"> + <property name="basicOffset" value="2"/> + <property name="braceAdjustment" value="2"/> + <property name="caseIndent" value="2"/> + <property name="throwsIndent" value="4"/> + <property name="lineWrappingIndentation" value="4"/> + <property name="arrayInitIndent" value="2"/> + </module> + <module name="AbbreviationAsWordInName"> + <property name="ignoreFinal" value="false"/> + <property name="allowedAbbreviationLength" value="0"/> + <property name="tokens" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF, + PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF, PATTERN_VARIABLE_DEF, RECORD_DEF, + RECORD_COMPONENT_DEF"/> + </module> + <module name="NoWhitespaceBeforeCaseDefaultColon"/> + <module name="OverloadMethodsDeclarationOrder"/> + <module name="VariableDeclarationUsageDistance"/> + <module name="CustomImportOrder"> + <property name="sortImportsInGroupAlphabetically" value="true"/> + <property name="separateLineBetweenGroups" value="true"/> + <property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/> + <property name="tokens" value="IMPORT, STATIC_IMPORT, PACKAGE_DEF"/> + </module> + <module name="MethodParamPad"> + <property name="tokens" + value="CTOR_DEF, LITERAL_NEW, METHOD_CALL, METHOD_DEF, + SUPER_CTOR_CALL, ENUM_CONSTANT_DEF, RECORD_DEF"/> + </module> + <module name="NoWhitespaceBefore"> + <property name="tokens" + value="COMMA, SEMI, POST_INC, POST_DEC, DOT, + LABELED_STAT, METHOD_REF"/> + <property name="allowLineBreaks" value="true"/> + </module> + <module name="ParenPad"> + <property name="tokens" + value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF, + EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW, + LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL, + METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA, + RECORD_DEF"/> + </module> + <module name="OperatorWrap"> + <property name="option" value="NL"/> + <property name="tokens" + value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, + LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF, + TYPE_EXTENSION_AND "/> + </module> + <module name="AnnotationLocation"> + <property name="id" value="AnnotationLocationMostCases"/> + <property name="tokens" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, + RECORD_DEF, COMPACT_CTOR_DEF"/> + </module> + <module name="AnnotationLocation"> + <property name="id" value="AnnotationLocationVariables"/> + <property name="tokens" value="VARIABLE_DEF"/> + <property name="allowSamelineMultipleAnnotations" value="true"/> + </module> + <module name="NonEmptyAtclauseDescription"/> + <module name="InvalidJavadocPosition"/> + <module name="JavadocTagContinuationIndentation"/> + <module name="SummaryJavadoc"> + <property name="forbiddenSummaryFragments" + value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/> + </module> + <module name="JavadocParagraph"/> + <module name="RequireEmptyLineBeforeBlockTagGroup"/> + <module name="AtclauseOrder"> + <property name="tagOrder" value="@param, @return, @throws, @deprecated"/> + <property name="target" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/> + </module> + <module name="JavadocMethod"> + <property name="accessModifiers" value="public"/> + <property name="allowMissingParamTags" value="true"/> + <property name="allowMissingReturnTag" value="true"/> + <property name="allowedAnnotations" value="Override, Test"/> + <property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, COMPACT_CTOR_DEF"/> + </module> + <module name="MissingJavadocMethod"> + <property name="scope" value="public"/> + <property name="minLineCount" value="2"/> + <property name="allowedAnnotations" value="Override, Test"/> + <property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, + COMPACT_CTOR_DEF"/> + </module> + <module name="MissingJavadocType"> + <property name="scope" value="protected"/> + <property name="tokens" + value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, + RECORD_DEF, ANNOTATION_DEF"/> + <property name="excludeScope" value="nothing"/> + </module> + <module name="MethodName"> + <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/> + <message key="name.invalidPattern" + value="Method name ''{0}'' must match pattern ''{1}''."/> + </module> + <module name="SingleLineJavadoc"/> + <module name="EmptyCatchBlock"> + <property name="exceptionVariableName" value="expected"/> + </module> + <module name="CommentsIndentation"> + <property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/> + </module> + <!-- https://checkstyle.org/config_filters.html#SuppressionXpathFilter --> + <module name="SuppressionXpathFilter"> + <property name="file" value="${org.checkstyle.google.suppressionxpathfilter.config}" + default="checkstyle-xpath-suppressions.xml" /> + <property name="optional" value="true"/> + </module> + </module> +</module> diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6a64f768b6856bc6e7ef10711be18f58d70b229d --- /dev/null +++ b/client/README.md @@ -0,0 +1,100 @@ +## Client Module + +### API Flow + +The ClientFactory provides fluent API clients both for **Sender** and **Subscriber**. + +As the flow chart below shows, the fluent client guides through all essential calls in order to hand in a correct +**Submission** as well as receive submissions on the subscriber side. + +#### Api client flow for sending a submission + +For the actual sender client those calls look like this: + + +<table> +<tr> +<th>Workflow</th> +<th>Java sample calls</th> +</tr> +<tr> +<td> + +```mermaid + +flowchart TD + +A[Create Client] --> B(Provide DestinationID) +B -->|next| C[Add Attachments] +C -->|send| D[SubmissionForPickup] +C -->|next| E[Add Data] +E -->|send| D[SubmissionForPickup] +``` + +</td> +<td> + +```java + +List<File> attachments... + +// Send without data + ClientFactory.senderClient() + .withDestination(UUID.randomUUID()) + .withAttachments(attachments) + .submit(); + +// Send with data + ClientFactory.senderClient() + .withDestination(UUID.randomUUID()) + .withAttachments(attachments) + .withData("some json or xml content") + .submit(); +``` + +</td> +</tr> +</table> + +#### Api client flow for subscribing to a submission + +<table> +<tr> +<th>Workflow</th> +<th>Java sample calls</th> +</tr> +<tr> +<td> + +```mermaid +flowchart TD + +A[Create Client] --> B(Poll List ofAvailable Submissions) +A[Create Client] --> C(Request Submission by ID) +B -->|next| C[Request Submission by ID] +C -->|get| D[Attachments] +C -->|get| E[Metadata] +C -->|get| F[Data] + +``` + +</td> +<td> + +```java + + var client = ClientFactory.subscriberClient(); + + var submissions = client.getAvailableSubmissions(destinationId); + // filter submission list for requested one ... + + var submission = client.requestSubmission(submissionId) + submission.getAttachments(); + submission.getMetadata(); + submission.getData(); +``` + +</td> +</tr> +</table> + diff --git a/client/pom.xml b/client/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..687f198f6c781d120b2b1aa888237a7be92cddfe --- /dev/null +++ b/client/pom.xml @@ -0,0 +1,41 @@ +<?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>de.fitko.fitconnect.sdk</groupId> + <version>1.0-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>client</artifactId> + + <dependencies> + <dependency> + <groupId>de.fitko.fitconnect.sdk</groupId> + <artifactId>api</artifactId> + </dependency> + <dependency> + <groupId>de.fitko.fitconnect.sdk</groupId> + <artifactId>dependency</artifactId> + </dependency> + <dependency> + <groupId>com.google.inject</groupId> + <artifactId>guice</artifactId> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </dependency> + <dependency> + <groupId>com.beust</groupId> + <artifactId>jcommander</artifactId> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + </dependencies> + +</project> \ No newline at end of file diff --git a/client/src/main/java/de/fitko/fitconnect/TestRunner.java b/client/src/main/java/de/fitko/fitconnect/TestRunner.java new file mode 100644 index 0000000000000000000000000000000000000000..c530b55d6e41f8dd25fe6bca2e3befedb173df48 --- /dev/null +++ b/client/src/main/java/de/fitko/fitconnect/TestRunner.java @@ -0,0 +1,52 @@ +package de.fitko.fitconnect; + +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import de.fitko.fitconnect.client.factory.ClientFactory; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public class TestRunner { + + public static void main(final String[] args) { + senderSample(); + subscriberSample(); + } + + private static void senderSample() { + final var destinationId = UUID.randomUUID(); + + // Without data + ClientFactory.senderClient() + .withDestination(destinationId) + .withAttachments(Collections.emptyList()) + .submit(); + + // With data + ClientFactory.senderClient() + .withDestination(destinationId) + .withAttachments(Collections.emptyList()) + .withData("some json or xml") + .submit(); + } + + private static void subscriberSample() { + + final var destinationId = UUID.randomUUID(); + + final var client = ClientFactory.subscriberClient(); + final var submissions = client.getAvailableSubmissions(destinationId); + final List<Attachment> attachments = client + .requestSubmission(getFirstSubmission(submissions)) + .getAttachments(); + } + + private static UUID getFirstSubmission(final List<SubmissionForPickup> submissions) { + final Optional<SubmissionForPickup> submission = submissions.stream().findFirst(); + return submission.isPresent() ? submission.get().getSubmissionId() : null; + } + +} diff --git a/client/src/main/java/de/fitko/fitconnect/client/SenderClient.java b/client/src/main/java/de/fitko/fitconnect/client/SenderClient.java new file mode 100644 index 0000000000000000000000000000000000000000..df433d8f4f922bf24f65378a384ce022cb720080 --- /dev/null +++ b/client/src/main/java/de/fitko/fitconnect/client/SenderClient.java @@ -0,0 +1,195 @@ +package de.fitko.fitconnect.client; + +import com.nimbusds.jose.jwk.RSAKey; +import de.fitko.fitconnect.api.domain.auth.OAuthToken; +import de.fitko.fitconnect.api.domain.model.metadata.Metadata; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.metadata.data.Data; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionSubmit; +import de.fitko.fitconnect.api.domain.validation.ValidationResult; +import de.fitko.fitconnect.api.exceptions.AttachmentUploadException; +import de.fitko.fitconnect.api.exceptions.ClientNotAuthenticatedException; +import de.fitko.fitconnect.api.exceptions.EncryptionException; +import de.fitko.fitconnect.api.exceptions.InvalidPublicKeyException; +import de.fitko.fitconnect.api.services.Sender; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * A fluent client for announcing and handing in a {@link SubmissionSubmit} + */ +public class SenderClient { + + private SenderClient() { + } + + public static WithDestination build(final Sender sender, final String clientId, final String secret) { + return new ClientBuilder(sender, clientId, secret); + } + + public interface WithDestination { + + /** + * Configures the client for the given destination and loads the public key + * + * @param destinationId unique identifier of the clients destination + * @return the upload step for attachments + */ + WithAttachments withDestination(UUID destinationId); + } + + public interface WithAttachments { + + /** + * Sends the submission with a list of attachments + * + * @param attachments that are sent with the submission + * @return the step where additional data can be added to the submission + */ + WithData withAttachments(List<File> attachments); + } + + public interface WithData { + + /** + * Data as string. + * + * @param data json or xml as string + * @return next step to submit the data + */ + Submit withData(String data); + + /** + * Data as byte array. + * + * @param data json or xml as byte array + * @return next step to submit the data + */ + Submit withData(byte[] data); + + /** + * Send submission to FIT-Connect API. + * + * @return {@link SubmissionForPickup} object of the handed in submission + */ + SubmissionForPickup submit(); + } + + public interface Submit { + /** + * Send submission to FIT-Connect API. + * + * @return {@link SubmissionForPickup} object of the handed in submission + */ + SubmissionForPickup submit(); + } + + public static class ClientBuilder implements WithDestination, WithAttachments, WithData, Submit { + + private final Sender sender; + private final String clientId; + private final String secret; + + private UUID destinationId; + private Data data; + private List<Attachment> attachments; + private RSAKey encryptionKey; + private UUID submissionId; + + public ClientBuilder(final Sender sender, final String clientId, final String secret) { + this.sender = sender; + this.clientId = clientId; + this.secret = secret; + authenticate(); + } + + @Override + public WithAttachments withDestination(final UUID destinationId) { + final RSAKey encryptionKey = sender.getEncryptionKeyForDestination(destinationId); + final ValidationResult validationResult = sender.validatePublicKey(encryptionKey); + if (validationResult.hasError()) { + throw new InvalidPublicKeyException("Public encryption key is not valid", validationResult.getError()); + } + this.encryptionKey = encryptionKey; + return this; + } + + @Override + public Submit withData(final String data) { + return withData(data.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public Submit withData(final byte[] data) throws EncryptionException { + final var unencryptedData = Data.builder().build(); + final Data encryptedData = sender.encryptSubmissionData(encryptionKey, unencryptedData); + this.data = encryptedData; + return this; + } + + @Override + public WithData withAttachments(final List<File> attachmentFiles) throws AttachmentUploadException { + final List<Attachment> attachments = readFilesToAttachments(attachmentFiles); + final SubmissionForPickup submission = createSubmission(destinationId, attachments); + this.submissionId = submission.getSubmissionId(); + final List<Attachment> encryptedAttachments = encryptAttachments(encryptionKey, attachments); + sender.uploadAttachments(submissionId, encryptedAttachments); + return this; + } + + @Override + public SubmissionForPickup submit() { + return sender.sendSubmission(submissionId, getMetadata()); + } + + private Metadata getMetadata() { + if (this.data == null) { + return sender.createMetadata(attachments); + } + return sender.createMetadata(data, attachments); + } + + private List<Attachment> encryptAttachments(final RSAKey encryptionKey, final List<Attachment> attachments) { + return attachments.stream().map(attachment -> encryptAttachment(encryptionKey, attachment)).collect(Collectors.toList()); + } + + private Attachment encryptAttachment(final RSAKey encryptionKey, final Attachment attachment) { + return sender.encryptAttachment(encryptionKey, attachment); + } + + private SubmissionForPickup createSubmission(final UUID destinationId, final List<Attachment> attachments) { + final SubmissionSubmit request = SubmissionSubmit.builder() + .destinationId(destinationId) + .announcedAttachments(asListOfAttachmentsIds(attachments)) + .build(); + + return sender.createSubmission(request); + } + + private List<UUID> asListOfAttachmentsIds(final List<Attachment> attachments) { + return attachments.stream().map(attachment -> attachment.getAttachmentId()).collect(Collectors.toList()); + } + + private void authenticate() { + final OAuthToken oAuthToken = sender.retrieveOAuthToken(clientId, secret); + if (oAuthToken == null) { + throw new ClientNotAuthenticatedException("Client is not authenticated, please authenticate first"); + } + } + + private List<Attachment> readFilesToAttachments(final List<File> attachmentFiles) { + return attachmentFiles.stream() + .map(this::readFileToAttachment) + .collect(Collectors.toList()); + } + + private Attachment readFileToAttachment(final File file) { + return sender.createAttachment(file); + } + } +} \ No newline at end of file diff --git a/client/src/main/java/de/fitko/fitconnect/client/SubscriberClient.java b/client/src/main/java/de/fitko/fitconnect/client/SubscriberClient.java new file mode 100644 index 0000000000000000000000000000000000000000..c920fdade4722f740ba79c5e6d980b5fa387fd41 --- /dev/null +++ b/client/src/main/java/de/fitko/fitconnect/client/SubscriberClient.java @@ -0,0 +1,177 @@ +package de.fitko.fitconnect.client; + +import com.nimbusds.jose.jwk.RSAKey; +import de.fitko.fitconnect.api.domain.auth.OAuthToken; +import de.fitko.fitconnect.api.domain.model.metadata.Metadata; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.metadata.data.Data; +import de.fitko.fitconnect.api.domain.model.submission.Submission; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import de.fitko.fitconnect.api.domain.validation.ValidationResult; +import de.fitko.fitconnect.api.exceptions.ClientNotAuthenticatedException; +import de.fitko.fitconnect.api.exceptions.DecryptionException; +import de.fitko.fitconnect.api.exceptions.ValidationException; +import de.fitko.fitconnect.api.services.Subscriber; + +import java.text.ParseException; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * A fluent client for handing in a subscription for a {@link Subscriber} + */ +public class SubscriberClient { + + private SubscriberClient() { + } + + public static RequestSubmission builder(final Subscriber subscriber, final String clientId, final String clientSecret, final String privateKey) { + return new ClientBuilder(subscriber, clientId, clientSecret, privateKey); + } + + public interface RequestSubmission { + + /** + * Loads a list of available {@link SubmissionForPickup} that were submitted to the {@link Subscriber}. + * + * @param destinationId unique identifier for a destination + * @param limit number of submissions in result (max. is 500) + * @param offset position in the dataset + * + * @return list of available submissions for pickup + */ + List<SubmissionForPickup> getAvailableSubmissions(UUID destinationId, int offset, int limit); + + /** + * Loads a list of available {@link SubmissionForPickup} that were submitted to the {@link Subscriber}. + * + * @param destinationId unique identifier for a destination + * @return list of available submissions for pickup + */ + List<SubmissionForPickup> getAvailableSubmissions(UUID destinationId); + + /** + * Loads a single {@link SubmissionForPickup} by id. + * @param submissionId unique identifier of a {@link SubmissionForPickup} + * + * @return next step to load available {@link Metadata}, {@link Data} and {@link Attachment}s + */ + GetData requestSubmission(UUID submissionId); + } + + public interface GetData { + + /** + * Gets a {@link SubmissionForPickup}´s {@link Attachment}s + * @return list of {@link Attachment}s + */ + List<Attachment> getAttachments(); + + /** + * Gets a {@link SubmissionForPickup}´s {@link Metadata} + * @return the {@link Metadata} + */ + Metadata getMetadata(); + + /** + * Gets a {@link SubmissionForPickup}´s {@link Data} + * @return the {@link Data} + */ + Data getData(); + } + + public static class ClientBuilder implements RequestSubmission, GetData { + + private static final int DEFAULT_SUBMISSION_LIMIT = 100; + + private final Subscriber subscriber; + private final String clientId; + private final String secret; + private final String privateKey; + + public ClientBuilder(final Subscriber subscriber, final String clientId, final String clientSecret, final String privateKey) { + this.subscriber = subscriber; + this.clientId = clientId; + this.secret = clientSecret; + this.privateKey = privateKey; + authenticate(); + } + + @Override + public List<SubmissionForPickup> getAvailableSubmissions(final UUID destinationId) { + return getAvailableSubmissions(destinationId, 0, DEFAULT_SUBMISSION_LIMIT); + } + + @Override + public List<SubmissionForPickup> getAvailableSubmissions(final UUID destinationId, final int offset, final int limit) { + return subscriber.pollAvailableSubmissions(destinationId, offset, limit); + } + + @Override + public GetData requestSubmission(final UUID submissionId) { + + final Submission submission = subscriber.getSubmission(submissionId); + final List<Attachment> attachments = subscriber.fetchAttachments(submissionId, submission.getAttachments()); + final String encryptedData = submission.getEncryptedData(); + final String encryptedMetadata = submission.getEncryptedMetadata(); + + final RSAKey decryptionKey = parseDecryptionKey(privateKey); + + final byte[] decryptedSubmissionData = subscriber.decryptStringContent(decryptionKey, encryptedData); + final byte[] decryptedMetadata = subscriber.decryptStringContent(decryptionKey, encryptedMetadata); + // TODO generate objects metadata and data from byte[] + + final List<ValidationResult> validationsResults = Collections.emptyList(); + validationsResults.add(subscriber.validateEventLog(submission.getCaseId())); + validationsResults.add(subscriber.validateMetadata(getMetadata(), "schemaString")); + validationsResults.add(subscriber.validateData(getData())); + validationsResults.add(subscriber.validateAttachments(getAttachments())); + + final Optional<ValidationResult> validationFailed = findFailedValidation(validationsResults); + + if (validationFailed.isEmpty()) { + subscriber.confirmValidSubmission(); + return this; + } + + throw new ValidationException("Validation has errors, submission is not valid"); + } + + @Override + public List<Attachment> getAttachments() { + return null; + } + + @Override + public Metadata getMetadata() { + return null; + } + + @Override + public Data getData() { + return null; + } + + private RSAKey parseDecryptionKey(final String privateKey) { + try { + return RSAKey.parse(privateKey); + } catch (final ParseException e) { + throw new DecryptionException("Key could not be parsed", e); + } + } + + private Optional<ValidationResult> findFailedValidation(final List<ValidationResult> validationsResults) { + return validationsResults.stream().filter(result -> result.hasError()).findFirst(); + } + + private void authenticate() { + final OAuthToken oAuthToken = subscriber.retrieveOAuthToken(clientId, secret); + if (oAuthToken == null) { + throw new ClientNotAuthenticatedException("Client is not authenticated, please authenticate first"); + } + } + + } +} \ No newline at end of file diff --git a/client/src/main/java/de/fitko/fitconnect/client/cmd/CommandLineArgs.java b/client/src/main/java/de/fitko/fitconnect/client/cmd/CommandLineArgs.java new file mode 100644 index 0000000000000000000000000000000000000000..47cf079798e4c85df988cb6cdb5dbd8652a295f5 --- /dev/null +++ b/client/src/main/java/de/fitko/fitconnect/client/cmd/CommandLineArgs.java @@ -0,0 +1,21 @@ +package de.fitko.fitconnect.client.cmd; + +import com.beust.jcommander.Parameter; + +public class CommandLineArgs { + + @Parameter( + names = "--clientId", + description = "FitConnect OAuth clientId", + required = true + ) + public String clientId; + + @Parameter( + names = "--secret", + description = "FitConnect OAuth clientId", + required = true + ) + public String secret; + +} \ No newline at end of file diff --git a/client/src/main/java/de/fitko/fitconnect/client/cmd/CommandLineRunner.java b/client/src/main/java/de/fitko/fitconnect/client/cmd/CommandLineRunner.java new file mode 100644 index 0000000000000000000000000000000000000000..daf87507ec5ee3bf4e7a97dbd4da9c79e45e1f71 --- /dev/null +++ b/client/src/main/java/de/fitko/fitconnect/client/cmd/CommandLineRunner.java @@ -0,0 +1,24 @@ +package de.fitko.fitconnect.client.cmd; + +import com.beust.jcommander.JCommander; +import de.fitko.fitconnect.client.SenderClient; + +public class CommandLineRunner { + + public static void main(final String[] args) { + + final CommandLineArgs cmdArgs = new CommandLineArgs(); + final JCommander build = JCommander.newBuilder() + .addObject(cmdArgs) + .build(); + + build.parse(args); + + final var clientId = cmdArgs.clientId; + final var secret = cmdArgs.secret; + + // sample high -level- api calls to send a submission + //SenderClient.build(clientId, secret) + + } +} diff --git a/client/src/main/java/de/fitko/fitconnect/client/di/Client.java b/client/src/main/java/de/fitko/fitconnect/client/di/Client.java new file mode 100644 index 0000000000000000000000000000000000000000..95c3ef599e60827a9f2d4341dfcbfe37aa2c7b8e --- /dev/null +++ b/client/src/main/java/de/fitko/fitconnect/client/di/Client.java @@ -0,0 +1,36 @@ +package de.fitko.fitconnect.client.di; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.name.Names; +import de.fitko.fitconnect.dependency.SdkModule; + +/** + * Abstract client that bootstraps the sdk´s dependency module and provides access to services and named properties. + */ +public abstract class Client { + + private static final Injector injector = Guice.createInjector(new SdkModule()); + + /** + * Get implementation for a service interface. + * + * @param clazz class of interface + * @param <T> type of class interface + * @return implementation for the given service interface + */ + static <T> T getService(final Class<T> clazz){ + return injector.getInstance(clazz); + } + + /** + * Get configuration property. + * + * @param propertyName name of the config property + * @return value of the named config property + */ + static String getProperty(final String propertyName){ + return injector.getInstance(Key.get(String.class, Names.named(propertyName))); + } +} diff --git a/client/src/main/java/de/fitko/fitconnect/client/di/FluentSenderClient.java b/client/src/main/java/de/fitko/fitconnect/client/di/FluentSenderClient.java new file mode 100644 index 0000000000000000000000000000000000000000..9fca87139719a1c4de25d2284288920569853eb6 --- /dev/null +++ b/client/src/main/java/de/fitko/fitconnect/client/di/FluentSenderClient.java @@ -0,0 +1,200 @@ +package de.fitko.fitconnect.client.di; + +import com.nimbusds.jose.jwk.RSAKey; +import de.fitko.fitconnect.api.domain.auth.OAuthToken; +import de.fitko.fitconnect.api.domain.model.metadata.Metadata; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.metadata.data.Data; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionSubmit; +import de.fitko.fitconnect.api.domain.validation.ValidationResult; +import de.fitko.fitconnect.api.exceptions.ClientNotAuthenticatedException; +import de.fitko.fitconnect.api.services.Sender; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * A fluent client for announcing and handing in a {@link SubmissionSubmit} + */ +public class FluentSenderClient extends Client { + + private FluentSenderClient() { + } + + public static WithDestination build() { + final Sender sender = getService(Sender.class); + final String clientId = getProperty("clientId"); + final String secret = getProperty("clientSecret"); + return new ClientBuilder(sender, clientId, secret); + } + + public interface WithDestination { + + /** + * Configures the client for the given destination and loads the public key + * + * @param destinationId the clients destination + * @return the upload step for attachments + */ + WithAttachments withDestination(UUID destinationId); + + SubmissionForPickup send(UUID destinationId, List<File> attachments, String data); + + SubmissionForPickup send(UUID destinationId, List<File> attachments, byte[] data); + + SubmissionForPickup send(UUID destinationId, List<File> attachments); + } + + public interface WithAttachments { + + /** + * Sends the submission with a list of attachments + * + * @param attachments that are sent with the submission + * @return the step where additional data can be added to the submission + */ + WithData withAttachments(List<File> attachments); + } + + public interface WithData { + + Submit withData(String data); + + Submit withData(byte[] data); + + SubmissionForPickup submit(); + } + + public interface Submit { + SubmissionForPickup submit(); + } + + public static class ClientBuilder implements WithDestination, WithAttachments, WithData, Submit { + + private final Sender sender; + private final String clientId; + private final String secret; + + private UUID destinationId; + private Data data; + private List<Attachment> attachments; + private RSAKey encryptionKey; + private UUID submissionId; + private OAuthToken token; + + public ClientBuilder(final Sender sender, final String clientId, final String secret) { + this.sender = sender; + this.clientId = clientId; + this.secret = secret; + authenticate(); + } + + @Override + public WithAttachments withDestination(final UUID destinationId) { + final RSAKey encryptionKey = sender.getEncryptionKeyForDestination(destinationId); + final ValidationResult validationResult = sender.validatePublicKey(encryptionKey); + if (validationResult.hasError()) { + throw new RuntimeException("Public encryption key is not valid", validationResult.getError()); + } + this.encryptionKey = encryptionKey; + return this; + } + + @Override + public SubmissionForPickup send(final UUID destinationId, final List<File> attachments, final String data) { + return this.withDestination(destinationId) + .withAttachments(attachments) + .withData(data) + .submit(); + } + + @Override + public SubmissionForPickup send(final UUID destinationId, final List<File> attachments, final byte[] data) { + return this.withDestination(destinationId) + .withAttachments(attachments) + .withData(data) + .submit(); + } + + @Override + public SubmissionForPickup send(final UUID destinationId, final List<File> attachments) { + return this.withDestination(destinationId) + .withAttachments(attachments) + .submit(); + } + + @Override + public Submit withData(final String data) { + return withData(data.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public Submit withData(final byte[] data) { + final var unencryptedData = Data.builder().build(); + final Data encryptedData = sender.encryptSubmissionData(encryptionKey, unencryptedData); + this.data = encryptedData; + return this; + } + + @Override + public WithData withAttachments(final List<File> attachmentFiles) { + final List<Attachment> attachments = readFilesToAttachments(attachmentFiles); + final SubmissionForPickup submission = createSubmission(destinationId, attachments); + this.submissionId = submission.getSubmissionId(); + final List<Attachment> encryptedAttachments = encryptAttachments(encryptionKey, attachments); + sender.uploadAttachments(submissionId, encryptedAttachments); + return this; + } + + @Override + public SubmissionForPickup submit() { + return sender.sendSubmission(submissionId, getMetadata()); + } + + private Metadata getMetadata() { + return this.data == null ? sender.createMetadata(attachments) : sender.createMetadata(data, attachments); + } + + private List<Attachment> encryptAttachments(final RSAKey encryptionKey, final List<Attachment> attachments) { + return attachments.stream().map(attachment -> encryptAttachment(encryptionKey, attachment)).collect(Collectors.toList()); + } + + private Attachment encryptAttachment(final RSAKey encryptionKey, final Attachment attachment) { + return sender.encryptAttachment(encryptionKey, attachment); + } + + private SubmissionForPickup createSubmission(final UUID destinationId, final List<Attachment> attachments) { + final SubmissionSubmit request = SubmissionSubmit.builder() + .destinationId(destinationId) + .announcedAttachments(asListOfAttachmentsIds(attachments)) + .build(); + + return sender.createSubmission(request); + } + + private List<UUID> asListOfAttachmentsIds(final List<Attachment> attachments) { + return attachments.stream().map(attachment -> attachment.getAttachmentId()).collect(Collectors.toList()); + } + + private void authenticate() { + final OAuthToken oAuthToken = sender.retrieveOAuthToken(clientId, secret); + if (oAuthToken == null) { + throw new ClientNotAuthenticatedException("Could not authenticate client, please authenticate first"); + } + } + + private List<Attachment> readFilesToAttachments(final List<File> attachmentFiles) { + return attachmentFiles.stream() + .map(this::readFileToAttachment) + .collect(Collectors.toList()); + } + + private Attachment readFileToAttachment(final File file) { + return sender.createAttachment(file); + } + } +} \ No newline at end of file diff --git a/client/src/main/java/de/fitko/fitconnect/client/factory/ClientFactory.java b/client/src/main/java/de/fitko/fitconnect/client/factory/ClientFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..38b20a258c07ea4871509ff2a17f96c02e25b0f6 --- /dev/null +++ b/client/src/main/java/de/fitko/fitconnect/client/factory/ClientFactory.java @@ -0,0 +1,106 @@ +package de.fitko.fitconnect.client.factory; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigBeanFactory; +import com.typesafe.config.ConfigFactory; +import de.fitko.fitconnect.api.services.Sender; +import de.fitko.fitconnect.api.services.Subscriber; +import de.fitko.fitconnect.api.services.auth.OAuthService; +import de.fitko.fitconnect.api.services.crypto.CryptoService; +import de.fitko.fitconnect.api.services.metadata.MetadataService; +import de.fitko.fitconnect.api.services.validation.CertificateValidator; +import de.fitko.fitconnect.api.services.validation.MetadataValidator; +import de.fitko.fitconnect.client.SenderClient; +import de.fitko.fitconnect.client.SubscriberClient; +import de.fitko.fitconnect.core.SubmissionSender; +import de.fitko.fitconnect.core.SubmissionSubscriber; +import de.fitko.fitconnect.core.auth.DefaultOAuthService; +import de.fitko.fitconnect.core.crypto.JWECryptoService; +import de.fitko.fitconnect.core.http.ProxyConfig; +import de.fitko.fitconnect.core.metadata.MetadataUploadService; +import de.fitko.fitconnect.core.validation.KeyValidator; +import de.fitko.fitconnect.core.validation.MetadataSubmissionValidator; +import de.fitko.fitconnect.dependency.ApplicationConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.client.RestTemplate; + +import java.io.File; + +/** + * Factory that constructs clients for {@link Sender} and {@link Subscriber}. + */ +public class ClientFactory { + + private static final Logger logger = LoggerFactory.getLogger(ClientFactory.class); + + public static final String CONFIG_FILENAME = "sdk.conf"; + public static final String CONFIG_CONTEXT = "sdk"; + + private ClientFactory() { + } + + /** + * Create a new {@link SenderClient} that is configured via {@link ApplicationConfig}. + * + * @return the sender client + */ + public static SenderClient.WithDestination senderClient() { + final ApplicationConfig config = loadConfig(); + final RestTemplate restTemplate = getRestTemplate(config); + final Sender sender = getSender(config, restTemplate); + return SenderClient.build(sender, config.getClientId(), config.getClientSecret()); + } + + /** + * Create a new {@link SubscriberClient} that is configured via {@link ApplicationConfig}. + * + * @return the sender client + */ + public static SubscriberClient.RequestSubmission subscriberClient() { + final ApplicationConfig config = loadConfig(); + final RestTemplate restTemplate = getRestTemplate(config); + final Subscriber subscriber = getSubscriber(config, restTemplate); + return SubscriberClient.builder(subscriber, config.getClientId(), config.getClientSecret(), config.getPrivateKey()); + } + + private static Subscriber getSubscriber(final ApplicationConfig config, final RestTemplate restTemplate) { + final OAuthService oAuthService = getOAuthService(config, restTemplate); + final CryptoService cryptoService = getCryptoService(); + final MetadataValidator validator = new MetadataSubmissionValidator(); + return new SubmissionSubscriber(oAuthService, cryptoService, validator); + } + + private static Sender getSender(final ApplicationConfig config, final RestTemplate restTemplate) { + final OAuthService authService = getOAuthService(config, restTemplate); + final CryptoService cryptoService = getCryptoService(); + final CertificateValidator validator = new KeyValidator(); + final MetadataService metadataService = new MetadataUploadService(restTemplate); + return new SubmissionSender(authService, cryptoService, validator, metadataService); + } + + private static CryptoService getCryptoService() { + return new JWECryptoService(); + } + + private static OAuthService getOAuthService(final ApplicationConfig config, final RestTemplate restTemplate) { + return new DefaultOAuthService(restTemplate, config.getActiveEnvironment().getAuthTokenUrl()); + } + + private static RestTemplate getRestTemplate(final ApplicationConfig config) { + final ProxyConfig proxyConfig = new ProxyConfig(config.getHttpProxyHost(), config.getHttpProxyPort()); + return proxyConfig.proxyRestTemplate(); + } + + private static ApplicationConfig loadConfig() { + final Config configFile = ConfigFactory.parseFile(new File(CONFIG_FILENAME)); + final Config sdkConfig = ConfigFactory.load(configFile).getConfig(CONFIG_CONTEXT); + final ApplicationConfig applicationConfig = ConfigBeanFactory.create(sdkConfig, ApplicationConfig.class); + logger.info("Using sdk environment config {} with {}", applicationConfig.getMode(), applicationConfig); + return applicationConfig; + } +} + + + + diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000000000000000000000000000000000000..51805e8db4a5d7ca67b5593237f2434eb7e86587 --- /dev/null +++ b/core/README.md @@ -0,0 +1,6 @@ +## Core Module + +The core module contains all interface implementations of the ``API`` module. A client should interact with the actual +implementations only via the provided API service interfaces. It contains two main facades a clients can use to send and +receive submissions. + diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..06909aa70d5062ed7c21c3646f2a4d3e883ecf80 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,51 @@ +<?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>de.fitko.fitconnect.sdk</groupId> + <version>1.0-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>core</artifactId> + + <dependencies> + <dependency> + <groupId>de.fitko.fitconnect.sdk</groupId> + <artifactId>api</artifactId> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + <dependency> + <groupId>com.nimbusds</groupId> + <artifactId>nimbus-jose-jwt</artifactId> + </dependency> + <dependency> + <groupId>com.github.erosb</groupId> + <artifactId>everit-json-schema</artifactId> + </dependency> + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-web</artifactId> + </dependency> + <dependency> + <groupId>com.google.inject</groupId> + <artifactId>guice</artifactId> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </dependency> + </dependencies> + + + +</project> \ No newline at end of file diff --git a/core/src/main/java/de/fitko/fitconnect/core/SubmissionSender.java b/core/src/main/java/de/fitko/fitconnect/core/SubmissionSender.java new file mode 100644 index 0000000000000000000000000000000000000000..beb4937fd49b6a1d53c56449cb78aacaa3af7c61 --- /dev/null +++ b/core/src/main/java/de/fitko/fitconnect/core/SubmissionSender.java @@ -0,0 +1,122 @@ +package de.fitko.fitconnect.core; + +import com.google.inject.Inject; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.RSAKey; +import de.fitko.fitconnect.api.domain.auth.OAuthToken; +import de.fitko.fitconnect.api.domain.model.metadata.Metadata; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.signature.Hash__1; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.signature.Type; +import de.fitko.fitconnect.api.domain.model.metadata.data.Data; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionSubmit; +import de.fitko.fitconnect.api.domain.validation.ValidationResult; +import de.fitko.fitconnect.api.exceptions.AttachmentCreationException; +import de.fitko.fitconnect.api.services.Sender; +import de.fitko.fitconnect.api.services.auth.OAuthService; +import de.fitko.fitconnect.api.services.crypto.CryptoService; +import de.fitko.fitconnect.api.services.metadata.MetadataService; +import de.fitko.fitconnect.api.services.validation.CertificateValidator; +import de.fitko.fitconnect.core.crypto.HashUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.UUID; + +public class SubmissionSender implements Sender { + + private static final Logger logger = LoggerFactory.getLogger(SubmissionSender.class); + + private final OAuthService authService; + private final CertificateValidator certificateValidator; + private final CryptoService cryptoService; + private final MetadataService metadataService; + private final HashUtil hashUtil = new HashUtil(); + + @Inject + public SubmissionSender(final OAuthService authService, + final CryptoService encryptionService, + final CertificateValidator certificateValidator, + final MetadataService metadataService) { + this.authService = authService; + this.cryptoService = encryptionService; + this.certificateValidator = certificateValidator; + this.metadataService = metadataService; + } + + + @Override + public ValidationResult validatePublicKey(final JWK publicKey) { + return certificateValidator.validatePublicKey(publicKey); + } + + @Override + public Data encryptSubmissionData(final RSAKey publicKey, final Data data) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public Attachment encryptAttachment(final RSAKey publicKey, final Attachment attachment) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public OAuthToken retrieveOAuthToken(final String clientId, final String clientSecret, final String... scope) { + final OAuthToken token = authService.authenticate(clientId, clientSecret, scope); + logger.debug("Successfully retrieved OAuth token: {}", token.getAccessToken()); + return token; + } + + @Override + public Metadata createMetadata(final Data data, final List<Attachment> attachments) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public Metadata createMetadata(final List<Attachment> attachments) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public SubmissionForPickup createSubmission(final SubmissionSubmit submission) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public SubmissionForPickup sendSubmission(final UUID submissionId, final Metadata encryptedMetadata) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public void uploadAttachments(final UUID submissionId, final List<Attachment> encryptedAttachments) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public Attachment createAttachment(final File file) { + try { + final String hashedFileContent = hashUtil.createHashFromFile(file); + final Hash__1 hash = new Hash__1(); + hash.setType(Type.SHA_512); + hash.setContent(hashedFileContent); + return Attachment.builder() + .attachmentId(UUID.randomUUID()) + .filename(file.getName()) + .hash(hash) + .build(); + } catch (final IOException e) { + logger.error("Attachment could not be read", e); + throw new AttachmentCreationException(e.getMessage(), e); + } + } + + @Override + public RSAKey getEncryptionKeyForDestination(final UUID destinationId) { + throw new UnsupportedOperationException("not yet implemented"); + } + +} diff --git a/core/src/main/java/de/fitko/fitconnect/core/SubmissionSubscriber.java b/core/src/main/java/de/fitko/fitconnect/core/SubmissionSubscriber.java new file mode 100644 index 0000000000000000000000000000000000000000..a9368c1347197c6b4e68a1adc23a3d4ba2321e0d --- /dev/null +++ b/core/src/main/java/de/fitko/fitconnect/core/SubmissionSubscriber.java @@ -0,0 +1,91 @@ +package de.fitko.fitconnect.core; + +import com.google.inject.Inject; +import com.nimbusds.jose.jwk.RSAKey; +import de.fitko.fitconnect.api.domain.auth.OAuthToken; +import de.fitko.fitconnect.api.domain.model.metadata.Metadata; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.metadata.data.Data; +import de.fitko.fitconnect.api.domain.model.submission.Submission; +import de.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import de.fitko.fitconnect.api.domain.validation.ValidationResult; +import de.fitko.fitconnect.api.services.Subscriber; +import de.fitko.fitconnect.api.services.auth.OAuthService; +import de.fitko.fitconnect.api.services.crypto.CryptoService; +import de.fitko.fitconnect.api.services.validation.MetadataValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.UUID; + +public class SubmissionSubscriber implements Subscriber { + + private static final Logger logger = LoggerFactory.getLogger(SubmissionSubscriber.class); + + private final OAuthService authService; + private final CryptoService cryptoService; + private final MetadataValidator metadataValidator; + + @Inject + public SubmissionSubscriber(final OAuthService authService, + final CryptoService cryptoService, + final MetadataValidator metadataValidator) { + this.authService = authService; + this.cryptoService = cryptoService; + this.metadataValidator = metadataValidator; + } + + @Override + public OAuthToken retrieveOAuthToken(final String clientId, final String clientSecret, final String... scope) { + final OAuthToken token = authService.authenticate(clientId, clientSecret, scope); + logger.debug("Successfully retrieved OAuth token: {}", token.getAccessToken()); + return token; + } + + @Override + public byte[] decryptStringContent(final RSAKey privateKey, final String encryptedContent) { + return cryptoService.decryptBytes(privateKey, encryptedContent); + } + + @Override + public List<SubmissionForPickup> pollAvailableSubmissions(final UUID destinationId, final int limit, final int offset) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public Submission getSubmission(final UUID submissionId) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public List<Attachment> fetchAttachments(final UUID submissionId, final List<UUID> announcedAttachments) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public ValidationResult validateEventLog(final UUID caseId) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public ValidationResult validateMetadata(final Metadata metadata, final String schema) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public ValidationResult validateData(final Data data) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public ValidationResult validateAttachments(final List<Attachment> attachments) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public void confirmValidSubmission() { + throw new UnsupportedOperationException("not yet implemented"); + } + +} diff --git a/core/src/main/java/de/fitko/fitconnect/core/auth/DefaultOAuthService.java b/core/src/main/java/de/fitko/fitconnect/core/auth/DefaultOAuthService.java new file mode 100644 index 0000000000000000000000000000000000000000..0b54e6408212f1366e859e92c510ceeedfc87abc --- /dev/null +++ b/core/src/main/java/de/fitko/fitconnect/core/auth/DefaultOAuthService.java @@ -0,0 +1,72 @@ +package de.fitko.fitconnect.core.auth; + +import com.google.inject.Inject; +import com.google.inject.name.Named; +import de.fitko.fitconnect.api.domain.auth.OAuthToken; +import de.fitko.fitconnect.api.exceptions.AuthenticationException; +import de.fitko.fitconnect.api.services.auth.OAuthService; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import static java.util.stream.Collectors.joining; + +public class DefaultOAuthService implements OAuthService { + + private final RestTemplate restTemplate; + private final String tokenUrl; + + @Inject + public DefaultOAuthService(final RestTemplate restTemplate, @Named("environment.authTokenUrl") final String tokenUrl) { + this.restTemplate = restTemplate; + this.tokenUrl = tokenUrl; + } + + @Override + public OAuthToken authenticate(final String clientId, final String clientSecret, final String... scope) throws AuthenticationException { + final String requestBody = buildRequestBody(clientId, clientSecret, scope); + return performTokenRequest(requestBody); + } + + private String buildRequestBody(final String clientId, final String clientSecret, final String... scope) { + final var data = new HashMap<String, String>() {{ + put("grant_type", "client_credentials"); + put("client_id", clientId); + put("client_secret", clientSecret); + + }}; + + if (scope.length > 0) { + data.put("scope", String.join(",", scope)); + } + + return data.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(joining("&")); + } + + private OAuthToken performTokenRequest(final String requestBody) throws AuthenticationException { + final HttpHeaders headers = getHeaders(); + final HttpEntity<String> entity = new HttpEntity<>(requestBody, headers); + try { + return restTemplate.exchange(tokenUrl, HttpMethod.POST, entity, OAuthToken.class).getBody(); + } catch (final RestClientException e) { + throw new AuthenticationException("could not retrieve OAuth token", e); + } + } + + private HttpHeaders getHeaders() { + final var headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + headers.setAcceptCharset(List.of(StandardCharsets.UTF_8)); + return headers; + } + +} diff --git a/core/src/main/java/de/fitko/fitconnect/core/crypto/HashUtil.java b/core/src/main/java/de/fitko/fitconnect/core/crypto/HashUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..749f043afa960244fef6480d6eaa448516e61967 --- /dev/null +++ b/core/src/main/java/de/fitko/fitconnect/core/crypto/HashUtil.java @@ -0,0 +1,57 @@ +package de.fitko.fitconnect.core.crypto; + +import de.fitko.fitconnect.api.exceptions.InitializationException; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class HashUtil { + + private static final String DEFAULT_ALGORITHM = "SHA-512"; // Currently, only SHA-512 is supported. + + private final MessageDigest messageDigest; + + public HashUtil() { + try { + this.messageDigest = MessageDigest.getInstance(DEFAULT_ALGORITHM); + } catch (final NoSuchAlgorithmException e) { + throw new InitializationException(e.getMessage(), e); + } + } + + public HashUtil(final MessageDigest messageDigest) { + this.messageDigest = messageDigest; + } + + public byte[] createHash(final byte[] data) { + this.messageDigest.reset(); + return this.messageDigest.digest(data); + } + + public boolean verify(final byte[] originalHash, final byte[] data) { + final byte[] newHash = createHash(data); + return compareHashes(originalHash, newHash); + } + + public String createHashFromFile(final File file) throws IOException { + try { + final byte[] rawData = Files.readAllBytes(Paths.get(file.getPath())); + return new String(rawData); + } catch (final IOException e) { + throw e; + } + } + + private boolean compareHashes(final byte[] originalHash, final byte[] comparisonHash) { + int diff = originalHash.length ^ comparisonHash.length; + for (int i = 0; i < originalHash.length && i < comparisonHash.length; i++) { + diff |= originalHash[i] ^ comparisonHash[i]; + } + return diff == 0; + } + +} diff --git a/core/src/main/java/de/fitko/fitconnect/core/crypto/JWECryptoService.java b/core/src/main/java/de/fitko/fitconnect/core/crypto/JWECryptoService.java new file mode 100644 index 0000000000000000000000000000000000000000..82cf7606eec4e5e6bce9246695f97815301d8142 --- /dev/null +++ b/core/src/main/java/de/fitko/fitconnect/core/crypto/JWECryptoService.java @@ -0,0 +1,103 @@ +package de.fitko.fitconnect.core.crypto; + +import com.nimbusds.jose.*; +import com.nimbusds.jose.crypto.RSADecrypter; +import com.nimbusds.jose.crypto.RSAEncrypter; +import com.nimbusds.jose.jwk.RSAKey; +import de.fitko.fitconnect.api.services.crypto.CryptoService; +import de.fitko.fitconnect.api.exceptions.DecryptionException; +import de.fitko.fitconnect.api.exceptions.EncryptionException; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPublicKey; +import java.text.ParseException; + +public class JWECryptoService implements CryptoService { + + public static final JWEAlgorithm ALGORITHM = JWEAlgorithm.RSA_OAEP_256; + public static final EncryptionMethod ENCRYPTION_METHOD = EncryptionMethod.A256GCM; + + @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 { + 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 encryptBytes(final RSAKey publicKey, final byte[] bytes) throws EncryptionException { + final Payload payload = new Payload(bytes); + return encrypt(publicKey, payload); + } + + private String encrypt(final RSAKey publicKey, final Payload payload) throws EncryptionException { + try { + final KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); + keyGenerator.init(ENCRYPTION_METHOD.cekBitLength()); + final SecretKey cek = keyGenerator.generateKey(); + final String keyID = getIdFromPublicKey(publicKey); + return encryptPayload(publicKey, payload, cek, keyID); + } catch (final NoSuchAlgorithmException | JOSEException e) { + throw new EncryptionException(e.getMessage(), e); + } + } + + private Payload decrypt(final RSAKey privateKey, final String encData) throws DecryptionException { + try { + final JWEObject jwe = JWEObject.parse(encData); + jwe.decrypt(new RSADecrypter(privateKey)); + return jwe.getPayload(); + } catch (final ParseException | JOSEException e) { + throw new DecryptionException(e.getMessage(), e); + } + } + + private JWEObject getJWEObject(final String keyID, final Payload payload) { + return new JWEObject(getJWEHeader(keyID), payload); + } + + private JWEHeader getJWEHeader(final String keyID) { + return new JWEHeader.Builder(ALGORITHM, ENCRYPTION_METHOD) + .compressionAlgorithm(CompressionAlgorithm.DEF) + .contentType("application/json") + .keyID(keyID) + .build(); + } + + private RSAEncrypter getEncrypter(final RSAPublicKey publicKey, final SecretKey cek) { + return new RSAEncrypter(publicKey, cek); + } + + private String encryptPayload(final RSAKey publicKey, final Payload payload, final SecretKey cek, final String keyID) throws JOSEException, EncryptionException { + final JWEObject jwe = getJWEObject(keyID, payload); + jwe.encrypt(getEncrypter(publicKey.toRSAPublicKey(), cek)); + checkIfJWEObjectIsEncrypted(jwe); + return jwe.serialize(); + } + + private void checkIfJWEObjectIsEncrypted(final JWEObject jwe) throws EncryptionException { + if (!jwe.getState().equals(JWEObject.State.ENCRYPTED)) { + throw new EncryptionException("JWE object is not encrypted"); + } + } + + private String getIdFromPublicKey(final RSAKey publicKey) throws EncryptionException { + final String keyID = publicKey.getKeyID(); + if (keyID == null || keyID.isEmpty()) { + throw new EncryptionException("public key has no keyID"); + } + return keyID; + } + +} diff --git a/core/src/main/java/de/fitko/fitconnect/core/http/ProxyConfig.java b/core/src/main/java/de/fitko/fitconnect/core/http/ProxyConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..c3e271979a245e5c473a924b51a1b91e2c064232 --- /dev/null +++ b/core/src/main/java/de/fitko/fitconnect/core/http/ProxyConfig.java @@ -0,0 +1,45 @@ +package de.fitko.fitconnect.core.http; + +import com.google.common.base.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import java.net.InetSocketAddress; +import java.net.Proxy; + +public class ProxyConfig { + + private static final Logger logger = LoggerFactory.getLogger(ProxyConfig.class); + + private final String host; + private final int port; + + public ProxyConfig(String host, int port) { + this.host = host; + this.port = port; + } + + public RestTemplate proxyRestTemplate() { + if (!hasProxySet()) { + logger.info("No proxy configured"); + return new RestTemplate(); + } + + logger.info("Using proxy {}", this); + var requestFactory = new SimpleClientHttpRequestFactory(); + var proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port)); + requestFactory.setProxy(proxy); + return new ProxyRestTemplate(requestFactory); + } + + public boolean hasProxySet() { + return !Strings.isNullOrEmpty(host) && port > 0; + } + + @Override + public String toString() { + return String.format("ProxyConfig {host='%s', port=%d}", host, port); + } +} \ No newline at end of file diff --git a/core/src/main/java/de/fitko/fitconnect/core/http/ProxyRestTemplate.java b/core/src/main/java/de/fitko/fitconnect/core/http/ProxyRestTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..ea3aa13a05b47d48ee988adb52e7f25d1c9667e3 --- /dev/null +++ b/core/src/main/java/de/fitko/fitconnect/core/http/ProxyRestTemplate.java @@ -0,0 +1,12 @@ +package de.fitko.fitconnect.core.http; + +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +public class ProxyRestTemplate extends RestTemplate { + + public ProxyRestTemplate(final ClientHttpRequestFactory requestFactory) { + super(requestFactory); + this.getMessageConverters().add(new X509CRLHttpMessageConverter()); + } +} diff --git a/core/src/main/java/de/fitko/fitconnect/core/http/X509CRLHttpMessageConverter.java b/core/src/main/java/de/fitko/fitconnect/core/http/X509CRLHttpMessageConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..1c677f3402f056c0ecdeba4f901b5adccce54837 --- /dev/null +++ b/core/src/main/java/de/fitko/fitconnect/core/http/X509CRLHttpMessageConverter.java @@ -0,0 +1,42 @@ +package de.fitko.fitconnect.core.http; + +import org.springframework.http.HttpInputMessage; +import org.springframework.http.HttpOutputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.AbstractHttpMessageConverter; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.http.converter.HttpMessageNotWritableException; +import org.springframework.lang.NonNull; + +import java.security.cert.CertificateFactory; +import java.security.cert.X509CRL; + +public class X509CRLHttpMessageConverter extends AbstractHttpMessageConverter<X509CRL> { + + public X509CRLHttpMessageConverter() { + super(new MediaType("application", "pkix-crl")); + } + + @Override + protected boolean supports(@NonNull Class<?> clazz) { + return X509CRL.class == clazz; + } + + @Override + protected X509CRL readInternal(@NonNull Class<? extends X509CRL> clazz, @NonNull HttpInputMessage inputMessage) + throws HttpMessageNotReadableException { + try { + var cf = CertificateFactory.getInstance("X.509"); + return (X509CRL) cf.generateCRL(inputMessage.getBody()); + } catch (Exception e) { + throw new HttpMessageNotReadableException("CertificateFactory of type X.509 could not be created", inputMessage); + } + } + + @Override + protected void writeInternal(@NonNull X509CRL x509CRL, @NonNull HttpOutputMessage outputMessage) + throws HttpMessageNotWritableException { + throw new HttpMessageNotWritableException("Writing X509CRL is not supported in ProxyRestTemplate"); + } + +} \ No newline at end of file diff --git a/core/src/main/java/de/fitko/fitconnect/core/metadata/MetadataUploadService.java b/core/src/main/java/de/fitko/fitconnect/core/metadata/MetadataUploadService.java new file mode 100644 index 0000000000000000000000000000000000000000..aec1989dad84f0e24233bb257e6713dce6ee20ec --- /dev/null +++ b/core/src/main/java/de/fitko/fitconnect/core/metadata/MetadataUploadService.java @@ -0,0 +1,30 @@ +package de.fitko.fitconnect.core.metadata; + +import com.google.inject.Inject; +import de.fitko.fitconnect.api.domain.model.metadata.Metadata; +import de.fitko.fitconnect.api.domain.model.metadata.attachment.Attachment; +import de.fitko.fitconnect.api.domain.model.metadata.data.Data; +import de.fitko.fitconnect.api.services.metadata.MetadataService; +import org.springframework.web.client.RestTemplate; + +import java.util.List; + +public class MetadataUploadService implements MetadataService { + + private final RestTemplate restTemplate; + + @Inject + public MetadataUploadService(final RestTemplate restTemplate){ + this.restTemplate = restTemplate; + } + + @Override + public Metadata createMetadata(final Data data, final List<Attachment> attachments) { + throw new UnsupportedOperationException("not yet implemented"); + } + + @Override + public void uploadMetadata(final Metadata metadata) { + throw new UnsupportedOperationException("not yet implemented"); + } +} diff --git a/core/src/main/java/de/fitko/fitconnect/core/validation/KeyValidator.java b/core/src/main/java/de/fitko/fitconnect/core/validation/KeyValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..27b6e9decee40ee571f231dca4dbc019ce6332df --- /dev/null +++ b/core/src/main/java/de/fitko/fitconnect/core/validation/KeyValidator.java @@ -0,0 +1,13 @@ +package de.fitko.fitconnect.core.validation; + +import com.nimbusds.jose.jwk.JWK; +import de.fitko.fitconnect.api.domain.validation.ValidationResult; +import de.fitko.fitconnect.api.services.validation.CertificateValidator; + +public class KeyValidator implements CertificateValidator { + + @Override + public ValidationResult validatePublicKey(final JWK publicKey) { + throw new UnsupportedOperationException("not yet implemented"); + } +} diff --git a/core/src/main/java/de/fitko/fitconnect/core/validation/MetadataSubmissionValidator.java b/core/src/main/java/de/fitko/fitconnect/core/validation/MetadataSubmissionValidator.java new file mode 100644 index 0000000000000000000000000000000000000000..88eca6cac1407d67a76bb257c2ad9fdc22560f8e --- /dev/null +++ b/core/src/main/java/de/fitko/fitconnect/core/validation/MetadataSubmissionValidator.java @@ -0,0 +1,46 @@ +package de.fitko.fitconnect.core.validation; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.fitko.fitconnect.api.domain.model.metadata.Metadata; +import de.fitko.fitconnect.api.services.validation.MetadataValidator; +import de.fitko.fitconnect.api.domain.validation.ValidationResult; +import de.fitko.fitconnect.core.crypto.HashUtil; +import org.everit.json.schema.Schema; +import org.everit.json.schema.ValidationException; +import org.everit.json.schema.loader.SchemaLoader; +import org.json.JSONObject; +import org.json.JSONTokener; + +public class MetadataSubmissionValidator implements MetadataValidator { + + public static final String DEFAULT_SCHEMA_PATH = "schemas/metadata.schema.json"; + private static final HashUtil verifier = new HashUtil(); + private final ObjectMapper mapper = new ObjectMapper(); + + @Override + public ValidationResult validateMetadataSchema(Metadata metadata, String jsonSchema) { + final JSONObject schemaObject = new JSONObject(new JSONTokener(jsonSchema)); + try { + JSONObject jsonSubject = toJsonObject(metadata); + Schema schema = SchemaLoader.load(schemaObject); + schema.validate(jsonSubject); + } catch (JsonProcessingException | ValidationException e) { + return ValidationResult.error(e); + } + return ValidationResult.ok(); + } + + @Override + public ValidationResult validateMetadataHashValues(Metadata metadata) { + return ValidationResult.ok(); + } + + private JSONObject toJsonObject(Metadata metadata) throws JsonProcessingException { + return new JSONObject(new JSONTokener(mapper.writeValueAsString(metadata))); + } + + private String loadSchemaFile(final String jsonSchemaPath) { + return ""; + } +} diff --git a/core/src/main/resources/schemas/metadata.schema.json b/core/src/main/resources/schemas/metadata.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..997aeb08a5fe08d460c0e9efc23f4229931d5b97 --- /dev/null +++ b/core/src/main/resources/schemas/metadata.schema.json @@ -0,0 +1 @@ +{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json","type":"object","title":"Metadaten","description":"","required":["contentStructure"],"properties":{"contentStructure":{"description":"Beschreibt die Struktur der zusätzlichen Inhalte der Einreichung, wie Anlagen oder Fachdaten.","type":"object","required":["attachments"],"properties":{"data":{"description":"Definiert das Schema und die Signatur(-art), die für die Fachdaten verwendet werden.","type":"object","required":["hash","submissionSchema"],"properties":{"signature":{"type":"object","description":"Beschreibt das Signaturformt und Profile","examples":[],"properties":{"signatureFormat":{"type":"string","description":"Beschreibt, welches Signaturformat die genutzte Signatur / das genutzte Siegel nutzt. Aktuell wird die Hinterlegung folgender Signaturformate unterstützt: CMS = Cryptographic Message Syntax, Asic = Associated Signature Containers, PDF = PDF Signatur, XML = XML-Signature, JSON = JSON Web Signature. ","enum":["cms","xml","pdf","asic","json"]},"eidasAdesProfile":{"type":"string","description":"Referenziert ein eindeutiges Profil einer AdES (advanced electronic signature/seal) gemäß eIDAS-Verordnung über eine URI gemäß [ETSI TS 119 192](https://www.etsi.org/deliver/etsi_ts/119100_119199/119192/01.01.01_60/ts_119192v010101p.pdf).\n\nFür die Details zur Verwendung und Validierung von Profilen siehe auch https://ec.europa.eu/cefdigital/DSS/webapp-demo/doc/dss-documentation.html#_signatures_profile_simplification","enum":["http://uri.etsi.org/ades/191x2/level/baseline/B-B#","http://uri.etsi.org/ades/191x2/level/baseline/B-T#","http://uri.etsi.org/ades/191x2/level/baseline/B-LT#","http://uri.etsi.org/ades/191x2/level/baseline/B-LTA#"]},"detachedSignature":{"type":"boolean","description":"Beschreibt, ob die Signatur als seperate (detached) Signatur (`true`) oder als Teil des Fachdatensatzes bzw. der Anlage (`false`) übertragen wird. Wenn der Wert `true` ist, dann wird die Signatur Base64- oder Base64Url-kodiert im Feld `content` übertragen."},"content":{"type":"string","description":"Hier wird die Signatur im Falle einer Detached-Signatur als Base64- oder Base64Url-kodierte Zeichenkette hinterlegt. Eine Base64Url-Kodierung kommt nur bei Einsatz von JSON Web Signatures (JWS / JAdES) zum Einsatz.","pattern":"^[a-zA-Z0-9+/=]+|[a-zA-Z0-9_-]+$"}},"required":["signatureFormat","detachedSignature"]},"hash":{"title":"Hashwert","description":"Der Hashwert der unverschlüsselten Fachdaten. Die Angabe des Hashwertes dient der Integritätssicherung des Gesamtantrags und schützt vor einem Austausch der Fachdaten durch Systeme zwischen Sender und Subscriber (z.B. dem Zustelldienst).","type":"object","required":["type","content"],"properties":{"type":{"type":"string","description":"Der verwendete Hash-Algorithmus. Derzeit ist nur `sha512` erlaubt.","enum":["sha512"]},"content":{"type":"string","description":"Der Hex-kodierte Hashwert gemäß des angegebenen Algorithmus.","pattern":"^[a-f0-9]{128}$"}}},"submissionSchema":{"title":"Fachdatenschema","description":"Referenz auf ein Schema, das die Struktur der Fachdaten einer Einreichung beschreibt.","type":"object","required":["schemaUri","mimeType"],"properties":{"schemaUri":{"type":"string","format":"uri","description":"URI des Fachschemas. Wird hier eine URL verwendet, sollte das Schema unter der angegebenen URL abrufbar sein. Eine Verfügbarkeit des Schemas unter der angegebenen URL darf jedoch nicht vorausgesetzt werden."},"mimeType":{"type":"string","description":"Mimetype (z.B. application/json oder application/xml) des referenzierten Schemas (z.B. XSD- oder JSON-Schema).","enum":["application/json","application/xml"]}}}}},"attachments":{"type":"array","items":{"type":"object","description":"Eine in der Einreichung enthaltene Anlage.","required":["hash","purpose","mimeType","attachmentId"],"properties":{"hash":{"title":"Hashwert","description":"Der Hashwert der unverschlüsselten Anlage. Die Angabe des Hashwertes dient der Integritätssicherung des Gesamtantrags und schützt vor einem Austausch der Anlage durch Systeme zwischen Sender und Subscriber (z.B. dem Zustelldienst).","type":"object","required":["type","content"],"properties":{"type":{"type":"string","description":"Der verwendete Hash-Algorithmus. Derzeit ist nur `sha512` erlaubt.","enum":["sha512"]},"content":{"type":"string","description":"Der Hex-kodierte Hashwert gemäß des angegebenen Algorithmus.","pattern":"^[a-f0-9]{128}$"}}},"signature":{"$ref":"#/properties/contentStructure/properties/data/properties/signature"},"purpose":{"description":"Zweck/Art der Anlage\n- form: Automatisch generierte PDF-Repräsentation des vollständigen Antragsformulars\n- attachment: Anlage, die von einem Bürger hochgeladen wurde\n- report: Vom Onlinedienst, nachträglich erzeugte Unterlage","type":"string","enum":["form","attachment","report"]},"filename":{"type":"string","description":"Ursprünglicher Dateiname bei Erzeugung oder Upload"},"description":{"type":"string","description":"Optionale Beschreibung der Anlage"},"mimeType":{"type":"string","title":"MIME Type","description":"Internet Media Type gemäß RFC 2045, z. B. application/pdf.","examples":["application/xml"],"pattern":"^[-\\w.]+/[-\\w.+]+$"},"attachmentId":{"type":"string","description":"Innerhalb einer Einreichung eindeutige Id der Anlage im Format einer UUIDv4.","format":"uuid","minLength":32,"maxLength":36}}}}}},"publicServiceType":{"type":"object","title":"Verwaltungsleistung","description":"Beschreibung der Art der Verwaltungsleistung. Eine Verwaltungsleistung sollte immer mit einer LeiKa-Id beschrieben werden. Ist für die gegebene Verwaltungsleistung keine LeiKa-Id vorhanden, kann die Verwaltungsleistung übergangsweise über die Angabe einer anderen eindeutigen Schema-URN beschrieben werden.","properties":{"name":{"type":"string","description":"Name/Bezeichnung der Verwaltungsleistung"},"description":{"type":"string","description":"(Kurz-)Beschreibung der Verwaltungsleistung"},"identifier":{"title":"Leistungs-Identifikator","description":"URN einer Leistung. Im Falle einer Leistung aus dem Leistungskatalog sollte hier `urn:de:fim:leika:leistung:` vorangestellt werden.\n","type":"string","minLength":7,"maxLength":255,"pattern":"^urn:[a-z0-9][a-z0-9-]{0,31}:[a-z0-9()+,.:=@;$_!*'%/?#-]+$"}},"required":["identifier"]},"authenticationInformation":{"description":"Eine Liste aller Identifikationsnachweise der Einreichung.","type":"array","minItems":1,"items":{"type":"object","description":"Eine Struktur, die einen Identifikationsnachweis beschreibt.","properties":{"type":{"description":"Definiert die Art des Identifikationsnachweises.","type":"string","enum":["identificationReport"]},"version":{"type":"string","pattern":"^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$","description":"semver kompatible Versionsangabe des genutzten Nachweistyps."},"content":{"type":"string","description":"Der Nachweis wird als Base64Url-kodierte Zeichenkette angegeben.","pattern":"^[a-zA-Z0-9_\\-.]+$"}},"required":["type","version","content"]}},"paymentInformation":{"description":"Dieses Objekt enthält die Informationen vom Bezahldienst.","type":"object","required":["transactionReference","transactionId","paymentMethod","status"],"properties":{"transactionUrl":{"type":"string","format":"uri","minLength":1,"examples":["https://payment.bundesland.zzzz/api/v1/paymenttransaction/12002312/MELD-ANT-FORM-4711/9xxd-432x-6543-xfd6-gfdx-fd27"],"description":"Die Rest-URL der Payment Transaction für die Statusabfrage."},"transactionId":{"type":"string","minLength":1,"maxLength":36,"pattern":"^[\\w\\d-]+$","examples":["9xxd-432x-6543-xfd6-gfdx-fd27"],"description":"Eine vom Bezahldienst vergebene Transaktions-Id."},"transactionReference":{"type":"string","description":"Bezahlreferenz bzw. Verwendungszweck, wie z. B. ein Kassenzeichen."},"transactionTimestamp":{"type":"string","format":"date-time","description":"Zeitstempel der erfolgreichen Durchführung der Bezahlung."},"paymentMethod":{"type":"string","enum":["GIROPAY","PAYDIRECT","CREDITCARD","PAYPAL","INVOICE","OTHER"],"examples":["CREDITCARD"],"description":"Die vom Benutzer ausgewählte Zahlart. Das Feld ist nur bei einer erfolgreichen Zahlung vorhanden / befüllt."},"paymentMethodDetail":{"type":"string","minLength":1,"maxLength":36,"pattern":"^[\\w\\d-]+$","examples":["Visa"],"description":"Weitere Erläuterung zur gewählten Zahlart."},"status":{"type":"string","enum":["INITIAL","BOOKED","FAILED","CANCELED"],"description":"- INITIAL - der Einreichung hat einen Payment-Request ausgelöst und eine Payment-Transaction wurde angelegt. Der Nutzer hat aber im Bezahldienst noch keine Wirkung erzeugt.\n- BOOKED - der Nutzer hat die Bezahlung im Bezahldienst autorisiert.\n- FAILED - der Vorgang wurde vom Bezahldienst aufgrund der Nutzereingaben abgebrochen.\n- CANCELED - der Nutzer hat die Bezahlung im Bezahldienst abgebrochen."},"grossAmount":{"type":"number","minimum":0.01,"multipleOf":0.01,"description":"Bruttobetrag"}}},"replyChannel":{"type":"object","minProperties":1,"maxProperties":1,"properties":{"eMail":{"type":"object","properties":{"address":{"type":"string","format":"email"},"pgpPublicKey":{"type":"string","description":"Hilfe zur Erstellung gibt es in der Dokumentation unter https://docs.fitko.de/fit-connect/details/pgp-export","pattern":"^-----BEGIN PGP PUBLIC KEY BLOCK-----\\n\\n"}},"required":["address"]},"deMail":{"type":"object","description":"Akkreditierte Anbieter siehe https://www.bsi.bund.de/DE/Themen/Oeffentliche-Verwaltung/Moderner-Staat/De-Mail/Akkreditierte-DMDA/akkreditierte-dmda_node.html","properties":{"address":{"type":"string","format":"email"}},"required":["address"]},"fink":{"type":"object","description":"Postfachadresse in einem interoperablen Servicekonto (FINK.PFISK)","properties":{"finkPostfachRef":{"type":"string","description":"FINK Postfachadresse","examples":["hh/by/12345"],"maxLength":150,"pattern":"^[-._a-z0-9~/]*$"},"host":{"type":"string","description":"URL des Servicekontos, in dem das Ziel-Postfach liegt","format":"uri","examples":["https://servicekonto1.example.com/"]}},"required":["finkPostfachRef"]},"elster":{"type":"object","description":"Siehe https://www.elster.de/elsterweb/infoseite/elstertransfer_hilfe_schnittstellen","properties":{"accountId":{"type":"string","pattern":"^\\d{10}$"},"lieferTicket":{"type":"string"},"geschaeftszeichen":{"type":"string","maxLength":10}},"required":["accountId"]}}},"additionalReferenceInfo":{"type":"object","description":"Eine Struktur, um zusätzliche Informationen zu hinterlegen","properties":{"senderReference":{"type":"string","description":"Eine Referenz zum Vorgang im sendenden System, um bei Problemen und Rückfragen außerhalb von FIT-Connect den Vorgang im dortigen System schneller zu identifizieren."},"applicationDate":{"type":"string","format":"date","description":"Das Datum der Antragstellung. Das Datum muss nicht zwingend identisch mit dem Datum der Einreichung des Antrags über FIT-Connect sein."}}}}} \ No newline at end of file diff --git a/core/src/test/java/de/fitko/fitconnect/core/auth/OAuthTokenIntegrationTest.java b/core/src/test/java/de/fitko/fitconnect/core/auth/OAuthTokenIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6dacd8422c80c6dd2c5404d95cfceecfc1c5427a --- /dev/null +++ b/core/src/test/java/de/fitko/fitconnect/core/auth/OAuthTokenIntegrationTest.java @@ -0,0 +1,38 @@ +package de.fitko.fitconnect.core.auth; + +import de.fitko.fitconnect.api.domain.auth.OAuthToken; +import de.fitko.fitconnect.api.services.Sender; +import de.fitko.fitconnect.core.SubmissionSender; +import de.fitko.fitconnect.core.auth.DefaultOAuthService; +import org.junit.jupiter.api.Test; +import org.springframework.web.client.RestTemplate; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; + +class OAuthTokenIntegrationTest { + + @Test + void retrieveAuthenticationToken() { + + // Given + final var tokenUrl = "https://auth-testing.fit-connect.fitko.dev/token"; + final var clientId = "781f6213-0f0f-4a79-9372-e7187ffda98b"; + final var secret = "PnzR8Vbmhpv_VwTkT34wponqXWK8WBm-LADlryYdV4o"; + final var scope1 = "send:region:DE"; + final var scope2 = "send:region:EN"; + + final var authService = new DefaultOAuthService(new RestTemplate(), tokenUrl); + final Sender sender = new SubmissionSender(authService, null, null, null); + + // When + final OAuthToken token = sender.retrieveOAuthToken(clientId, secret, scope1, scope2); + + // Then + assertNotNull(token); + assertNull(token.getError()); + assertNotNull(token.getAccessToken()); + assertEquals(1800, token.getExpiresIn()); + } +} \ No newline at end of file diff --git a/core/src/test/java/de/fitko/fitconnect/core/crypto/HashUtilTest.java b/core/src/test/java/de/fitko/fitconnect/core/crypto/HashUtilTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4781501d48b434eebc99d5c5dbba82e418563a7e --- /dev/null +++ b/core/src/test/java/de/fitko/fitconnect/core/crypto/HashUtilTest.java @@ -0,0 +1,30 @@ +package de.fitko.fitconnect.core.crypto; + +import de.fitko.fitconnect.core.crypto.HashUtil; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +class HashUtilTest { + + HashUtil underTest = new HashUtil(); + + @Test + public void testMessageDigestForDataAndAttachments() throws IOException { + + // Given (sender creates hash of (Fachdaten) or attachments (Anhänge)) + final byte[] exampleFileContent = getExampleFileContent("/example.pdf"); + final byte[] hashFromSender = underTest.createHash(exampleFileContent); + + // When (subscriber verifies hash of data or attachments) + final boolean verificationResult = underTest.verify(hashFromSender, exampleFileContent); + assertTrue(verificationResult); + } + + private byte[] getExampleFileContent(final String path) throws IOException { + return HashUtil.class.getResourceAsStream(path).readAllBytes(); + } + +} \ No newline at end of file diff --git a/core/src/test/java/de/fitko/fitconnect/core/crypto/JWECryptoServiceTest.java b/core/src/test/java/de/fitko/fitconnect/core/crypto/JWECryptoServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..078212522b6b48d2a2ab01ff7e271cd5857fd5fe --- /dev/null +++ b/core/src/test/java/de/fitko/fitconnect/core/crypto/JWECryptoServiceTest.java @@ -0,0 +1,57 @@ +package de.fitko.fitconnect.core.crypto; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.gen.RSAKeyGenerator; +import de.fitko.fitconnect.api.services.crypto.CryptoService; +import de.fitko.fitconnect.core.crypto.JWECryptoService; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class JWECryptoServiceTest { + + final CryptoService underTest = new JWECryptoService(); + + @Test + void encryptData() throws Exception { + + // Given + final RSAKey privateKey = generateRsaKey(4096); + final RSAKey publicKey = privateKey.toPublicJWK(); + + // When + final String encryptedData = underTest.encryptString(publicKey, "some foo"); + + // Then + assertNotNull(encryptedData); + assertNotEquals("some foo", encryptedData); + } + + @Test + void decryptData() throws Exception { + + // Given + final RSAKey privateKey = generateRsaKey(4096); + final RSAKey publicKey = privateKey.toPublicJWK(); + final String encryptedData = underTest.encryptString(publicKey, "some foo"); + + // When + final String decrypted = underTest.decryptString(privateKey, encryptedData); + + // Then + assertNotNull(decrypted); + assertEquals("some foo", decrypted); + } + + public static RSAKey generateRsaKey(final int size) throws JOSEException { + return new RSAKeyGenerator(size) + .keyUse(KeyUse.ENCRYPTION) + .keyID(UUID.randomUUID().toString()) + .generate(); + } + +} \ No newline at end of file diff --git a/core/src/test/resources/example.pdf b/core/src/test/resources/example.pdf new file mode 100644 index 0000000000000000000000000000000000000000..92aa8424e344f57d9206aa85becd6198255a44b9 Binary files /dev/null and b/core/src/test/resources/example.pdf differ diff --git a/dependency/README.md b/dependency/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d9d0eccbe1455620e22dfdad784130fae0f2968c --- /dev/null +++ b/dependency/README.md @@ -0,0 +1,46 @@ +## Dependency Module + +The DI module uses [Guice](https://github.com/google/guice/wiki/) as a dependency injection container of the SDK. All +service interfaces that are bound to their implementations in one central location - the configure method. + +### Service Binding + +The ```SdkModule``` implements a guice Abstract Module and is configured with all necessary service binding. New +Implementations or Providers can be registered here. + +```java + +@Override +protected void configure(){ + + bindConfigProperties(); + + bind(CryptoService.class).to(JWECryptoService.class); + bind(CertificateValidator.class).to(KeyValidator.class); + bind(MetadataValidator.class).to(MetadataSubmissionValidator.class); + bind(MetadataService.class).to(MetadataUploadService.class); + + bind(Sender.class).to(SubmissionSender.class); + bind(Subscriber.class).to(SubmissionSubscriber.class); + } +``` + +### Bootstrap and usage + +In order to initialize the container the sdk-module needs to be loaded: + +```java +private static final Injector injector=Guice.createInjector(new SdkModule()); +``` + +The actual injection can be done in three ways via @Inject: + +* Parameter Injection via @Injet on the field +* Constructor Injection via @Inject on the constructor +* Getting the Service directly from the DI Container, e.g. via ``injector.getService(Clazz.class)`` + +### Application Properties + +The SDK can be externally configured via the config file ``/sdk.conf``. Those keys are loaded during guice +initialization and can be accessed via the ``@Named`` annotation. See [Guice](https://github.com/google/guice/wiki/) +for further information on the usage. diff --git a/dependency/pom.xml b/dependency/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..9b050e048c976620bdaae137ee10a93807f0f540 --- /dev/null +++ b/dependency/pom.xml @@ -0,0 +1,41 @@ +<?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>de.fitko.fitconnect.sdk</groupId> + <version>1.0-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>dependency</artifactId> + + <dependencies> + <dependency> + <groupId>de.fitko.fitconnect.sdk</groupId> + <artifactId>api</artifactId> + </dependency> + <dependency> + <groupId>de.fitko.fitconnect.sdk</groupId> + <artifactId>core</artifactId> + </dependency> + <dependency> + <groupId>com.google.inject</groupId> + <artifactId>guice</artifactId> + </dependency> + <dependency> + <groupId>com.typesafe</groupId> + <artifactId>config</artifactId> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </dependency> + </dependencies> + +</project> \ No newline at end of file diff --git a/dependency/src/main/java/de/fitko/fitconnect/dependency/ApplicationConfig.java b/dependency/src/main/java/de/fitko/fitconnect/dependency/ApplicationConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..dcd890189d11270e3575dbbbf89c800a7d73fb2f --- /dev/null +++ b/dependency/src/main/java/de/fitko/fitconnect/dependency/ApplicationConfig.java @@ -0,0 +1,70 @@ +package de.fitko.fitconnect.dependency; + +import de.fitko.fitconnect.core.SubmissionSender; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Properties; + +@Getter +@Setter +@ToString +public class ApplicationConfig { + + private String httpProxyHost; + private Integer httpProxyPort; + + private String clientId; + private String clientSecret; + private String privateKey; + + private Environments environments; + + private String mode; + + public Environment getActiveEnvironment() { + switch (mode) { + case "PROD": + return environments.prod; + case "TEST": + return environments.test; + default: + return environments.dev; + } + } + + public Properties getAsProperties() { + final var props = new Properties(); + props.put("httpProxyHost", getHttpProxyHost()); + props.put("httpProxyPort", getHttpProxyPort()); + props.put("clientId", clientId); + props.put("clientSecret", clientSecret); + props.put("privateKey", privateKey); + //props.put("environment", getActiveEnvironment(mode, environments)); + return props; + } + + + @Getter + @Setter + @ToString + @NoArgsConstructor + static public class Environments { + private Environment dev; + private Environment prod; + private Environment test; + } + + @Getter + @Setter + @ToString + @NoArgsConstructor + static public class Environment { + private String authTokenUrl; + } +} + diff --git a/dependency/src/main/java/de/fitko/fitconnect/dependency/SdkModule.java b/dependency/src/main/java/de/fitko/fitconnect/dependency/SdkModule.java new file mode 100644 index 0000000000000000000000000000000000000000..972042ab9124619a43ffb667972cd6f6ace6a99f --- /dev/null +++ b/dependency/src/main/java/de/fitko/fitconnect/dependency/SdkModule.java @@ -0,0 +1,64 @@ +package de.fitko.fitconnect.dependency; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import com.google.inject.Singleton; +import com.google.inject.name.Named; +import com.google.inject.name.Names; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigBeanFactory; +import com.typesafe.config.ConfigFactory; +import de.fitko.fitconnect.api.services.Sender; +import de.fitko.fitconnect.api.services.Subscriber; +import de.fitko.fitconnect.api.services.auth.OAuthService; +import de.fitko.fitconnect.api.services.crypto.CryptoService; +import de.fitko.fitconnect.api.services.metadata.MetadataService; +import de.fitko.fitconnect.api.services.validation.CertificateValidator; +import de.fitko.fitconnect.api.services.validation.MetadataValidator; +import de.fitko.fitconnect.core.SubmissionSender; +import de.fitko.fitconnect.core.SubmissionSubscriber; +import de.fitko.fitconnect.core.auth.DefaultOAuthService; +import de.fitko.fitconnect.core.crypto.JWECryptoService; +import de.fitko.fitconnect.core.http.ProxyConfig; +import de.fitko.fitconnect.core.metadata.MetadataUploadService; +import de.fitko.fitconnect.core.validation.KeyValidator; +import de.fitko.fitconnect.core.validation.MetadataSubmissionValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.client.RestTemplate; + +import java.io.File; + +public class SdkModule extends AbstractModule { + + private static final Logger logger = LoggerFactory.getLogger(SdkModule.class); + public static final String CONFIG_NAME = "sdk.conf"; + + @Override + protected void configure() { + + bindConfigProperties(); + + bind(CryptoService.class).to(JWECryptoService.class); + bind(CertificateValidator.class).to(KeyValidator.class); + bind(MetadataValidator.class).to(MetadataSubmissionValidator.class); + bind(MetadataService.class).to(MetadataUploadService.class); + bind(OAuthService.class).to(DefaultOAuthService.class); + + bind(Sender.class).to(SubmissionSender.class); + bind(Subscriber.class).to(SubmissionSubscriber.class); + } + + @Provides + @Singleton + RestTemplate provideRestTemplate(@Named("httpProxyHost") String proxyHost, @Named("httpProxyPort") int proxyPort) { + return new ProxyConfig(proxyHost, proxyPort).proxyRestTemplate(); + } + + private void bindConfigProperties() { + final Config conf = ConfigFactory.load( ConfigFactory.parseFile(new File(CONFIG_NAME))).getConfig("sdk"); + final ApplicationConfig appConfig = ConfigBeanFactory.create(conf, ApplicationConfig.class); + Names.bindProperties(binder(), appConfig.getAsProperties()); + logger.info("using sdk config: {}", appConfig); + } +} diff --git a/logback.xml b/logback.xml new file mode 100644 index 0000000000000000000000000000000000000000..0d192e625eeb89cc8737c6fd0b6412233f16d3f4 --- /dev/null +++ b/logback.xml @@ -0,0 +1,10 @@ +<configuration> + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> + </encoder> + </appender> + <root level="debug"> + <appender-ref ref="STDOUT" /> + </root> +</configuration> \ No newline at end of file diff --git a/open-api/README.md b/open-api/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ed8c51663b487526ac666ee185102aa6a9aa1ec1 --- /dev/null +++ b/open-api/README.md @@ -0,0 +1,47 @@ +### OpenAPI Model Generation + +This module generates model classes from the open-api specification of the submission api. + +Usage: ``mvn compile exec:java`` generates all models into the package ``de.fitko.fitconnect.api.domain.model.submission`` under +**target/generated-sources/openapi/gen**. + +### Config + +The relevant config parameters are listed below. + +````xml + +<configuration> + <!-- open api spec url --> + <inputSpec>${submission-api.baseurl}/${submission-api.version}/${submission-api.spec}</inputSpec> + + <!-- language generator --> + <generatorName>java</generatorName> + + <!-- validation is currently disabled, otherwise the generation fails due to duplicate entries --> + <skipValidateSpec>true</skipValidateSpec> + + <!-- use jackson annotations --> + <library>resttemplate</library> + + <!-- target package name --> + <modelPackage>de.fitko.fitconnect.api.domain.model.submission</modelPackage> + + <!-- target folder to write files into --> + <configOptions> + <sourceFolder>../../../src/main/java</sourceFolder> + </configOptions> +</configuration> + +```` + +### Draft/Todo PostProcessing + +The ``de.fitko.fitconnect.api.postprocessing.ModelClassPostProcessor`` is running in a maven execute step and +replaces all comments and unwanted annotations. + +Currently, there are some @Nullable and @NonNull annotations from ```javax.annotations``` that need to be replaced. + +In a further step the cleaned and post-processed model classes can be updated in the original ``api/domain/model`` +package of the ``api`` module via the maven resource plugin. + diff --git a/open-api/pom.xml b/open-api/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..849e77d139caf21a2ee754cf08be0dc570a826ca --- /dev/null +++ b/open-api/pom.xml @@ -0,0 +1,127 @@ +<?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>de.fitko.fitconnect.sdk</groupId> + <version>1.0-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>open-api</artifactId> + + <properties> + <submission-api.baseurl>https://schema.fitko.de/fit-connect/submission-api</submission-api.baseurl> + <submission-api.version>1.0.7</submission-api.version> + <submission-api.spec>submission-api.yaml</submission-api.spec> + </properties> + + <dependencies> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + <dependency> + <groupId>io.swagger</groupId> + <artifactId>swagger-annotations</artifactId> + <version>1.6.1</version> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>javax.annotation</groupId> + <artifactId>javax.annotation-api</artifactId> + <version>1.3.2</version> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <version>3.0.2</version> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.openapitools</groupId> + <artifactId>openapi-generator-maven-plugin</artifactId> + <version>6.0.0</version> + <executions> + <execution> + <goals> + <goal>generate</goal> + </goals> + <configuration> + <inputSpec>${submission-api.baseurl}/${submission-api.version}/${submission-api.spec} + </inputSpec> + <generatorName>java</generatorName> + <generateApis>false</generateApis> + <skipValidateSpec>true</skipValidateSpec> + <generateApis>false</generateApis> + <generateModelDocumentation>false</generateModelDocumentation> + <generateModelTests>false</generateModelTests> + <generateSupportingFiles>false</generateSupportingFiles> + <library>resttemplate</library> + <modelPackage>de.fitko.fitconnect.api.domain.model.submission</modelPackage> + <configOptions> + <sourceFolder>gen</sourceFolder> + </configOptions> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>exec-maven-plugin</artifactId> + <version>3.0.0</version> + <configuration> + <mainClass>de.fitko.fitconnect.api.postprocessing.ModelClassPostProcessor</mainClass> + <workingDirectory>postprocessing</workingDirectory> + <arguments> + <argument>target/generated-sources/openapi/gen/de/fitko/fitconnect/api/domain/model/submission + </argument> + </arguments> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-resources-plugin</artifactId> + <version>3.2.0</version> + <executions> + <execution> + <id>copy-resource-one</id> + <phase>generate-sources</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${project.basedir}/../api/src/main/java/de/fitko/fitconnect/api/domain/generated + </outputDirectory> + <resources> + <resource> + <directory> + target/generated-sources/openapi/gen/de/fitko/fitconnect/api/domain/model/submission + </directory> + </resource> + </resources> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + + +</project> \ No newline at end of file diff --git a/open-api/src/main/java/de/fitko/fitconnect/api/postprocessing/ModelClassPostProcessor.java b/open-api/src/main/java/de/fitko/fitconnect/api/postprocessing/ModelClassPostProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..ea5203dba4095509b5591697c5ddda712d49dc32 --- /dev/null +++ b/open-api/src/main/java/de/fitko/fitconnect/api/postprocessing/ModelClassPostProcessor.java @@ -0,0 +1,91 @@ +package de.fitko.fitconnect.api.postprocessing; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ModelClassPostProcessor { + + private static final Logger logger = LoggerFactory.getLogger(ModelClassPostProcessor.class); + + public static void main(final String[] args) { + + final ModelClassPostProcessor postProcessor = new ModelClassPostProcessor(); + + final var dir = args[0]; // -modelPath + + postProcessor.readModelsToStrings(dir) + .stream() + .map(ModelClassPostProcessor::replaceContent) + //.peek(pair -> logger.info(pair.content)) + .forEach(ModelClassPostProcessor::writeFiles); + } + + private static ModelPair replaceContent(final ModelPair pair) { + String content = pair.content; + // Remove all Comments + content = content.replaceAll("\\/\\*([\\S\\s]+?)\\*\\/", ""); + content = content.replaceAll("(?s)/\\*.*?\\*/", ""); + // Remove unwanted annos + content = content.replaceAll("@ApiModel([\\w]+)((.*))", ""); + content = content.replaceAll("@ApiModelProperty([\\w]+)((.*))", ""); + content = content.replaceAll("Generated([\\w]+)((.*))", ""); + // Replace javax + //content = content.replaceAll("Nonnull([\\w]+)((.*))", ""); + //content = content.replaceAll("(@\([\\w]+)\\((.*?)\\)(?:\s|$))", ""); + return new ModelPair(pair.originalPath, content); + } + + public Set<ModelPair> readModelsToStrings(final String dir) { + return Stream.of(new File(dir).listFiles()) + .filter(file -> !file.isDirectory()) + .peek(file -> logger.info("Postprocessing model {}", file.getName())) + .map(file -> new ModelPair(file.toPath(), getFileAsString(file))) + .collect(Collectors.toSet()); + } + + private String getFileAsString(final File file) { + try { + return Files.readString(file.toPath(), StandardCharsets.UTF_8); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + private static void writeFiles(final ModelPair modelPair) { + try { + final File f = new File(modelPair.originalPath.toFile().getAbsolutePath()); + final FileWriter fw = new FileWriter( f, false); + fw.write(modelPair.content); + fw.flush(); + fw.close(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + + private static class ModelPair { + final Path originalPath; + final String content; + + ModelPair(final Path originalPath, final String content) { + this.originalPath = originalPath; + this.content = content; + } + + @Override + public String toString() { + return content; + } + } +} diff --git a/pom.xml b/pom.xml index 59250755823f6b232861b688175042f452085674..38891393345990d101e0e23fc32eef397babaf22 100644 --- a/pom.xml +++ b/pom.xml @@ -4,13 +4,163 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> - <groupId>de.fitconnect.sdk</groupId> + <groupId>de.fitko.fitconnect.sdk</groupId> <artifactId>sdk-java</artifactId> + <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <properties> - <maven.compiler.source>17</maven.compiler.source> - <maven.compiler.target>17</maven.compiler.target> + <java.version>11</java.version> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + + <!-- 3rd party dependencies --> + <nimbus.version>9.19</nimbus.version> + <jackson.version>2.13.3</jackson.version> + <json-schema.version>1.14.1</json-schema.version> + <lombock.version>1.18.24</lombock.version> + <typesafe-config.version>1.4.2</typesafe-config.version> + <spring-web.version>5.3.21</spring-web.version> + <guice.version>5.1.0</guice.version> + <logback.version>1.2.11</logback.version> + <jcommander.version>1.82</jcommander.version> + + <junit.version>5.8.2</junit.version> + <maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version> + <maven-checkstyle-plugin.version>3.1.2</maven-checkstyle-plugin.version> + </properties> + <modules> + <module>api</module> + <module>core</module> + <module>client</module> + <module>dependency</module> + <module>open-api</module> + </modules> + + <dependencyManagement> + <dependencies> + + <!-- Module Dependencies --> + <dependency> + <groupId>de.fitko.fitconnect.sdk</groupId> + <artifactId>api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>de.fitko.fitconnect.sdk</groupId> + <artifactId>core</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>de.fitko.fitconnect.sdk</groupId> + <artifactId>dependency</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>${jackson.version}</version> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + <version>${jackson.version}</version> + <scope>compile</scope> + </dependency> + + <dependency> + <groupId>com.nimbusds</groupId> + <artifactId>nimbus-jose-jwt</artifactId> + <version>${nimbus.version}</version> + </dependency> + + <dependency> + <groupId>com.github.erosb</groupId> + <artifactId>everit-json-schema</artifactId> + <version>${json-schema.version}</version> + </dependency> + + <dependency> + <groupId>org.springframework</groupId> + <artifactId>spring-web</artifactId> + <version>${spring-web.version}</version> + </dependency> + + <dependency> + <groupId>com.google.inject</groupId> + <artifactId>guice</artifactId> + <version>${guice.version}</version> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <version>${logback.version}</version> + </dependency> + + <dependency> + <groupId>com.beust</groupId> + <artifactId>jcommander</artifactId> + <version>${jcommander.version}</version> + </dependency> + <dependency> + <groupId>com.typesafe</groupId> + <artifactId>config</artifactId> + <version>${typesafe-config.version}</version> + </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>${lombock.version}</version> + <scope>provided</scope> + </dependency> + + <!-- Testing --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <version>${junit.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit</groupId> + <artifactId>junit-bom</artifactId> + <version>${junit.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>${maven-compiler-plugin.version}</version> + <configuration> + <source>11</source> + <target>11</target> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-checkstyle-plugin</artifactId> + <version>${maven-checkstyle-plugin.version}</version> + <configuration> + <configLocation>checkstyle.xml</configLocation> + </configuration> + <executions> + <execution> + <goals> + <goal>check</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </project> \ No newline at end of file diff --git a/sdk.conf b/sdk.conf new file mode 100644 index 0000000000000000000000000000000000000000..853cc4fafc76aefbcfed8c2b0bfec83cfef5f695 --- /dev/null +++ b/sdk.conf @@ -0,0 +1,32 @@ +# SKD application properties and global configurations +sdk { + + # Credentials to authenticate via OAuth + clientId: "781f6213-0f0f-4a79-9372-e7187ffda98b" + clientSecret: "PnzR8Vbmhpv_VwTkT34wponqXWK8WBm-LADlryYdV4o" + # Private key + # find location (ENV-var ?) + # and suitable format (PEM-Container ?) + privateKey: "$ref" + + # Proxy config for http api calls + httpProxyHost: "" + httpProxyPort: 0 + + # Mode to switch between the active environments DEV, PROD or TEST + mode: "DEV" + + # Configured environments for all api-urls + environments { + dev { + authTokenUrl: "https://auth-testing.fit-connect.fitko.dev/token" + # submissionApiUrls: ["http://test.url1", "http://test.url2", "http://test.url3"] + } + prod { + authTokenUrl: "https://auth-prod.fit-connect.fitko.dev/token" + } + test { + authTokenUrl: "https://auth-test.fit-connect.fitko.dev/token" + } + } +}