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 index d2d175dc40e3cb7859893380f75d0504ca1c7517..59ed0ea8a3caf3b15593090d9d6baba68b7df3cc 100644 --- a/api/src/main/java/de/fitko/fitconnect/api/services/Subscriber.java +++ b/api/src/main/java/de/fitko/fitconnect/api/services/Subscriber.java @@ -5,88 +5,93 @@ 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.event.SecurityEventToken; -import de.fitko.fitconnect.api.domain.model.submission.SubmissionRequest; +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 java.util.List; -import java.util.Optional; +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> + * @see <a href="https://docs.fitko.de/fit-connect/docs/receiving/overview">Receiving Submissions</a> */ public interface Subscriber { /** * Authenticates the {@link Subscriber} against the FitConnect-Auth-API and retrieves an {@link OAuthToken}. * - * @param clientId a unique client identifier + * @param clientId a unique client identifier * @param clientSecret the applications secret key - * @param scope 1 or more client scopes that determine if a submission is accepted by the client + * @param scope 1 or more client scopes that determine if a submission is accepted by the client * @return {@link OAuthToken}, is empty if an error occurred */ - Optional<OAuthToken> retrieveOAuthToken(final String clientId, final String clientSecret, final String... scope); + OAuthToken retrieveOAuthToken(final String clientId, final String clientSecret, final String... scope); /** - * Decrypts the JWE-encrypted submission data payload (json or xml). + * Decrypts JWE-encrypted string data. * - * @param privateKey the private key to decrypt the {@link Data} payload - * @param encryptedData the {@link Data} that is decrypted - * @return the decrypted {@link Data}, is empty if en error occurred + * @param privateKey the private key to decrypt the JWE-encrypted string + * @param encryptedContent the content that is decrypted + * @return the decrypted content as byte[] */ - Optional<Data> decryptSubmissionData(final RSAKey privateKey, final Data encryptedData); + byte[] decryptStringContent(final RSAKey privateKey, final String encryptedContent); /** - * Decrypts the JWE-encrypted submission attachment binary data. + * Polls for available {@link SubmissionSubmit}s on the given destinationId * - * @param privateKey the private key to decrypt the {@link Attachment} - * @param encryptedAttachment the JWE-encrypted {@link Attachment} that should be decrypted - * @return the decrypted {@link Attachment}, is empty if en error occurred + * @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 submissions */ - Optional<Attachment> decryptAttachment(final RSAKey privateKey, final Attachment encryptedAttachment); + List<SubmissionForPickup> pollAvailableSubmissions(UUID destinationId, int limit, int offset); /** - * Validates the {@link Metadata} structure against a given JSON-schema to ensure its correctness. + * Gets a specific {@link SubmissionSubmit}. * - * @param metadata the {@link Metadata} object that is validated - * @param jsonSchema the schema in JSON-format that the given {@link Metadata} structure is compared with - * @return a {@link ValidationResult}, contains error if the {@link Metadata} doesn't match the schema + * @param submissionId the unique identifier of a {@link SubmissionSubmit} + * @return the optional submission, is empty if none was found */ - ValidationResult validateMetadataSchema(final Metadata metadata, final String jsonSchema); + Submission getSubmission(UUID submissionId); /** - * Validates the hash-values of the {@link Metadata}s {@link Data} and {@link Attachment} in order to guarantee - * the integrity of the transmitted information. - * - * @param metadata the {@link Metadata} object including the hashes that where created by the {@link Sender} - * @return a {@link ValidationResult}, that contains errors if the hash-values of the transmitted data are not valid + * @param submissionId + * @param announcedAttachments + * @return */ - ValidationResult validateMetadataHashValues(final Metadata metadata); + List<Attachment> fetchAttachments(UUID submissionId, List<UUID> announcedAttachments); /** - * Polls for available {@link SubmissionRequest}s on the 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 submissions + * @param caseId + * @return */ - List<SubmissionRequest> pollAvailableSubmissions(String destinationId, int limit, int offset); + ValidationResult validateEventLog(UUID caseId); /** - * Gets a specific {@link SubmissionRequest}. - * - * @param submissionId the unique identifier of a {@link SubmissionRequest} - * - * @return the optional submission, is empty if none was found + * 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 error if the {@link Metadata} doesn't match the schema */ - Optional<SubmissionRequest> getSubmission(String submissionId); + ValidationResult validateMetadata(Metadata metadata); + /** + * @param data + * @return + */ + ValidationResult validateData(Data data); - Optional<SecurityEventToken> createSecurityEventToken(Data data, Attachment attachment, RSAKey privateKey); + /** + * @param attachments + * @return + */ + ValidationResult validateAttachments(List<Attachment> attachments); + /** + * + */ + void confirmValidSubmission(); } diff --git a/client/src/main/java/de/fitko/fitconnect/client/SubscriberClient.java b/client/src/main/java/de/fitko/fitconnect/client/SubscriberClient.java index 62e74c2a9f91e10b162e2717a3f92837c357eeec..144e0bc246ac0cba3bad7a2900308331bc852929 100644 --- a/client/src/main/java/de/fitko/fitconnect/client/SubscriberClient.java +++ b/client/src/main/java/de/fitko/fitconnect/client/SubscriberClient.java @@ -1,114 +1,142 @@ 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.submission.SubmissionRequest; +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 extends Client { +public class SubscriberClient { - private SubscriberClient() {} - - public static Authenticate builder() { - final Subscriber subscriber = getService(Subscriber.class); - return new ClientBuilder(subscriber); + private SubscriberClient() { } - public interface Authenticate { - PollSubmissions authenticate(String clientId, String secret, String... scope); + 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 PollSubmissions { - - List<SubmissionRequest> getAvailableSubmissions(String destinationId); - RequestMetadata requestSubmission(SubmissionRequest submission); - } + public interface RequestSubmission { + List<SubmissionForPickup> getAvailableSubmissions(UUID destinationId, int offset, int limit); - public interface RequestMetadata { - RequestAttachments requestMetadata(); - } + List<SubmissionForPickup> getAvailableSubmissions(UUID destinationId); - public interface RequestAttachments { - ConfirmSubmission requestAttachments(); + GetData requestSubmission(UUID submissionId); } - public interface ConfirmSubmission { - AccessData confirmSubmission(); - } - - public interface AccessData { - SubmissionRequest getSubmission(); + public interface GetData { List<Attachment> getAttachments(); + Metadata getMetadata(); + + Data getData(); } + public static class ClientBuilder implements RequestSubmission, GetData { - public static class ClientBuilder implements Authenticate, PollSubmissions, RequestMetadata, RequestAttachments, ConfirmSubmission, AccessData { + private static final int DEFAULT_SUBMISSION_LIMIT = 100; private final Subscriber subscriber; + private final String clientId; + private final String secret; + private final String privateKey; - private Optional<SubmissionRequest> submission = Optional.empty(); - private Optional<Metadata> metadata = Optional.empty(); - private List<Attachment> attachments = Collections.emptyList(); - private Optional<OAuthToken> token; - - private ClientBuilder(Subscriber subscriber) { + 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 PollSubmissions authenticate(String clientId, String secret, String... scope) { - this.token = subscriber.retrieveOAuthToken(clientId, secret, scope); - return this; + public List<SubmissionForPickup> getAvailableSubmissions(final UUID destinationId) { + return getAvailableSubmissions(destinationId, 0, DEFAULT_SUBMISSION_LIMIT); } @Override - public List<SubmissionRequest> getAvailableSubmissions(String destinationId) { - throw new UnsupportedOperationException("not yet implemented"); + public List<SubmissionForPickup> getAvailableSubmissions(final UUID destinationId, final int offset, final int limit) { + return subscriber.pollAvailableSubmissions(destinationId, offset, limit); } @Override - public RequestMetadata requestSubmission(SubmissionRequest submission) { - throw new UnsupportedOperationException("not yet implemented"); - } + 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(); - @Override - public RequestAttachments requestMetadata() { - throw new UnsupportedOperationException("not yet implemented"); + 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())); + 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 ConfirmSubmission requestAttachments() { - throw new UnsupportedOperationException("not yet implemented"); + public List<Attachment> getAttachments() { + return null; } @Override - public AccessData confirmSubmission() { - throw new UnsupportedOperationException("not yet implemented"); + public Metadata getMetadata() { + return null; } @Override - public SubmissionRequest getSubmission() { - return this.submission.orElseThrow(); + public Data getData() { + return null; } - @Override - public List<Attachment> getAttachments() { - return this.attachments; + private RSAKey parseDecryptionKey(final String privateKey) { + try { + return RSAKey.parse(privateKey); + } catch (final ParseException e) { + throw new DecryptionException("Key could not be parsed", e); + } } - @Override - public Metadata getMetadata() { - return this.metadata.orElseThrow(); + 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/impl/src/main/java/de/fitko/fitconnect/impl/SubmissionSubscriber.java b/core/src/main/java/de/fitko/fitconnect/core/SubmissionSubscriber.java similarity index 56% rename from impl/src/main/java/de/fitko/fitconnect/impl/SubmissionSubscriber.java rename to core/src/main/java/de/fitko/fitconnect/core/SubmissionSubscriber.java index 0a672df5a04b4f7123312aae6aea005bc0fadcad..b84b15bfe8d889609735f2a2d8151a94d00f7140 100644 --- a/impl/src/main/java/de/fitko/fitconnect/impl/SubmissionSubscriber.java +++ b/core/src/main/java/de/fitko/fitconnect/core/SubmissionSubscriber.java @@ -1,4 +1,4 @@ -package de.fitko.fitconnect.impl; +package de.fitko.fitconnect.core; import com.google.inject.Inject; import com.nimbusds.jose.jwk.RSAKey; @@ -6,10 +6,9 @@ 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.event.SecurityEventToken; -import de.fitko.fitconnect.api.domain.model.submission.SubmissionRequest; +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.internal.AuthenticationException; import de.fitko.fitconnect.api.services.Subscriber; import de.fitko.fitconnect.api.services.auth.OAuthService; import de.fitko.fitconnect.api.services.crypto.CryptoService; @@ -18,8 +17,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; -import java.util.Optional; - +import java.util.UUID; public class SubmissionSubscriber implements Subscriber { @@ -32,54 +30,62 @@ public class SubmissionSubscriber implements Subscriber { @Inject public SubmissionSubscriber(final OAuthService authService, final CryptoService cryptoService, - final MetadataValidator metadataValidator){ + final MetadataValidator metadataValidator) { this.authService = authService; this.cryptoService = cryptoService; this.metadataValidator = metadataValidator; } @Override - public Optional<OAuthToken> retrieveOAuthToken(String clientId, String clientSecret, String... scope) { - try { - return Optional.of(authService.authenticate(clientId, clientSecret, scope)); - } catch (AuthenticationException e) { - logger.error("Retrieving the OAuth token failed: ", e.getMessage()); - return Optional.empty(); - } + 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 Optional<Data> decryptSubmissionData(RSAKey privateKey, Data encryptedData) { + public List<SubmissionForPickup> pollAvailableSubmissions(final UUID destinationId, final int limit, final int offset) { throw new UnsupportedOperationException("not yet implemented"); } @Override - public Optional<Attachment> decryptAttachment(RSAKey privateKey, Attachment encryptedAttachment) { + public Submission getSubmission(final UUID submissionId) { throw new UnsupportedOperationException("not yet implemented"); } @Override - public ValidationResult validateMetadataSchema(Metadata metadata, String jsonSchema) { - return metadataValidator.validateMetadataSchema(metadata, jsonSchema); + public List<Attachment> fetchAttachments(final UUID submissionId, final List<UUID> announcedAttachments) { + throw new UnsupportedOperationException("not yet implemented"); } @Override - public ValidationResult validateMetadataHashValues(Metadata metadata) { - return metadataValidator.validateMetadataHashValues(metadata); + public ValidationResult validateEventLog(final UUID caseId) { + throw new UnsupportedOperationException("not yet implemented"); } @Override - public List<SubmissionRequest> pollAvailableSubmissions(String destinationId, int limit, int offset) { + public ValidationResult validateMetadata(final Metadata metadata) { throw new UnsupportedOperationException("not yet implemented"); } @Override - public Optional<SubmissionRequest> getSubmission(String submissionId) { + public ValidationResult validateData(final Data data) { throw new UnsupportedOperationException("not yet implemented"); } @Override - public Optional<SecurityEventToken> createSecurityEventToken(Data data, Attachment attachment, RSAKey privateKey) { + public ValidationResult validateAttachments(final List<Attachment> attachments) { throw new UnsupportedOperationException("not yet implemented"); } + + @Override + public void confirmValidSubmission() { + throw new UnsupportedOperationException("not yet implemented"); + } + }