Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • fit-connect/sdk-java
1 result
Show changes
Showing
with 1391 additions and 203 deletions
......@@ -47,12 +47,6 @@ public class SubmissionSender implements Sender {
this.keyService = keyService;
}
@Override
public ValidationResult validatePublicKey(final RSAKey publicKey) {
LOGGER.info("Validating public key integrity");
return validationService.validateEncryptionPublicKey(publicKey);
}
@Override
public ValidationResult validateMetadata(final Metadata metadata) {
LOGGER.info("Validating metadata");
......
......@@ -2,12 +2,19 @@ package dev.fitko.fitconnect.core;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.SignedJWT;
import dev.fitko.fitconnect.api.domain.model.destination.Destination;
import dev.fitko.fitconnect.api.domain.model.event.Event;
import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry;
import dev.fitko.fitconnect.api.domain.model.event.EventPayload;
import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags;
import dev.fitko.fitconnect.api.domain.model.metadata.Metadata;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentForValidation;
import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
import dev.fitko.fitconnect.api.domain.validation.ValidationResult;
import dev.fitko.fitconnect.api.exceptions.DecryptionException;
import dev.fitko.fitconnect.api.exceptions.EventCreationException;
import dev.fitko.fitconnect.api.exceptions.RestApiException;
import dev.fitko.fitconnect.api.services.Subscriber;
import dev.fitko.fitconnect.api.services.crypto.CryptoService;
import dev.fitko.fitconnect.api.services.events.EventLogService;
......@@ -44,7 +51,7 @@ public class SubmissionSubscriber implements Subscriber {
}
@Override
public byte[] decryptStringContent(final RSAKey privateKey, final String encryptedContent) {
public byte[] decryptStringContent(final RSAKey privateKey, final String encryptedContent) throws DecryptionException {
LOGGER.info("Decrypting string");
return cryptoService.decryptToBytes(privateKey, encryptedContent);
}
......@@ -62,32 +69,50 @@ public class SubmissionSubscriber implements Subscriber {
}
@Override
public Submission getSubmission(final UUID submissionId) {
public Submission getSubmission(final UUID submissionId) throws RestApiException {
LOGGER.info("Loading submission {}", submissionId);
return submissionService.getSubmission(submissionId);
}
@Override
public String fetchAttachment(final UUID submissionId, final UUID attachmentId) {
public String fetchAttachment(final UUID submissionId, final UUID attachmentId) throws RestApiException {
LOGGER.info("Loading attachment {} for submission {}", attachmentId, submissionId);
return submissionService.getAttachment(submissionId, attachmentId);
}
@Override
public List<EventLogEntry> getEventLog(final UUID caseId, final UUID destinationId) {
public List<EventLogEntry> getEventLog(final UUID caseId, final UUID destinationId) throws RestApiException {
LOGGER.info("Loading event log for destination {}", destinationId);
return eventLogService.getEventLog(caseId, destinationId);
}
@Override
public AuthenticationTags getAuthenticationTagsForEvent(final Event event, final Submission submission) {
LOGGER.info("Loading authentication tags of {} event for submission {}", event, submission.getSubmissionId());
return eventLogService.getAuthenticationTagsForEvent(event, submission);
}
@Override
public ValidationResult validateMetadata(final Metadata metadata, final Submission submission, final AuthenticationTags authenticationTags) {
LOGGER.info("Validating metadata");
final Destination destination = submissionService.getDestination(submission.getDestinationId());
return validationService.validateMetadata(metadata, submission, destination, authenticationTags);
}
@Override
public ValidationResult validateData(final byte[] data, final Submission submission, final Metadata metadata, final AuthenticationTags authenticationTags) {
LOGGER.info("Validating data");
return validationService.validateData(data, submission, metadata, authenticationTags);
}
@Override
public ValidationResult validateMetadata(final Metadata metadata) {
LOGGER.info("Validating metadata schema");
return validationService.validateMetadataSchema(metadata);
public ValidationResult validateAttachments(final List<AttachmentForValidation> attachmentsForValidation, final AuthenticationTags authenticationTags) {
LOGGER.info("Validating attachments");
return validationService.validateAttachments(attachmentsForValidation, authenticationTags);
}
@Override
public ValidationResult validateHashIntegrity(final String originalHash, final byte[] data) {
LOGGER.info("Validating data hash integrity");
LOGGER.info("Validating hash integrity");
return validationService.validateHashIntegrity(originalHash, data);
}
......@@ -98,7 +123,7 @@ public class SubmissionSubscriber implements Subscriber {
}
@Override
public void acceptSubmission(final EventPayload eventPayload) {
public void acceptSubmission(final EventPayload eventPayload) throws RestApiException, EventCreationException {
LOGGER.info("Accepting submission");
final SignedJWT confirmedSubmissionEvent = securityEventService.createAcceptSubmissionEvent(eventPayload);
eventLogService.sendEvent(eventPayload.getCaseId(), confirmedSubmissionEvent.serialize());
......@@ -106,7 +131,7 @@ public class SubmissionSubscriber implements Subscriber {
}
@Override
public void rejectSubmission(final EventPayload eventPayload) {
public void rejectSubmission(final EventPayload eventPayload) throws RestApiException, EventCreationException {
LOGGER.info("Rejecting submission");
final SignedJWT rejectSubmissionEvent = securityEventService.createRejectSubmissionEvent(eventPayload);
eventLogService.sendEvent(eventPayload.getCaseId(), rejectSubmissionEvent.serialize());
......
......@@ -2,7 +2,13 @@ package dev.fitko.fitconnect.core.crypto;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.*;
import com.nimbusds.jose.CompressionAlgorithm;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.RSADecrypter;
import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.jwk.RSAKey;
......@@ -78,7 +84,7 @@ public class JWECryptoService implements CryptoService {
jwe.decrypt(new RSADecrypter(privateKey));
LOGGER.info("Decrypting {} bytes took {} ", encData.getBytes().length, StopWatch.stopWithFormattedTime(start));
return jwe.getPayload();
} catch (final ParseException | JOSEException e) {
} catch (final ParseException | IllegalStateException | JOSEException e) {
throw new DecryptionException(e.getMessage(), e);
}
}
......
......@@ -2,18 +2,20 @@ package dev.fitko.fitconnect.core.events;
import com.nimbusds.jwt.SignedJWT;
import dev.fitko.fitconnect.api.config.ApplicationConfig;
import dev.fitko.fitconnect.api.domain.model.event.Event;
import dev.fitko.fitconnect.api.domain.model.event.EventLog;
import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry;
import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus;
import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags;
import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.domain.validation.ValidationContext;
import dev.fitko.fitconnect.api.domain.validation.ValidationResult;
import dev.fitko.fitconnect.api.exceptions.AuthenticationTagsEmptyException;
import dev.fitko.fitconnect.api.exceptions.EventLogException;
import dev.fitko.fitconnect.api.exceptions.RestApiException;
import dev.fitko.fitconnect.api.exceptions.SubmitEventNotFoundException;
import dev.fitko.fitconnect.api.services.auth.OAuthService;
import dev.fitko.fitconnect.api.services.events.EventLogService;
import dev.fitko.fitconnect.api.services.events.EventLogVerificationService;
import dev.fitko.fitconnect.core.util.EventLogUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpEntity;
......@@ -28,6 +30,13 @@ import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import static dev.fitko.fitconnect.core.util.EventLogUtil.eventEqualsSubmissionId;
import static dev.fitko.fitconnect.core.util.EventLogUtil.eventToEntry;
import static dev.fitko.fitconnect.core.util.EventLogUtil.getAuthenticationTags;
import static dev.fitko.fitconnect.core.util.EventLogUtil.getJWTSFromEvents;
import static dev.fitko.fitconnect.core.util.EventLogUtil.getJwtsFromEvents;
import static dev.fitko.fitconnect.core.util.EventLogUtil.mapEventLogToEntries;
public class EventLogApiService implements EventLogService {
private static final Logger LOGGER = LoggerFactory.getLogger(EventLogApiService.class);
......@@ -48,37 +57,59 @@ public class EventLogApiService implements EventLogService {
}
@Override
public List<EventLogEntry> getEventLog(final UUID caseId, final UUID destinationId) {
public List<EventLogEntry> getEventLog(final UUID caseId, final UUID destinationId) throws EventLogException {
final EventLog eventLog = loadEventLog(caseId);
final List<SignedJWT> events = EventLogUtil.getJWTSFromEvents(eventLog.getEventLogs());
final List<SignedJWT> events = getJWTSFromEvents(eventLog.getEventLogs());
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
validateLog(eventLogVerifier.validateEventLogs(ctx, events));
return EventLogUtil.mapEventLogToEntries(eventLog);
evaluateLogValidation(eventLogVerifier.validateEventLogs(ctx, events));
return mapEventLogToEntries(eventLog);
}
@Override
public AuthenticationTags getAuthenticationTagsForEvent(final Event event, final Submission submission) throws EventLogException {
final EventLog eventLog = loadEventLog(submission.getCaseId());
final List<SignedJWT> submitEvents = getJwtsFromEvents(event, submission, eventLog);
if (submitEvents.size() != 1) {
throw new SubmitEventNotFoundException("Event log does not contain exactly one submit event");
}
final SignedJWT submitEvent = submitEvents.stream().findFirst().get();
final AuthenticationTags authenticationTags = getAuthenticationTags(submitEvent);
if(authenticationTags.getMetadata() == null && authenticationTags.getData() == null){
throw new AuthenticationTagsEmptyException("Authentication tags are empty");
}
verifyEventWithAuthTags(submission, submitEvent, authenticationTags);
return authenticationTags;
}
@Override
public SubmissionStatus getLastedEvent(final UUID destinationId, final UUID caseId, final UUID submissionId, final AuthenticationTags authenticationTags) throws RestApiException, EventLogException {
public SubmissionStatus getLastedEvent(final UUID destinationId, final UUID caseId, final UUID submissionId, final AuthenticationTags authenticationTags) throws EventLogException {
final EventLog eventLog = loadEventLog(caseId);
final SignedJWT latestEvent = getLatestEventForSubmission(submissionId, eventLog);
final SignedJWT latestEvent = getLatestEventForSubmission(submissionId, eventLog.getEventLogs());
if (latestEvent == null) {
LOGGER.info("No events found for submission {}", submissionId);
return new SubmissionStatus();
}
final var ctx = new ValidationContext(destinationId, caseId, authenticationTags);
validateLog(eventLogVerifier.validateEventLogs(ctx, List.of(latestEvent)));
final EventLogEntry eventLogEntry = EventLogUtil.eventToEntry(latestEvent);
evaluateLogValidation(eventLogVerifier.validateEventLogs(ctx, List.of(latestEvent)));
final EventLogEntry eventLogEntry = eventToEntry(latestEvent);
return new SubmissionStatus(eventLogEntry.getEvent().getState(), eventLogEntry.getProblems());
}
@Override
public void sendEvent(final UUID caseId, final String signedAndSerializedSET) {
public void sendEvent(final UUID caseId, final String signedAndSerializedSET) throws EventLogException {
final String url = config.getEventsEndpoint();
final HttpHeaders headers = getHttpHeaders("application/jose");
final HttpEntity<String> entity = new HttpEntity<>(signedAndSerializedSET, headers);
try {
restTemplate.exchange(url, HttpMethod.POST, entity, Void.class, caseId);
} catch (final RestClientException e) {
throw new RestApiException("Sending event failed", e);
throw new EventLogException("Sending event failed", e);
}
}
......@@ -89,7 +120,7 @@ public class EventLogApiService implements EventLogService {
try {
return restTemplate.exchange(url, HttpMethod.GET, entity, EventLog.class, caseId).getBody();
} catch (final RestClientException e) {
throw new RestApiException("EventLog query failed", e);
throw new EventLogException("EventLog query failed", e);
}
}
......@@ -109,21 +140,33 @@ public class EventLogApiService implements EventLogService {
return headers;
}
private SignedJWT getLatestEventForSubmission(final UUID submissionId, final EventLog eventLog) {
return EventLogUtil.getJWTSFromEvents(eventLog.getEventLogs()).stream()
.filter(EventLogUtil.eventEqualsSubmissionId(submissionId))
private SignedJWT getLatestEventForSubmission(final UUID submissionId, final List<String> events) {
return getJWTSFromEvents(events).stream()
.filter(eventEqualsSubmissionId(submissionId))
.reduce((first, second) -> second)
.orElse(null);
}
private void validateLog(final List<ValidationResult> results) {
if (!results.isEmpty()) {
final String errorMessages = results.stream()
private void verifyEventWithAuthTags(final Submission submission, final SignedJWT submitEvent, final AuthenticationTags authenticationTags) {
final var ctx = new ValidationContext(submission.getDestinationId(), submission.getCaseId(), authenticationTags);
final List<ValidationResult> validationResults = eventLogVerifier.validateEventLogs(ctx, List.of(submitEvent));
if(!validationResults.isEmpty()){
final String errorMessages = validationResults.stream()
.map(ValidationResult::getError)
.map(Throwable::getMessage)
.distinct()
.map(Exception::getMessage)
.collect(Collectors.joining("\n"));
throw new EventLogException("Event log contains invalid entries: \n" + errorMessages);
throw new EventLogException(errorMessages);
}
}
private void evaluateLogValidation(final List<ValidationResult> results) {
results.stream()
.filter(ValidationResult::hasError)
.findFirst()
.ifPresent((result) -> {
throw new EventLogException(result.getError().getMessage(), result.getError());
});
}
}
......@@ -5,6 +5,7 @@ import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.KeyOperation;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
......@@ -99,12 +100,12 @@ public class EventLogVerifier implements EventLogVerificationService {
if (header.getAlgorithm() == null) {
ctx.addError("The provided alg in the SET header must not be null.");
} else {
ctx.addResult(header.getAlgorithm() == JWSAlgorithm.PS512, "The provided alg in the SET header is not allowed.");
ctx.addErrorIfTestFailed(header.getAlgorithm() == JWSAlgorithm.PS512, "The provided alg in the SET header is not allowed.");
}
if (header.getType() == null) {
ctx.addError("The provided typ in the SET header must not be null.");
} else {
ctx.addResult(header.getType().toString().equals(HEADER_TYPE), "The provided typ in the SET header is not " + HEADER_TYPE);
ctx.addErrorIfTestFailed(header.getType().toString().equals(HEADER_TYPE), "The provided typ in the SET header is not " + HEADER_TYPE);
}
}
......@@ -122,16 +123,16 @@ public class EventLogVerifier implements EventLogVerificationService {
}
private void validatePayload(final ValidationContext ctx, final JWTClaimsSet claims) throws ParseException {
ctx.addResult(claims.getClaim(ISSUER) != null, "The claim iss is missing in the payload of the SET.");
ctx.addResult(claims.getClaim(ISSUED_AT) != null, "The claim iat is missing in the payload of the SET.");
ctx.addResult(claims.getClaim(JWT_ID) != null, "The claim jti is missing in the payload of the SET.");
ctx.addResult(claims.getClaim(SUBJECT) != null, "The claim sub is missing in the payload of the SET.");
ctx.addResult(claims.getClaim(CLAIM_TXN) != null, "The claim txn is missing in the payload of the SET.");
ctx.addResult(claims.getClaim(CLAIM_EVENTS) != null, "The claim events is missing in the payload of the SET.");
ctx.addResult(claims.getJSONObjectClaim(CLAIM_EVENTS).keySet().size() == 1, "The events claims has must be exactly one event.");
ctx.addResult(claims.getStringClaim("sub").matches("(submission|case|reply):" + UUID_V4_PATTERN), "The provided subject does not match the allowed pattern.");
ctx.addResult(claims.getStringClaim(CLAIM_TXN).matches("case:" + UUID_V4_PATTERN), "The provided txn does not match the allowed pattern.");
getEventClaim(claims).ifPresentOrElse(event -> ctx.addResult(Event.fromSchemaUri(event) != null, "The provided event is not a valid event supported by this instance."),
ctx.addErrorIfTestFailed(claims.getClaim(ISSUER) != null, "The claim iss is missing in the payload of the SET.");
ctx.addErrorIfTestFailed(claims.getClaim(ISSUED_AT) != null, "The claim iat is missing in the payload of the SET.");
ctx.addErrorIfTestFailed(claims.getClaim(JWT_ID) != null, "The claim jti is missing in the payload of the SET.");
ctx.addErrorIfTestFailed(claims.getClaim(SUBJECT) != null, "The claim sub is missing in the payload of the SET.");
ctx.addErrorIfTestFailed(claims.getClaim(CLAIM_TXN) != null, "The claim txn is missing in the payload of the SET.");
ctx.addErrorIfTestFailed(claims.getClaim(CLAIM_EVENTS) != null, "The claim events is missing in the payload of the SET.");
ctx.addErrorIfTestFailed(claims.getJSONObjectClaim(CLAIM_EVENTS).keySet().size() == 1, "The events claims has must be exactly one event.");
ctx.addErrorIfTestFailed(claims.getStringClaim("sub").matches("(submission|case|reply):" + UUID_V4_PATTERN), "The provided subject does not match the allowed pattern.");
ctx.addErrorIfTestFailed(claims.getStringClaim(CLAIM_TXN).matches("case:" + UUID_V4_PATTERN), "The provided txn does not match the allowed pattern.");
getEventClaim(claims).ifPresentOrElse(event -> ctx.addErrorIfTestFailed(Event.fromSchemaUri(event) != null, "The provided event is not a valid event supported by this instance."),
() -> ctx.addError("No events in JWT"));
}
......@@ -139,7 +140,7 @@ public class EventLogVerifier implements EventLogVerificationService {
if (signatureKey == null) {
ctx.addError("The signature key cannot be validated, it must not be null.");
} else {
ctx.addResult(validationService.validateSignaturePublicKey(signatureKey));
ctx.addResult(validationService.validatePublicKey(signatureKey, KeyOperation.VERIFY));
}
}
......@@ -148,7 +149,7 @@ public class EventLogVerifier implements EventLogVerificationService {
ctx.addError("The signature cannot validated, signature key is unavailable.");
} else {
final JWSVerifier jwsVerifier = new RSASSAVerifier(signatureKey);
ctx.addResult(signedJWT.verify(jwsVerifier), "The signature of the token could not be verified with the specified key.");
ctx.addErrorIfTestFailed(signedJWT.verify(jwsVerifier), "The signature of the token could not be verified with the specified key.");
}
}
......@@ -177,15 +178,15 @@ public class EventLogVerifier implements EventLogVerificationService {
ctx.addError("The event '" + event.getSchemaUri() + "' has to be created by the destination ('iss' claim must be an UUID)");
}
if (destinationId != null) {
ctx.addResult(destinationId.equals(ctx.getDestinationId()), "The destination of the submission is not the issuer ('iss' claim must match submission.destinationId)");
ctx.addErrorIfTestFailed(destinationId.equals(ctx.getDestinationId()), "The destination of the submission is not the issuer ('iss' claim must match submission.destinationId)");
}
}
private void validateAuthenticationTags(final ValidationContext ctx, final JWTClaimsSet payload) throws ParseException {
final AuthenticationTags eventAuthTags = getAuthenticationTags(payload);
final AuthenticationTags submissionAuthTags = ctx.getAuthenticationTags();
ctx.addResult(eventAuthTags != null, "AuthenticationTags of event must not be null.");
ctx.addResult(submissionAuthTags != null, "AuthenticationTags of submission must not be null.");
ctx.addErrorIfTestFailed(eventAuthTags != null, "AuthenticationTags of event must not be null.");
ctx.addErrorIfTestFailed(submissionAuthTags != null, "AuthenticationTags of submission must not be null.");
if (eventAuthTags != null && submissionAuthTags != null) {
validateDataAuthTags(ctx, eventAuthTags.getData(), submissionAuthTags.getData());
validateMetadataAuthTags(ctx, eventAuthTags.getMetadata(), submissionAuthTags.getMetadata());
......@@ -195,20 +196,20 @@ public class EventLogVerifier implements EventLogVerificationService {
private void validateAttachmentsAuthTags(final ValidationContext ctx, final Map<UUID, String> eventAttachmentTags, final Map<UUID, String> submissionAttachmentTags) {
if (eventAttachmentTags != null && submissionAttachmentTags != null) {
ctx.addResult(eventAttachmentTags.size() == submissionAttachmentTags.size(), "The events quantity of attachments does not match the submission.");
ctx.addErrorIfTestFailed(eventAttachmentTags.size() == submissionAttachmentTags.size(), "The events quantity of attachments does not match the submission.");
eventAttachmentTags.forEach((key, eventTag) -> {
final String submissionTag = submissionAttachmentTags.get(key);
ctx.addResult(eventTag.equals(submissionTag), "The authentication-tag for the attachment " + key + " does not match the submission.");
ctx.addErrorIfTestFailed(eventTag.equals(submissionTag), "The authentication-tag for the attachment " + key + " does not match the submission.");
});
}
}
private void validateDataAuthTags(final ValidationContext ctx, final String eventDataAuthTag, final String submissionDataAuthTag) {
ctx.addResult(eventDataAuthTag.equals(submissionDataAuthTag), "The events data authentication-tag does not match the submission.");
ctx.addErrorIfTestFailed(eventDataAuthTag.equals(submissionDataAuthTag), "The events data authentication-tag does not match the submission.");
}
private void validateMetadataAuthTags(final ValidationContext ctx, final String eventMetadataAuthTag, final String submissionMetadataAuthTag) {
ctx.addResult(eventMetadataAuthTag.equals(submissionMetadataAuthTag), "The events metadata authentication-tag does not match the submission.");
ctx.addErrorIfTestFailed(eventMetadataAuthTag.equals(submissionMetadataAuthTag), "The events metadata authentication-tag does not match the submission.");
}
private static boolean eventContainsAuthTags(final JWTClaimsSet payload) throws ParseException {
......
......@@ -21,6 +21,7 @@ import dev.fitko.fitconnect.api.services.events.SecurityEventService;
import dev.fitko.fitconnect.api.services.validation.ValidationService;
import java.text.ParseException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
......@@ -123,6 +124,9 @@ public class SecurityEventTokenService implements SecurityEventService {
}
private Map<UUID, String> buildAttachmentAuthenticationTags(final Map<UUID, String> encryptedAttachments) {
if(encryptedAttachments == null || encryptedAttachments.isEmpty()){
return Collections.emptyMap();
}
return encryptedAttachments
.entrySet()
.stream()
......
......@@ -2,6 +2,7 @@ package dev.fitko.fitconnect.core.keys;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.jwk.KeyOperation;
import com.nimbusds.jose.jwk.RSAKey;
import dev.fitko.fitconnect.api.config.ApplicationConfig;
import dev.fitko.fitconnect.api.domain.model.destination.Destination;
......@@ -107,12 +108,12 @@ public class PublicKeyService implements KeyService {
}
private void validateEncryptionKey(final RSAKey rsaKey) {
final ValidationResult result = validationService.validateEncryptionPublicKey(rsaKey);
final ValidationResult result = validationService.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY);
validateResult(result, "Invalid public encryption key");
}
private void validateSignatureKey(final RSAKey rsaKey) {
final ValidationResult result = validationService.validateSignaturePublicKey(rsaKey);
final ValidationResult result = validationService.validatePublicKey(rsaKey, KeyOperation.VERIFY);
validateResult(result, "Public signature key is not valid");
}
......
......@@ -19,7 +19,9 @@ import lombok.Setter;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
......@@ -42,7 +44,7 @@ public class SubmissionApiService implements SubmissionService {
}
@Override
public Destination getDestination(final UUID destinationID) {
public Destination getDestination(final UUID destinationID) throws RestApiException {
final RequestSettings requestSettings = RequestSettings.builder()
.url(config.getDestinationsEndpoint())
.method(HttpMethod.GET)
......@@ -55,7 +57,7 @@ public class SubmissionApiService implements SubmissionService {
}
@Override
public SubmissionForPickup announceSubmission(final CreateSubmission submission) {
public SubmissionForPickup announceSubmission(final CreateSubmission submission) throws RestApiException {
final RequestSettings requestSettings = RequestSettings.builder()
.url(config.getSubmissionsEndpoint())
.method(HttpMethod.POST)
......@@ -68,7 +70,7 @@ public class SubmissionApiService implements SubmissionService {
}
@Override
public void uploadAttachment(final UUID submissionId, final UUID attachmentId, final String encryptedAttachment) {
public void uploadAttachment(final UUID submissionId, final UUID attachmentId, final String encryptedAttachment) throws RestApiException {
final Map<String, Object> params = new HashMap<>();
params.put("submissionId", submissionId);
params.put("attachmentId", attachmentId);
......@@ -84,7 +86,7 @@ public class SubmissionApiService implements SubmissionService {
}
@Override
public String getAttachment(final UUID submissionId, final UUID attachmentId) {
public String getAttachment(final UUID submissionId, final UUID attachmentId) throws RestApiException {
final Map<String, Object> params = new HashMap<>();
params.put("submissionId", submissionId);
params.put("attachmentId", attachmentId);
......@@ -100,7 +102,7 @@ public class SubmissionApiService implements SubmissionService {
}
@Override
public Submission sendSubmission(final SubmitSubmission submission) {
public Submission sendSubmission(final SubmitSubmission submission) throws RestApiException {
final Map<String, Object> params = Map.of("submissionId", submission.getSubmissionId());
final RequestSettings requestSettings = RequestSettings.builder()
.url(config.getSubmissionEndpoint())
......@@ -114,7 +116,7 @@ public class SubmissionApiService implements SubmissionService {
}
@Override
public SubmissionsForPickup pollAvailableSubmissions(final int offset, final int limit) {
public SubmissionsForPickup pollAvailableSubmissions(final int offset, final int limit) throws RestApiException {
final String urlWithQueryParams = UriComponentsBuilder.fromHttpUrl(config.getSubmissionsEndpoint())
.queryParam("limit", limit)
.queryParam("offset", offset)
......@@ -149,7 +151,7 @@ public class SubmissionApiService implements SubmissionService {
}
@Override
public Submission getSubmission(final UUID submissionId) {
public Submission getSubmission(final UUID submissionId) throws RestApiException {
final Map<String, Object> params = Map.of("submissionId", submissionId);
final RequestSettings requestSettings = RequestSettings.builder()
.url(config.getSubmissionEndpoint())
......@@ -169,12 +171,19 @@ public class SubmissionApiService implements SubmissionService {
final Class<T> responseType = requestSettings.responseType;
final Map<String, ?> params = requestSettings.params;
try {
return restTemplate.exchange(url, method, entity, responseType, params).getBody();
return evaluateStatusCode(restTemplate.exchange(url, method, entity, responseType, params));
} catch (final RestClientException e) {
throw new RestApiException(e.getMessage(), e);
}
}
private <T> T evaluateStatusCode(final ResponseEntity<T> response) {
if(response.getStatusCode().equals(HttpStatus.NOT_FOUND)){
throw new RestApiException(response.getStatusCode(), "requested entity could not be found");
}
return response.getBody();
}
private HttpEntity<String> getHttpEntity(final HttpHeaders headers) {
return getHttpEntity(null, headers);
}
......
......@@ -4,19 +4,31 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import dev.fitko.fitconnect.api.domain.model.event.*;
import dev.fitko.fitconnect.api.config.ApplicationConfig;
import dev.fitko.fitconnect.api.domain.model.event.Event;
import dev.fitko.fitconnect.api.domain.model.event.EventClaimFields;
import dev.fitko.fitconnect.api.domain.model.event.EventIssuer;
import dev.fitko.fitconnect.api.domain.model.event.EventLog;
import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry;
import dev.fitko.fitconnect.api.domain.model.event.TypeAndUUID;
import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags;
import dev.fitko.fitconnect.api.domain.model.event.problems.Problem;
import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.exceptions.EventCreationException;
import dev.fitko.fitconnect.api.exceptions.EventLogException;
import java.text.ParseException;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
import static dev.fitko.fitconnect.api.domain.model.event.EventIssuer.*;
import static dev.fitko.fitconnect.api.domain.model.event.EventIssuer.DESTINATION;
import static dev.fitko.fitconnect.api.domain.model.event.EventIssuer.SUBMISSION_SERVICE;
import static dev.fitko.fitconnect.api.domain.model.event.EventIssuer.UNDEFINED;
public final class EventLogUtil {
......@@ -79,8 +91,8 @@ public final class EventLogUtil {
final String eventName = eventEntry.getKey();
final Map<String, Object> eventPayload = eventEntry.getValue();
final List<Problem> problems = new ArrayList<>();
if (eventPayload.containsKey("problems")) {
problems.addAll((List<Problem>) eventPayload.get("problems"));
if (eventPayload.containsKey(EventClaimFields.PROBLEMS)) {
problems.addAll((List<Problem>) eventPayload.get(EventClaimFields.PROBLEMS));
}
return EventLogEntry.builder()
.event(Event.fromSchemaUri(eventName))
......@@ -88,7 +100,7 @@ public final class EventLogUtil {
.issueTime(claimsSet.getIssueTime())
.eventId(getEventId(claimsSet))
.caseId(getTransactionClaim(claimsSet))
.submissionId(getSubmissionId(claimsSet))
.submissionId(getSubmissionIdFromJWT(claimsSet))
.problems(problems)
.build();
}
......@@ -107,10 +119,14 @@ public final class EventLogUtil {
}
}
private static UUID getSubmissionId(final JWTClaimsSet claimsSet) {
public static UUID getSubmissionIdFromJWT(final JWTClaimsSet claimsSet) {
return TypeAndUUID.fromString(claimsSet.getSubject()).getUuid();
}
public static UUID getSubmissionIdFromJWT(final SignedJWT signedJWT) {
return TypeAndUUID.fromString(getClaimsSet(signedJWT).getSubject()).getUuid();
}
private static UUID getEventId(final JWTClaimsSet claimsSet) {
return UUID.fromString(claimsSet.getJWTID());
}
......@@ -139,18 +155,38 @@ public final class EventLogUtil {
}
}
public static AuthenticationTags getAuthenticationTags(final SignedJWT jwt) {
return getAuthenticationTags(getClaimsSet(jwt));
}
public static AuthenticationTags getAuthenticationTags(final JWTClaimsSet claims) {
try {
final Map<String, Object> eventsClaim = (Map) claims.getClaim(EventClaimFields.CLAIM_EVENTS);
final Map.Entry<String, Map> eventEntry = (Map.Entry) eventsClaim.entrySet().iterator().next();
final Map<String, Object> events = eventEntry.getValue();
final String authTags = MAPPER.writeValueAsString(events.get("authenticationTags"));
final String authTags = MAPPER.writeValueAsString(events.get(EventClaimFields.AUTHENTICATION_TAGS));
return MAPPER.readValue(authTags, AuthenticationTags.class);
} catch (final JsonProcessingException e) {
throw new EventCreationException(e.getMessage(), e);
}
}
public static String getAuthenticationTagFromEncryptedData(final String encryptedData) {
try {
final String[] parts = encryptedData.split(ApplicationConfig.AUTH_TAG_SPLIT_TOKEN);
return parts[parts.length - 1];
} catch (final Exception e) {
return null;
}
}
public static List<SignedJWT> getJwtsFromEvents(final Event event, final Submission submission, final EventLog eventLog) {
return getJWTSFromEvents(eventLog.getEventLogs()).stream()
.filter(jwt -> getSubmissionIdFromJWT(jwt).equals(submission.getSubmissionId()))
.filter(jwt -> getEventFromJWT(jwt).equals(event))
.collect(Collectors.toList());
}
public static Predicate<SignedJWT> eventEqualsSubmissionId(final UUID submissionId) {
return jwt -> {
try {
......
package dev.fitko.fitconnect.core.validation;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
......@@ -11,8 +12,35 @@ import com.nimbusds.jose.jwk.KeyOperation;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.util.Base64;
import dev.fitko.fitconnect.api.config.ApplicationConfig;
import dev.fitko.fitconnect.api.domain.model.destination.Destination;
import dev.fitko.fitconnect.api.domain.model.destination.DestinationService;
import dev.fitko.fitconnect.api.domain.model.event.EventClaimFields;
import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags;
import dev.fitko.fitconnect.api.domain.model.event.problems.Problem;
import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.AttachmentHashMismatch;
import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.IncorrectAttachmentAuthenticationTag;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataHashMismatch;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataJsonSyntaxViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataXmlSyntaxViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.IncorrectDataAuthenticationTag;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.AttachmentsMismatch;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.IncorrectMetadataAuthenticationTag;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MetadataJsonSyntaxViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MetadataSchemaViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MissingData;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.ServiceMismatch;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.UnsupportedDataSchema;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.UnsupportedMetadataSchema;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.UnsupportedReplyChannel;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.UnsupportedService;
import dev.fitko.fitconnect.api.domain.model.metadata.Metadata;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentForValidation;
import dev.fitko.fitconnect.api.domain.model.metadata.data.Data;
import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema;
import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel;
import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.domain.validation.ValidationResult;
import dev.fitko.fitconnect.api.exceptions.DataIntegrityException;
import dev.fitko.fitconnect.api.exceptions.SchemaNotFoundException;
......@@ -20,7 +48,6 @@ import dev.fitko.fitconnect.api.exceptions.ValidationException;
import dev.fitko.fitconnect.api.services.crypto.MessageDigestService;
import dev.fitko.fitconnect.api.services.schema.SchemaProvider;
import dev.fitko.fitconnect.api.services.validation.ValidationService;
import dev.fitko.fitconnect.core.util.Strings;
import dev.fitko.fitconnect.jwkvalidator.JWKValidator;
import dev.fitko.fitconnect.jwkvalidator.JWKValidatorBuilder;
import dev.fitko.fitconnect.jwkvalidator.exceptions.JWKValidationException;
......@@ -47,20 +74,25 @@ import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static dev.fitko.fitconnect.core.util.EventLogUtil.getAuthenticationTagFromEncryptedData;
import static dev.fitko.fitconnect.jwkvalidator.JWKValidator.withX5CValidation;
import static dev.fitko.fitconnect.jwkvalidator.JWKValidator.withoutX5CValidation;
import static java.util.Objects.isNull;
public class DefaultValidationService implements ValidationService {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultValidationService.class);
private static final ObjectMapper MAPPER = new ObjectMapper().setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
private static final ObjectMapper MAPPER = new ObjectMapper().setDateFormat(new SimpleDateFormat("yyyy-MM-dd")).setSerializationInclusion(JsonInclude.Include.NON_NULL);
private static final JsonSchemaFactory SCHEMA_FACTORY_DRAFT_2020 = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
private static final JsonSchemaFactory SCHEMA_FACTORY_DRAFT_2007 = JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7);
public static final String VALID_SCHEMA_URL_EXPRESSION = "https://schema\\.fitko\\.de/fit-connect/metadata/1\\.\\d+\\.\\d+/metadata.schema.json";
......@@ -75,19 +107,9 @@ public class DefaultValidationService implements ValidationService {
}
@Override
public ValidationResult validateEncryptionPublicKey(final RSAKey publicKey) {
public ValidationResult validatePublicKey(final RSAKey publicKey, final KeyOperation keyOperation) {
try {
validateKey(publicKey, KeyOperation.WRAP_KEY);
} catch (final Exception e) {
return ValidationResult.error(e);
}
return ValidationResult.ok();
}
@Override
public ValidationResult validateSignaturePublicKey(final RSAKey signatureKey) {
try {
validateKey(signatureKey, KeyOperation.VERIFY);
validateKey(publicKey, keyOperation);
} catch (final Exception e) {
return ValidationResult.error(e);
}
......@@ -113,7 +135,9 @@ public class DefaultValidationService implements ValidationService {
public ValidationResult validateMetadataSchema(final Metadata metadata) {
if (metadata.getSchema() != null && !metadata.getSchema().isEmpty() && !metadata.getSchema().matches(VALID_SCHEMA_URL_EXPRESSION)) {
return ValidationResult.error(new ValidationException("The provided metadata schema is not supported."));
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#required-schema-reference
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#metadatenschema
return ValidationResult.problem(new UnsupportedMetadataSchema(metadata.getSchema()));
}
try {
......@@ -121,7 +145,8 @@ public class DefaultValidationService implements ValidationService {
final JsonNode inputNode = MAPPER.readTree(metadataJson);
return validate2020JsonSchema(schemaProvider.loadMetadataSchema(config.getMetadataSchemaWriteVersion()), inputNode);
} catch (final JsonProcessingException e) {
return ValidationResult.error(e);
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#schema-pr%C3%BCfung
return ValidationResult.withErrorAndProblem(e, new MetadataSchemaViolation());
}
}
......@@ -143,7 +168,7 @@ public class DefaultValidationService implements ValidationService {
final byte[] originalHash = messageDigestService.fromHexString(originalHexHash);
final boolean hashesAreNotEqual = !messageDigestService.verify(originalHash, data);
if (hashesAreNotEqual) {
return ValidationResult.error(new DataIntegrityException("Hash sum of transmitted data does not equal the hash of the sender."));
return ValidationResult.error(new DataIntegrityException("Metadata contains invalid hash value"));
}
return ValidationResult.ok();
} catch (final IllegalArgumentException | NullPointerException e) {
......@@ -151,6 +176,7 @@ public class DefaultValidationService implements ValidationService {
}
}
@Override
public ValidationResult validateJsonFormat(final String json) {
try {
......@@ -190,6 +216,148 @@ public class DefaultValidationService implements ValidationService {
return ValidationResult.ok();
}
@Override
public ValidationResult validateData(final byte[] decryptedData, final Submission submission, final Metadata metadata, final AuthenticationTags authenticationTags) {
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen-1
final var dataAuthTag = getAuthenticationTagFromEncryptedData(submission.getEncryptedData());
if (!authenticationTags.getData().equals(dataAuthTag)) {
return ValidationResult.problem(new IncorrectDataAuthenticationTag());
}
final Data data = metadata.getContentStructure().getData();
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#submission-data-hash
final String hashFromSender = data.getHash().getContent();
final ValidationResult hashValidation = validateHashIntegrity(hashFromSender, decryptedData);
if (hashValidation.hasError()) {
return ValidationResult.problem(new DataHashMismatch());
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#syntax-validierung-1
if (data.getSubmissionSchema().getMimeType().equals(MimeType.APPLICATION_JSON)) {
final ValidationResult jsonValidation = validateJsonFormat(new String(decryptedData, StandardCharsets.UTF_8));
if (jsonValidation.hasError()) {
return ValidationResult.problem(new DataJsonSyntaxViolation());
}
}
if (data.getSubmissionSchema().getMimeType().equals(MimeType.APPLICATION_XML)) {
final ValidationResult xmlValidation = validateXmlFormat(new String(decryptedData, StandardCharsets.UTF_8));
if (xmlValidation.hasError()) {
return ValidationResult.problem(new DataXmlSyntaxViolation());
}
}
return ValidationResult.ok();
}
@Override
public ValidationResult validateAttachments(final List<AttachmentForValidation> attachmentsForValidation, final AuthenticationTags authenticationTags) {
final Map<UUID, String> eventAuthTags = authenticationTags.getAttachments();
final List<Problem> validationProblems = new ArrayList<>();
for (final AttachmentForValidation attachment : attachmentsForValidation) {
final UUID attachmentId = attachment.getAttachmentId();
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen-2
final String authTagFromEvent = eventAuthTags.get(attachmentId);
final String authTagFromAttachment = getAuthenticationTagFromEncryptedData(attachment.getEncryptedData());
if (!authTagFromEvent.equals(authTagFromAttachment)) {
validationProblems.add(new IncorrectAttachmentAuthenticationTag(attachmentId));
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#attachment-hash
final ValidationResult validationResult = validateHashIntegrity(attachment.getMetadataHash(), attachment.getDecryptedData());
if (validationResult.hasError()) {
validationProblems.add(new AttachmentHashMismatch(attachmentId));
}
}
return validationProblems.isEmpty() ? ValidationResult.ok() : ValidationResult.problems(validationProblems);
}
@Override
public ValidationResult validateMetadata(final Metadata metadata, final Submission submission, final Destination destination, final AuthenticationTags eventAuthenticationTags) {
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen
final var submissionMetadataAuthTag = getAuthenticationTagFromEncryptedData(submission.getEncryptedMetadata());
if (!eventAuthenticationTags.getMetadata().equals(submissionMetadataAuthTag)) {
return ValidationResult.problem(new IncorrectMetadataAuthenticationTag());
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#syntax-validierung
if (invalidJsonSyntax(metadata)) {
return ValidationResult.problem(new MetadataJsonSyntaxViolation());
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#fachdatensatz
if (metadata.getContentStructure().getData() == null) {
return ValidationResult.problem(new MissingData());
}
final ValidationResult validationResult = validateMetadataSchema(metadata);
if (validationResult.hasProblems()) {
return validationResult;
}
if (metadata.getPublicServiceType() != null) {
final String serviceIdentifier = metadata.getPublicServiceType().getIdentifier();
if (!serviceIdentifier.equals(submission.getServiceType().getIdentifier())) {
return ValidationResult.problem(new ServiceMismatch());
}
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#verwaltungsleistung-abgleichen-ii
final var destinationService = destination.getServices()
.stream()
.map(DestinationService::getIdentifier)
.filter(identifier -> identifier.equals(submission.getServiceType().getIdentifier()))
.findFirst();
if (destinationService.isEmpty()) {
return ValidationResult.problem(new UnsupportedService());
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#fachdatenschema
if (destination.getServices().isEmpty()) {
return ValidationResult.problem(new UnsupportedDataSchema());
}
final URI submissionDataSchemaUri = metadata.getContentStructure().getData().getSubmissionSchema().getSchemaUri();
final boolean matchingSchemas = matchingDestinationAndSubmissionSchema(destination, submissionDataSchemaUri);
if (!matchingSchemas) {
return ValidationResult.problem(new UnsupportedDataSchema());
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#liste-der-anlagen-abgleichen
final ValidationResult attachmentValidation = checkAttachmentCount(submission, metadata, eventAuthenticationTags);
if (attachmentValidation.hasProblems()) {
return attachmentValidation;
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#r%C3%BCckkanal
final ReplyChannel submissionReplyChannel = metadata.getReplyChannel();
final ReplyChannel destinationReplyChannel = destination.getReplyChannels();
if (submissionReplyChannel != null) {
final List<Class<?>> submissionReplyChannelClass = getNonNullReplyChannelTypes(submissionReplyChannel);
final List<Class<?>> destinationReplyChannelClass = getNonNullReplyChannelTypes(destinationReplyChannel);
if (!new HashSet<>(destinationReplyChannelClass).containsAll(submissionReplyChannelClass)) {
return ValidationResult.problem(new UnsupportedReplyChannel());
}
}
return ValidationResult.ok();
}
private static boolean matchingDestinationAndSubmissionSchema(final Destination destination, final URI submissionDataSchemaUri) {
return destination.getServices().stream()
.flatMap(service -> service.getSubmissionSchemas().stream())
.map(SubmissionSchema::getSchemaUri)
.anyMatch(submissionDataSchemaUri::equals);
}
private ValidationResult validate2020JsonSchema(final String schema, final JsonNode inputNode) {
return returnValidationResult(SCHEMA_FACTORY_DRAFT_2020.getSchema(schema).validate(inputNode));
}
......@@ -206,7 +374,7 @@ public class DefaultValidationService implements ValidationService {
if (errors.isEmpty()) {
return ValidationResult.ok();
}
return ValidationResult.error(new ValidationException(errorsToSingleString(errors)));
return ValidationResult.withErrorAndProblem(new ValidationException(errorsToSingleString(errors)), new MetadataSchemaViolation());
}
private void validateWithX509AndProxy(final List<String> pemCerts, final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException {
......@@ -242,12 +410,12 @@ public class DefaultValidationService implements ValidationService {
private void validateCertChain(final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException, CertificateEncodingException {
final List<X509Certificate> x509CertChain = publicKey.getParsedX509CertChain();
if(x509CertChain == null){
throw new IllegalStateException("public key with id '"+publicKey.getKeyID()+"' does not contain a certificate chain");
if (x509CertChain == null) {
throw new IllegalStateException("public key with id '" + publicKey.getKeyID() + "' does not contain a certificate chain");
}
final List<String> pemCerts = getPemCerts(x509CertChain);
LOGGER.info("Validation public key with XC5 certificate chain checks");
if (isProxySet()) {
if (config.isProxySet()) {
validateWithX509AndProxy(pemCerts, publicKey, purpose);
} else {
validateWithX509AndWithoutProxy(pemCerts, publicKey, purpose);
......@@ -266,10 +434,6 @@ public class DefaultValidationService implements ValidationService {
return Base64.encode(x509Certificate.getEncoded()).toString();
}
private boolean isProxySet() {
return config.getHttpProxyPort() != null && Strings.isNotNullOrEmpty(config.getHttpProxyHost());
}
private boolean validateSilent() {
return config.getCurrentEnvironment().isAllowInsecurePublicKey();
}
......@@ -286,4 +450,43 @@ public class DefaultValidationService implements ValidationService {
return saxParserFactory.newSAXParser().getXMLReader();
}
private static List<Class<?>> getNonNullReplyChannelTypes(final ReplyChannel destinationReplyChannel) {
return Stream.of(destinationReplyChannel.getEMail(),
destinationReplyChannel.getDeMail(),
destinationReplyChannel.getFink(),
destinationReplyChannel.getElster())
.filter(Objects::nonNull)
.map(Object::getClass)
.collect(Collectors.toList());
}
private boolean invalidJsonSyntax(final Metadata metadata) {
try {
return metadata == null || validateJsonFormat(MAPPER.writeValueAsString(metadata)).hasError();
} catch (final JsonProcessingException | NullPointerException e) {
return true;
}
}
private ValidationResult checkAttachmentCount(final Submission submission, final Metadata metadata, final AuthenticationTags authenticationTags) {
final List<UUID> submissionAttachmentIds = submission.getAttachments();
final List<ApiAttachment> metadataAttachments = metadata.getContentStructure().getAttachments();
final Map<UUID, String> attachmentAuthTags = authenticationTags.getAttachments();
if ((isNull(submissionAttachmentIds) || submissionAttachmentIds.isEmpty()) && (isNull(attachmentAuthTags) || attachmentAuthTags.isEmpty())) {
return ValidationResult.ok();
}
if (metadataAttachments.size() != submissionAttachmentIds.size()
|| attachmentAuthTags.size() != submissionAttachmentIds.size()
|| !attachmentAuthTags.keySet().containsAll(submissionAttachmentIds)
|| !submissionAttachmentIds.stream().allMatch(id -> metadataAttachments.stream().anyMatch(m -> m.getAttachmentId().equals(id)))
|| !submissionAttachmentIds.stream().allMatch(attachmentAuthTags::containsKey)) {
return ValidationResult.problem(new AttachmentsMismatch());
}
return ValidationResult.ok();
}
}
......@@ -83,25 +83,6 @@ public class SubmissionSenderTest {
);
}
@Test
void validatePublicKey() throws JOSEException {
//Given
final RSAKey publicKey = new RSAKeyGenerator(2048)
.keyUse(KeyUse.ENCRYPTION)
.keyID(UUID.randomUUID().toString())
.generate()
.toPublicJWK();
when(validationServiceMock.validateEncryptionPublicKey(any())).thenReturn(ValidationResult.ok());
// When
final ValidationResult validationResult = underTest.validatePublicKey(publicKey);
// Then
assertTrue(validationResult.isValid());
}
@Test
void encryptBytes() throws JOSEException {
......
......@@ -7,8 +7,14 @@ import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
import com.nimbusds.jwt.SignedJWT;
import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry;
import dev.fitko.fitconnect.api.domain.model.event.EventPayload;
import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataHashMismatch;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MetadataSchemaViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.submission.AttachmentsMismatch;
import dev.fitko.fitconnect.api.domain.model.metadata.Hash;
import dev.fitko.fitconnect.api.domain.model.metadata.Metadata;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentForValidation;
import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
import dev.fitko.fitconnect.api.domain.model.submission.SubmissionsForPickup;
......@@ -33,6 +39,7 @@ import java.util.UUID;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
......@@ -40,6 +47,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
......@@ -167,10 +175,10 @@ class SubmissionSubscriberTest {
void validateMetadata() {
// Given
when(validationServiceMock.validateMetadataSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateMetadata(any(), any(), any(), any())).thenReturn(ValidationResult.ok());
// When
final ValidationResult validationResult = underTest.validateMetadata(new Metadata());
final ValidationResult validationResult = underTest.validateMetadata(new Metadata(), new Submission(), new AuthenticationTags());
// Then
assertTrue(validationResult.isValid());
......@@ -180,14 +188,28 @@ class SubmissionSubscriberTest {
void invalidMetadata() {
// Given
when(validationServiceMock.validateMetadataSchema(any())).thenReturn(ValidationResult.error(new Exception("Failed parsing metadata")));
when(validationServiceMock.validateMetadata(any(), any(), any(), any())).thenReturn(ValidationResult.problem(new MetadataSchemaViolation()));
// When
final ValidationResult validationResult = underTest.validateMetadata(new Metadata());
final ValidationResult validationResult = underTest.validateMetadata(new Metadata(), new Submission(), new AuthenticationTags());
// Then
assertTrue(validationResult.hasError());
assertThat(validationResult.getError().getMessage(), containsString("Failed parsing metadata"));
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(MetadataSchemaViolation.class));
}
@Test
void invalidData() {
// Given
when(validationServiceMock.validateData(any(), any(), any(), any())).thenReturn(ValidationResult.problem(new DataHashMismatch()));
// When
final ValidationResult validationResult = underTest.validateData("foo".getBytes(), new Submission(), new Metadata(), new AuthenticationTags());
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(DataHashMismatch.class));
}
@Test
......@@ -300,6 +322,29 @@ class SubmissionSubscriberTest {
assertThrows(EventLogException.class, () -> underTest.getEventLog(destinationId, caseId));
}
@Test
void testAttachmentValidationFailed() {
// Given
final Hash hash = new Hash();
hash.setContent("hashValue");
final ApiAttachment attachmentMetadata = new ApiAttachment();
attachmentMetadata.setAttachmentId(UUID.randomUUID());
attachmentMetadata.setHash(hash);
final AttachmentForValidation attachmentForValidation = new AttachmentForValidation(attachmentMetadata, null, null);
when(validationServiceMock.validateAttachments(anyList(), any())).thenReturn(ValidationResult.problem(new AttachmentsMismatch()));
// When
final ValidationResult validationResult = underTest.validateAttachments(List.of(attachmentForValidation), new AuthenticationTags());
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(AttachmentsMismatch.class));
}
private String getResourceAsString(final String filename) throws IOException {
return new String(SubmissionSenderTest.class.getResourceAsStream(filename).readAllBytes());
......
......@@ -75,7 +75,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -113,7 +113,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId, authenticationTags);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -142,7 +142,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId, null);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -178,7 +178,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId, authTags);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -216,7 +216,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -254,7 +254,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.error(new SchemaNotFoundException("Schema not found")));
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -289,7 +289,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -330,7 +330,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -371,7 +371,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -410,7 +410,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -450,7 +450,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getSubmissionServicePublicKey(any())).thenReturn(rsaKey);
// When
......@@ -488,7 +488,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -526,7 +526,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -563,7 +563,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -602,7 +602,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(rsaKey);
// When
......@@ -639,7 +639,7 @@ class EventLogVerifierTest {
final ValidationContext ctx = new ValidationContext(destinationId, caseId);
when(validationServiceMock.validateSetEventSchema(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
when(keyServiceMock.getPublicSignatureKey(any(), any())).thenReturn(null);
// When
......
......@@ -23,7 +23,7 @@ class ValidationContextTest {
final ValidationContext underTest = new ValidationContext(UUID.randomUUID(), UUID.randomUUID());
// When
underTest.addResult(1+2 == 3, "1+2 does not equal 3");
underTest.addErrorIfTestFailed(1+2 == 3, "1+2 does not equal 3");
final List<ValidationResult> results = underTest.getValidationResults();
// Then
......@@ -37,7 +37,7 @@ class ValidationContextTest {
final ValidationContext underTest = new ValidationContext(UUID.randomUUID(), UUID.randomUUID());
// When
underTest.addResult(1+2 == 2, "1+2 does not equal 3");
underTest.addErrorIfTestFailed(1+2 == 2, "1+2 does not equal 3");
final List<ValidationResult> results = underTest.getValidationResults();
// Then
......@@ -71,7 +71,7 @@ class ValidationContextTest {
final List<ValidationResult> results = underTest.getValidationResults();
// Then
assertTrue(results.size() == 1);
assertEquals(1, results.size());
assertTrue(results.get(0).hasError());
assertThat(results.get(0).getError().getMessage(), is("Test failed"));
}
......@@ -87,7 +87,7 @@ class ValidationContextTest {
final List<ValidationResult> results = underTest.getValidationResults();
// Then
assertTrue(results.size() == 1);
assertEquals(1, results.size());
assertTrue(results.get(0).hasError());
assertThat(results.get(0).getError().getMessage(), is("Test failed"));
}
......
......@@ -91,7 +91,7 @@ class PublicKeyServiceTest extends RestEndpointBase {
when(authServiceMock.getCurrentToken()).thenReturn(authToken);
when(submissionServiceMock.getDestination(any())).thenReturn(destination);
when(validationServiceMock.validateEncryptionPublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
// When
final RSAKey rsaKey = underTest.getPublicEncryptionKey(destination.getDestinationId());
......@@ -134,7 +134,7 @@ class PublicKeyServiceTest extends RestEndpointBase {
when(authServiceMock.getCurrentToken()).thenReturn(authToken);
when(submissionServiceMock.getDestination(any())).thenReturn(destination);
when(validationServiceMock.validateEncryptionPublicKey(any())).thenReturn(ValidationResult.error(new Exception("Public key is insecure !")));
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.error(new Exception("Public key is insecure !")));
// When
final InvalidKeyException exception = assertThrows(InvalidKeyException.class, () -> keyService.getPublicEncryptionKey(destination.getDestinationId()));
......@@ -165,7 +165,7 @@ class PublicKeyServiceTest extends RestEndpointBase {
when(authServiceMock.getCurrentToken()).thenReturn(authToken);
when(submissionServiceMock.getDestination(any())).thenReturn(destination);
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
// When
final RSAKey retrievedSignatureKey = underTest.getPublicSignatureKey(destination.getDestinationId(), "123");
......@@ -195,7 +195,7 @@ class PublicKeyServiceTest extends RestEndpointBase {
.withStatus(200)));
when(authServiceMock.getCurrentToken()).thenReturn(authToken);
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
// When
final RSAKey retrievedSignatureKey = underTest.getSubmissionServicePublicKey("123");
......@@ -225,7 +225,7 @@ class PublicKeyServiceTest extends RestEndpointBase {
.withStatus(200)));
when(authServiceMock.getCurrentToken()).thenReturn(authToken);
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
// When
final RSAKey retrievedSignatureKey = underTest.getPortalPublicKey("123");
......@@ -255,7 +255,7 @@ class PublicKeyServiceTest extends RestEndpointBase {
.withStatus(200)));
when(authServiceMock.getCurrentToken()).thenReturn(authToken);
when(validationServiceMock.validateSignaturePublicKey(any())).thenReturn(ValidationResult.ok());
when(validationServiceMock.validatePublicKey(any(), any())).thenReturn(ValidationResult.ok());
// When
final RSAKey retrievedSignatureKey = underTest.getWellKnownKeysForSubmissionUrl("http://localhost:" + wireMockServer.port() + "/custom/path", "123");
......
......@@ -5,6 +5,7 @@ import dev.fitko.fitconnect.api.domain.model.event.Event;
import dev.fitko.fitconnect.api.domain.model.event.EventIssuer;
import dev.fitko.fitconnect.api.domain.model.event.EventLog;
import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataEncryptionIssue;
import dev.fitko.fitconnect.api.domain.model.event.problems.submission.InvalidEventLog;
import dev.fitko.fitconnect.api.exceptions.EventLogException;
import org.junit.jupiter.api.Test;
......
......@@ -12,16 +12,43 @@ import dev.fitko.fitconnect.api.config.ApplicationConfig;
import dev.fitko.fitconnect.api.config.Environment;
import dev.fitko.fitconnect.api.config.EnvironmentName;
import dev.fitko.fitconnect.api.config.SchemaConfig;
import dev.fitko.fitconnect.api.domain.model.destination.Destination;
import dev.fitko.fitconnect.api.domain.model.destination.DestinationService;
import dev.fitko.fitconnect.api.domain.model.event.authtags.AuthenticationTags;
import dev.fitko.fitconnect.api.domain.model.event.problems.Problem;
import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.AttachmentHashMismatch;
import dev.fitko.fitconnect.api.domain.model.event.problems.attachment.IncorrectAttachmentAuthenticationTag;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataHashMismatch;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataJsonSyntaxViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataXmlSyntaxViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.IncorrectDataAuthenticationTag;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.AttachmentsMismatch;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.IncorrectMetadataAuthenticationTag;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MetadataJsonSyntaxViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MetadataSchemaViolation;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.MissingData;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.ServiceMismatch;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.UnsupportedDataSchema;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.UnsupportedMetadataSchema;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.UnsupportedReplyChannel;
import dev.fitko.fitconnect.api.domain.model.event.problems.metadata.UnsupportedService;
import dev.fitko.fitconnect.api.domain.model.metadata.ContentStructure;
import dev.fitko.fitconnect.api.domain.model.metadata.Hash;
import dev.fitko.fitconnect.api.domain.model.metadata.Metadata;
import dev.fitko.fitconnect.api.domain.model.metadata.PublicServiceType;
import dev.fitko.fitconnect.api.domain.model.metadata.SignatureType;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.ApiAttachment;
import dev.fitko.fitconnect.api.domain.model.metadata.attachment.AttachmentForValidation;
import dev.fitko.fitconnect.api.domain.model.metadata.data.Data;
import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema;
import dev.fitko.fitconnect.api.domain.model.replychannel.Email;
import dev.fitko.fitconnect.api.domain.model.replychannel.Fink;
import dev.fitko.fitconnect.api.domain.model.replychannel.ReplyChannel;
import dev.fitko.fitconnect.api.domain.model.submission.ServiceType;
import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.domain.schema.SchemaResources;
import dev.fitko.fitconnect.api.domain.validation.ValidationResult;
import dev.fitko.fitconnect.api.exceptions.ValidationException;
import dev.fitko.fitconnect.api.services.crypto.MessageDigestService;
import dev.fitko.fitconnect.api.services.schema.SchemaProvider;
import dev.fitko.fitconnect.api.services.validation.ValidationService;
......@@ -32,7 +59,6 @@ import dev.fitko.fitconnect.jwkvalidator.exceptions.JWKValidationException;
import org.hamcrest.CoreMatchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import java.io.IOException;
import java.net.URI;
......@@ -43,11 +69,15 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
......@@ -66,11 +96,13 @@ class DefaultValidationServiceTest {
@BeforeEach
void setUp() {
final var config = getApplicationConfig(true);
hashService = new HashService();
final List<String> setSchemas = SchemaConfig.getSetSchemaFilePaths("/set-schema");
final List<String> metadataSchemas = SchemaConfig.getMetadataSchemaFileNames("/metadata-schema");
final List<String> destinationSchemas = SchemaConfig.getDestinationSchemaPaths("/destination-schema");
final SchemaResources schemaResources = new SchemaResources(setSchemas, metadataSchemas, destinationSchemas);
hashService = new HashService();
schemaProvider = new SchemaResourceProvider(schemaResources);
underTest = new DefaultValidationService(config, hashService, schemaProvider);
}
......@@ -86,7 +118,7 @@ class DefaultValidationServiceTest {
.generate();
// When
final ValidationResult validationResult = underTest.validateEncryptionPublicKey(rsaKey);
final ValidationResult validationResult = underTest.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY);
// Then
assertTrue(validationResult.isValid());
......@@ -103,7 +135,7 @@ class DefaultValidationServiceTest {
.generate();
// When
final ValidationResult validationResult = underTest.validateEncryptionPublicKey(rsaKey);
final ValidationResult validationResult = underTest.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY);
// Then
assertTrue(validationResult.hasError());
......@@ -130,7 +162,7 @@ class DefaultValidationServiceTest {
final RSAKey rsaKey = getRsaKeyWithCertChain();
// When
underTest.validateEncryptionPublicKey(rsaKey);
underTest.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY);
// Then
logs.assertContains("Using proxy HTTP @ https://localhost:8080 for key validation");
......@@ -150,7 +182,7 @@ class DefaultValidationServiceTest {
.generate();
// When
final ValidationResult validationResult = underTest.validateEncryptionPublicKey(rsaKey);
final ValidationResult validationResult = underTest.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY);
// Then
assertTrue(validationResult.isValid());
......@@ -170,7 +202,7 @@ class DefaultValidationServiceTest {
.generate();
// When
final ValidationResult validationResult = underTest.validateEncryptionPublicKey(rsaKey);
final ValidationResult validationResult = underTest.validatePublicKey(rsaKey, KeyOperation.WRAP_KEY);
// Then
assertTrue(validationResult.hasError());
......@@ -179,7 +211,7 @@ class DefaultValidationServiceTest {
@Test
void validateMetadata() {
void testValidMetadataSchema() {
// Given
final var submissionSchema = new SubmissionSchema();
......@@ -209,6 +241,474 @@ class DefaultValidationServiceTest {
assertTrue(validationResult.isValid());
}
@Test
void testValidMetadata() {
// Given
final var submissionSchema = new SubmissionSchema();
submissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json"));
submissionSchema.setMimeType(MimeType.APPLICATION_JSON);
final var hash = new Hash();
hash.setContent(hashService.toHexString(hashService.createHash("someBogusContent".getBytes())));
hash.setSignatureType(SignatureType.SHA_512);
final var data = new Data();
data.setSubmissionSchema(submissionSchema);
data.setHash(hash);
final var attachmentOne = new ApiAttachment();
attachmentOne.setAttachmentId(UUID.randomUUID());
final var attachmentTwo = new ApiAttachment();
attachmentTwo.setAttachmentId(UUID.randomUUID());
final var contentStructure = new ContentStructure();
contentStructure.setData(data);
contentStructure.setAttachments(List.of(attachmentOne, attachmentTwo));
final PublicServiceType publicServiceType = new PublicServiceType();
publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
final ReplyChannel replyChannel = new ReplyChannel();
replyChannel.setEMail(new Email("test@mail.net", false, null));
final var metadata = new Metadata();
metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString());
metadata.setContentStructure(contentStructure);
metadata.setPublicServiceType(publicServiceType);
metadata.setReplyChannel(replyChannel);
final ServiceType serviceType = new ServiceType();
serviceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
final var submission = new Submission();
submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag");
submission.setServiceType(serviceType);
submission.setAttachments(contentStructure.getAttachments().stream().map(ApiAttachment::getAttachmentId).collect(Collectors.toList()));
final DestinationService destinationService = new DestinationService();
destinationService.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
destinationService.setSubmissionSchemas(Set.of(submissionSchema));
final var destination = new Destination();
destination.setServices(Set.of(destinationService));
destination.setReplyChannels(replyChannel);
final var authenticationTags = new AuthenticationTags();
authenticationTags.setMetadata("metadataAuthTag");
authenticationTags.setAttachments(Map.of(attachmentOne.getAttachmentId(), "authTag", attachmentTwo.getAttachmentId(), "authTag"));
// When
final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags);
// Then
assertTrue(validationResult.isValid());
}
@Test
void testIncorrectMetadataAuthenticationTag() {
// Given
final var metadata = new Metadata();
final var destination = new Destination();
final var submission = new Submission();
submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag");
final var authenticationTags = new AuthenticationTags();
authenticationTags.setMetadata("wrongMetadataAuthTag");
// When
final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), is(new IncorrectMetadataAuthenticationTag()));
}
@Test
void testUnsupportedMetadataSchema() {
// Given
final String unsupportedSchema = "https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json/metadata_schema_3.2.1.json";
final Data data = new Data();
final ContentStructure contentStructure = new ContentStructure();
contentStructure.setData(data);
final var metadata = new Metadata();
metadata.setContentStructure(contentStructure);
metadata.setSchema(unsupportedSchema);
final var destination = new Destination();
final var submission = new Submission();
submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag");
final var authenticationTags = new AuthenticationTags();
authenticationTags.setMetadata("metadataAuthTag");
// When
final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), is(new UnsupportedMetadataSchema(unsupportedSchema)));
}
@Test
void testMetadataSyntaxViolation() {
// Given
final var destination = new Destination();
final var submission = new Submission();
submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag");
final var authenticationTags = new AuthenticationTags();
authenticationTags.setMetadata("metadataAuthTag");
// When
final ValidationResult validationResult = underTest.validateMetadata(null, submission, destination, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(MetadataJsonSyntaxViolation.class));
}
@Test
void testMissingData() {
// Given
final var destination = new Destination();
final var submission = new Submission();
submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag");
final var authenticationTags = new AuthenticationTags();
authenticationTags.setMetadata("metadataAuthTag");
final var metadata = new Metadata();
metadata.setContentStructure(new ContentStructure());
// When
final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(MissingData.class));
}
@Test
void testMetadataSchemaViolation() {
// Given
final ContentStructure contentStructure = new ContentStructure();
contentStructure.setData(new Data());
final var metadata = new Metadata();
metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString());
metadata.setContentStructure(contentStructure);
final var destination = new Destination();
final var submission = new Submission();
submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag");
final var authenticationTags = new AuthenticationTags();
authenticationTags.setMetadata("metadataAuthTag");
// When
final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), is(new MetadataSchemaViolation()));
}
@Test
void testServiceMismatch() {
// Given
final var hash = new Hash();
hash.setContent(hashService.toHexString(hashService.createHash("someBogusContent".getBytes())));
hash.setSignatureType(SignatureType.SHA_512);
final SubmissionSchema submissionSchema = new SubmissionSchema();
submissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json"));
submissionSchema.setMimeType(MimeType.APPLICATION_JSON);
final var data = new Data();
data.setSubmissionSchema(submissionSchema);
data.setHash(hash);
final ContentStructure contentStructure = new ContentStructure();
contentStructure.setData(data);
contentStructure.setAttachments(Collections.emptyList());
final PublicServiceType publicServiceType = new PublicServiceType();
publicServiceType.setIdentifier("urn:de:fim:leika:leistung:123456789");
final var metadata = new Metadata();
metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString());
metadata.setContentStructure(contentStructure);
metadata.setPublicServiceType(publicServiceType);
final var destination = new Destination();
final ServiceType submissionServiceType = new ServiceType();
submissionServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
final var submission = new Submission();
submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag");
submission.setServiceType(submissionServiceType);
final var authenticationTags = new AuthenticationTags();
authenticationTags.setMetadata("metadataAuthTag");
// When
final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(ServiceMismatch.class));
}
@Test
void testUnsupportedService() {
// Given
final SubmissionSchema submissionSchema = new SubmissionSchema();
submissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json"));
submissionSchema.setMimeType(MimeType.APPLICATION_JSON);
final Hash hash = new Hash();
hash.setContent(hashService.toHexString(hashService.createHash("someBogusContent".getBytes())));
hash.setSignatureType(SignatureType.SHA_512);
final Data data = new Data();
data.setSubmissionSchema(submissionSchema);
data.setHash(hash);
final ContentStructure contentStructure = new ContentStructure();
contentStructure.setAttachments(Collections.emptyList());
contentStructure.setData(data);
final PublicServiceType publicServiceType = new PublicServiceType();
publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
final var metadata = new Metadata();
metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString());
metadata.setContentStructure(contentStructure);
metadata.setPublicServiceType(publicServiceType);
final DestinationService destinationService = new DestinationService();
destinationService.setIdentifier("urn:de:fim:leika:leistung:9999999999999");
final var destination = new Destination();
destination.setServices(Set.of(destinationService));
final ServiceType submissionServiceType = new ServiceType();
submissionServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
final var submission = new Submission();
submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag");
submission.setServiceType(submissionServiceType);
final var authenticationTags = new AuthenticationTags();
authenticationTags.setMetadata("metadataAuthTag");
// When
final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(UnsupportedService.class));
}
@Test
void testUnsupportedDataSchema() {
// Given
final SubmissionSchema submissionSchema = new SubmissionSchema();
submissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json"));
submissionSchema.setMimeType(MimeType.APPLICATION_JSON);
final Hash hash = new Hash();
hash.setContent(hashService.toHexString(hashService.createHash("someBogusContent".getBytes())));
hash.setSignatureType(SignatureType.SHA_512);
final Data data = new Data();
data.setSubmissionSchema(submissionSchema);
data.setHash(hash);
final ContentStructure contentStructure = new ContentStructure();
contentStructure.setAttachments(Collections.emptyList());
contentStructure.setData(data);
final PublicServiceType publicServiceType = new PublicServiceType();
publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
final var metadata = new Metadata();
metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString());
metadata.setContentStructure(contentStructure);
metadata.setPublicServiceType(publicServiceType);
final SubmissionSchema destinationSubmissionSchema = new SubmissionSchema();
destinationSubmissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fit-connect/metadata/3.0.0/metadata.schema.json"));
destinationSubmissionSchema.setMimeType(MimeType.APPLICATION_JSON);
final DestinationService destinationService = new DestinationService();
destinationService.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
destinationService.setSubmissionSchemas(Set.of(destinationSubmissionSchema));
final var destination = new Destination();
destination.setServices(Set.of(destinationService));
final ServiceType submissionServiceType = new ServiceType();
submissionServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
final var submission = new Submission();
submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag");
submission.setServiceType(submissionServiceType);
final var authenticationTags = new AuthenticationTags();
authenticationTags.setMetadata("metadataAuthTag");
// When
final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(UnsupportedDataSchema.class));
}
@Test
void testAttachmentsMismatch() {
// Given
final SubmissionSchema schema = new SubmissionSchema();
schema.setSchemaUri(URI.create("https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json"));
schema.setMimeType(MimeType.APPLICATION_JSON);
final Hash hash = new Hash();
hash.setContent(hashService.toHexString(hashService.createHash("someBogusContent".getBytes())));
hash.setSignatureType(SignatureType.SHA_512);
final Data data = new Data();
data.setSubmissionSchema(schema);
data.setHash(hash);
final ContentStructure contentStructure = new ContentStructure();
contentStructure.setData(data);
final PublicServiceType publicServiceType = new PublicServiceType();
publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
final var metadata = new Metadata();
metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString());
metadata.setContentStructure(contentStructure);
metadata.setPublicServiceType(publicServiceType);
final DestinationService destinationService = new DestinationService();
destinationService.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
destinationService.setSubmissionSchemas(Set.of(schema));
final var destination = new Destination();
destination.setServices(Set.of(destinationService));
final ServiceType submissionServiceType = new ServiceType();
submissionServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
final var submission = new Submission();
submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag");
submission.setServiceType(submissionServiceType);
final var authenticationTags = new AuthenticationTags();
authenticationTags.setMetadata("metadataAuthTag");
// attachmentId that were announced
submission.setAttachments(List.of(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()));
// attachment in metadata that doesn't match
final ApiAttachment attachment = new ApiAttachment();
attachment.setAttachmentId(UUID.randomUUID());
contentStructure.setAttachments(List.of(attachment));
// authTag from submit event
authenticationTags.setAttachments(Map.of(attachment.getAttachmentId(), "attachmentAuthTag"));
// When
final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(AttachmentsMismatch.class));
}
@Test
void testUnsupportedReplyChannel() {
// Given
final SubmissionSchema schema = new SubmissionSchema();
schema.setSchemaUri(URI.create("https://schema.fitko.de/fit-connect/metadata/1.0.0/metadata.schema.json"));
schema.setMimeType(MimeType.APPLICATION_JSON);
final Hash hash = new Hash();
hash.setContent(hashService.toHexString(hashService.createHash("someBogusContent".getBytes())));
hash.setSignatureType(SignatureType.SHA_512);
final Data data = new Data();
data.setSubmissionSchema(schema);
data.setHash(hash);
final ContentStructure contentStructure = new ContentStructure();
contentStructure.setAttachments(Collections.emptyList());
contentStructure.setData(data);
final PublicServiceType publicServiceType = new PublicServiceType();
publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
final var metadata = new Metadata();
metadata.setSchema(SchemaConfig.METADATA_V_1_0_0.toString());
metadata.setContentStructure(contentStructure);
metadata.setPublicServiceType(publicServiceType);
final DestinationService destinationService = new DestinationService();
destinationService.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
destinationService.setSubmissionSchemas(Set.of(schema));
final var destination = new Destination();
destination.setServices(Set.of(destinationService));
final ServiceType submissionServiceType = new ServiceType();
submissionServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
final var submission = new Submission();
submission.setEncryptedMetadata("header.encryption_key.init_vector.ciphertext.metadataAuthTag");
submission.setServiceType(submissionServiceType);
final var authenticationTags = new AuthenticationTags();
authenticationTags.setMetadata("metadataAuthTag");
final ReplyChannel submissionReplyChannel = new ReplyChannel();
submissionReplyChannel.setEMail(new Email("test@mail.org", false, null));
metadata.setReplyChannel(submissionReplyChannel);
final ReplyChannel destinationReplyChannel = new ReplyChannel();
destinationReplyChannel.setFink(new Fink("finkPostBoxRef", "finkHost"));
destination.setReplyChannels(destinationReplyChannel);
// When
final ValidationResult validationResult = underTest.validateMetadata(metadata, submission, destination, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(UnsupportedReplyChannel.class));
}
@Test
void validateMetadataWithoutSchemaAttributeButValidSchema() {
......@@ -229,9 +729,8 @@ class DefaultValidationServiceTest {
final ValidationResult validationResult = underTest.validateMetadataSchema(metadata);
assertTrue(validationResult.hasError());
assertThat(validationResult.getError().getClass(), equalTo(ValidationException.class));
assertThat(validationResult.getError().getMessage(), containsString("The provided metadata schema is not supported."));
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems(), contains(new UnsupportedMetadataSchema(metadata.getSchema())));
}
@Test
......@@ -293,6 +792,276 @@ class DefaultValidationServiceTest {
assertThat(validationResult.getError().getMessage(), containsString("$.contentStructure.data.hash.content: does not match the regex pattern ^[a-f0-9]{128}$"));
}
@Test
void testIncorrectAttachmentAuthenticationTag() {
// Given
final var firstAttachmentId = UUID.randomUUID();
final var secondAttachmentId = UUID.randomUUID();
final var firstDecryptedData = "first test attachment".getBytes();
final var firstEncryptedData = "part1.part2.part3.part4.authTag1";
final var firstOriginalHash = hashService.toHexString(hashService.createHash(firstDecryptedData));
final byte[] secondDecryptedData = "second test attachment".getBytes();
final var secondEncryptedData = "part1.part2.part3.part4.authTag2";
final var secondOriginalHash = hashService.toHexString(hashService.createHash(secondDecryptedData));
final AuthenticationTags authenticationTags = new AuthenticationTags();
authenticationTags.setAttachments(Map.of(firstAttachmentId, "notMatchingAuthTag1", secondAttachmentId, "notMatchingAuthTag2"));
final Hash firstHash = new Hash();
firstHash.setContent(firstOriginalHash);
final ApiAttachment firstAttachmentMetadata = new ApiAttachment();
firstAttachmentMetadata.setAttachmentId(firstAttachmentId);
firstAttachmentMetadata.setHash(firstHash);
final Hash secondHash = new Hash();
secondHash.setContent(secondOriginalHash);
final ApiAttachment secondAttachmentMetadata = new ApiAttachment();
secondAttachmentMetadata.setAttachmentId(secondAttachmentId);
secondAttachmentMetadata.setHash(secondHash);
final AttachmentForValidation firstAttachment = new AttachmentForValidation(firstAttachmentMetadata, firstEncryptedData, firstDecryptedData);
final AttachmentForValidation secondAttachment = new AttachmentForValidation(secondAttachmentMetadata, secondEncryptedData, secondDecryptedData);
// When
final ValidationResult validationResult = underTest.validateAttachments(List.of(firstAttachment, secondAttachment), authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems(), hasSize(2));
assertThat(validationResult.getProblems().stream().map(Problem::getClass).collect(Collectors.toList()), hasItems(IncorrectAttachmentAuthenticationTag.class));
assertThat(validationResult.getProblems().get(0).getInstance(), containsString("attachment:" + firstAttachmentId));
assertThat(validationResult.getProblems().get(1).getInstance(), containsString("attachment:" + secondAttachmentId));
}
@Test
void testAttachmentHashMismatch() {
// Given
final var firstAttachmentId = UUID.randomUUID();
final var secondAttachmentId = UUID.randomUUID();
final var firstDecryptedData = "first test attachment".getBytes();
final var firstEncryptedData = "part1.part2.part3.part4.authTag1";
final var firstOriginalHash = hashService.toHexString(hashService.createHash(firstDecryptedData));
final byte[] secondDecryptedData = "second test attachment".getBytes();
final var secondEncryptedData = "part1.part2.part3.part4.authTag2";
final var secondOriginalHash = hashService.toHexString(hashService.createHash(secondDecryptedData));
final AuthenticationTags authenticationTags = new AuthenticationTags();
authenticationTags.setAttachments(Map.of(firstAttachmentId, "authTag1", secondAttachmentId, "authTag2"));
final Hash firstHash = new Hash();
firstHash.setContent(firstOriginalHash);
final ApiAttachment firstAttachmentMetadata = new ApiAttachment();
firstAttachmentMetadata.setAttachmentId(firstAttachmentId);
firstAttachmentMetadata.setHash(firstHash);
final Hash secondHash = new Hash();
secondHash.setContent(secondOriginalHash);
final ApiAttachment secondAttachmentMetadata = new ApiAttachment();
secondAttachmentMetadata.setAttachmentId(secondAttachmentId);
secondAttachmentMetadata.setHash(secondHash);
final AttachmentForValidation firstAttachment = new AttachmentForValidation(firstAttachmentMetadata, firstEncryptedData, "modified attachment 1 content".getBytes());
final AttachmentForValidation secondAttachment = new AttachmentForValidation(secondAttachmentMetadata, secondEncryptedData, "modified attachment 2 content".getBytes());
// When
final ValidationResult validationResult = underTest.validateAttachments(List.of(firstAttachment, secondAttachment), authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems(), hasSize(2));
assertThat(validationResult.getProblems().stream().map(Problem::getClass).collect(Collectors.toList()), hasItems(AttachmentHashMismatch.class));
assertThat(validationResult.getProblems().get(0).getInstance(), containsString("attachment:" + firstAttachmentId));
assertThat(validationResult.getProblems().get(1).getInstance(), containsString("attachment:" + secondAttachmentId));
}
@Test
void testValidData() {
// Given
final var decryptedData = "{ \"fachdaten\" : \"test\"}".getBytes();
final var submission = new Submission();
submission.setEncryptedData("part1.part2.part3.part4.dataAuthTag");
final var submissionSchema = new SubmissionSchema();
submissionSchema.setSchemaUri(URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json"));
submissionSchema.setMimeType(MimeType.APPLICATION_JSON);
final var hash = new Hash();
hash.setContent(hashService.toHexString(hashService.createHash(decryptedData)));
hash.setSignatureType(SignatureType.SHA_512);
final var data = new Data();
data.setHash(hash);
data.setSubmissionSchema(submissionSchema);
final var contentStructure = new ContentStructure();
contentStructure.setData(data);
contentStructure.setAttachments(Collections.emptyList());
final var metadata = new Metadata();
metadata.setContentStructure(contentStructure);
final var authenticationTags =new AuthenticationTags();
authenticationTags.setData("dataAuthTag");
// When
final ValidationResult validationResult = underTest.validateData(decryptedData, submission, metadata, authenticationTags);
// Then
assertTrue(validationResult.isValid());
}
@Test
void testIncorrectDataAuthenticationTag() {
// Given
final var decryptedData = "{ \"fachdaten\" : \"test\"}".getBytes();
final var submission = new Submission();
submission.setEncryptedData("part1.part2.part3.part4.wrongDataAuthTag");
final var hash = new Hash();
hash.setContent(hashService.toHexString(hashService.createHash(decryptedData)));
hash.setSignatureType(SignatureType.SHA_512);
final var data = new Data();
data.setHash(hash);
final var contentStructure = new ContentStructure();
contentStructure.setData(data);
final var metadata = new Metadata();
metadata.setContentStructure(contentStructure);
final var authenticationTags =new AuthenticationTags();
authenticationTags.setData("dataAuthTag");
// When
final ValidationResult validationResult = underTest.validateData(decryptedData, submission, metadata, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(IncorrectDataAuthenticationTag.class));
}
@Test
void testDataHashMismatch() {
// Given
final var decryptedData = "{ \"fachdaten\" : \"test\"}".getBytes();
final var submission = new Submission();
submission.setEncryptedData("part1.part2.part3.part4.dataAuthTag");
final var hash = new Hash();
hash.setContent(hashService.toHexString(hashService.createHash("manipulated data".getBytes())));
hash.setSignatureType(SignatureType.SHA_512);
final var data = new Data();
data.setHash(hash);
final var contentStructure = new ContentStructure();
contentStructure.setData(data);
final var metadata = new Metadata();
metadata.setContentStructure(contentStructure);
final var authenticationTags =new AuthenticationTags();
authenticationTags.setData("dataAuthTag");
// When
final ValidationResult validationResult = underTest.validateData(decryptedData, submission, metadata, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(DataHashMismatch.class));
}
@Test
void testDataJsonSyntaxViolation() {
// Given
final var decryptedData = "{ invalid_json }".getBytes();
final var submission = new Submission();
submission.setEncryptedData("part1.part2.part3.part4.dataAuthTag");
final var hash = new Hash();
hash.setContent(hashService.toHexString(hashService.createHash(decryptedData)));
hash.setSignatureType(SignatureType.SHA_512);
final var submissionSchema = new SubmissionSchema();
submissionSchema.setMimeType(MimeType.APPLICATION_JSON);
final var data = new Data();
data.setHash(hash);
data.setSubmissionSchema(submissionSchema);
final var contentStructure = new ContentStructure();
contentStructure.setData(data);
final var metadata = new Metadata();
metadata.setContentStructure(contentStructure);
final var authenticationTags =new AuthenticationTags();
authenticationTags.setData("dataAuthTag");
// When
final ValidationResult validationResult = underTest.validateData(decryptedData, submission, metadata, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(DataJsonSyntaxViolation.class));
}
@Test
void testDataXmlSyntaxViolation() {
// Given
final var decryptedData = "<xmehl>invalid xml</xml> }".getBytes();
final var submission = new Submission();
submission.setEncryptedData("part1.part2.part3.part4.dataAuthTag");
final var hash = new Hash();
hash.setContent(hashService.toHexString(hashService.createHash(decryptedData)));
hash.setSignatureType(SignatureType.SHA_512);
final var submissionSchema = new SubmissionSchema();
submissionSchema.setMimeType(MimeType.APPLICATION_XML);
final var data = new Data();
data.setHash(hash);
data.setSubmissionSchema(submissionSchema);
final var contentStructure = new ContentStructure();
contentStructure.setData(data);
final var metadata = new Metadata();
metadata.setContentStructure(contentStructure);
final var authenticationTags =new AuthenticationTags();
authenticationTags.setData("dataAuthTag");
// When
final ValidationResult validationResult = underTest.validateData(decryptedData, submission, metadata, authenticationTags);
// Then
assertTrue(validationResult.hasProblems());
assertThat(validationResult.getProblems().get(0), instanceOf(DataXmlSyntaxViolation.class));
}
@Test
void validateMatchingHash() {
......@@ -317,7 +1086,7 @@ class DefaultValidationServiceTest {
// Then
assertTrue(validationResult.hasError());
assertThat(validationResult.getError().getMessage(), containsString("transmitted data does not equal the hash of the sender"));
assertThat(validationResult.getError().getMessage(), containsString("Metadata contains invalid hash value"));
}
@Test
......
......@@ -5,15 +5,20 @@ import dev.fitko.fitconnect.api.domain.model.event.Event;
import dev.fitko.fitconnect.api.domain.model.event.EventLogEntry;
import dev.fitko.fitconnect.api.domain.model.event.SubmissionState;
import dev.fitko.fitconnect.api.domain.model.event.SubmissionStatus;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.DataEncryptionIssue;
import dev.fitko.fitconnect.api.domain.model.event.problems.data.IncorrectDataAuthenticationTag;
import dev.fitko.fitconnect.api.domain.model.event.problems.submission.InvalidEventLog;
import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup;
import dev.fitko.fitconnect.api.exceptions.SubmissionRequestException;
import dev.fitko.fitconnect.client.SenderClient;
import dev.fitko.fitconnect.client.SubscriberClient;
import dev.fitko.fitconnect.client.factory.ClientFactory;
import dev.fitko.fitconnect.client.sender.model.Attachment;
import dev.fitko.fitconnect.client.sender.model.SendableSubmission;
import dev.fitko.fitconnect.integrationtests.condition.EnableIfEnvironmentVariablesAreSet;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.nio.file.Path;
......@@ -25,20 +30,30 @@ import java.util.stream.Collectors;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@EnableIfEnvironmentVariablesAreSet
public class EventLogIT extends IntegrationTestBase {
@BeforeEach
public void cleanup(){
cleanupTestSubmissions();
}
@Test
void testRejectEvent() {
// Given
final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true);
final SenderClient senderClient = ClientFactory.getSenderClient(config);
final var submission = SendableSubmission.Builder()
.setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID")))
.setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service")
......@@ -46,9 +61,9 @@ public class EventLogIT extends IntegrationTestBase {
.addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text"))
.build();
final var sentSubmission = ClientFactory.getSenderClient(config).send(submission);
final var sentSubmission = senderClient.send(submission);
Assertions.assertNotNull(sentSubmission);
assertNotNull(sentSubmission);
// When
final var subscriberClient = ClientFactory.getSubscriberClient(config);
......@@ -56,11 +71,16 @@ public class EventLogIT extends IntegrationTestBase {
final var sentSubmissionId = sentSubmission.getSubmissionId();
// reject and remove
subscriberClient.requestSubmission(sentSubmissionId).rejectSubmission(List.of(new InvalidEventLog()));
subscriberClient.requestSubmission(sentSubmissionId).rejectSubmission(List.of(new DataEncryptionIssue()));
// check event log if reject event was sent
final SubmissionStatus status = senderClient.getStatusForSubmission(sentSubmission);
assertThat(status.getStatus(), is(SubmissionState.REJECTED));
// second attempt to receive and reject the submission should return an empty result
// since the submission is gone after being rejected
Assertions.assertNull(subscriberClient.requestSubmission(sentSubmissionId));
final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> subscriberClient.requestSubmission(sentSubmissionId));
assertThat(exception.getMessage(), containsString("Submission not found"));
}
@Test
......@@ -69,6 +89,9 @@ public class EventLogIT extends IntegrationTestBase {
// Given
final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true);
final SenderClient senderClient = ClientFactory.getSenderClient(config);
final var subscriberClient = ClientFactory.getSubscriberClient(config);
final var submission = SendableSubmission.Builder()
.setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID")))
.setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service")
......@@ -76,21 +99,65 @@ public class EventLogIT extends IntegrationTestBase {
.addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text"))
.build();
final var sentSubmission = ClientFactory.getSenderClient(config).send(submission);
final var sentSubmission = senderClient.send(submission);
Assertions.assertNotNull(sentSubmission);
// When
final var subscriberClient = ClientFactory.getSubscriberClient(config);
final var sentSubmissionId = sentSubmission.getSubmissionId();
// accept and remove
subscriberClient.requestSubmission(sentSubmissionId).acceptSubmission();
// check event log if accept event was sent
final SubmissionStatus status = senderClient.getStatusForSubmission(sentSubmission);
assertThat(status.getStatus(), is(SubmissionState.ACCEPTED));
// second attempt to receive the submission should return an empty result
// since the submission is gone after being accepted
Assertions.assertNull(subscriberClient.requestSubmission(sentSubmissionId));
final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> subscriberClient.requestSubmission(sentSubmissionId));
assertThat(exception.getMessage(), containsString("Submission not found"));
}
@Test
void testAcceptEventWithProblem() {
// Given
final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true);
final var submission = SendableSubmission.Builder()
.setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID")))
.setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service")
.setJsonData("{ \"data\": \"Beispiel Fachdaten\" }")
.addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text"))
.build();
final var sentSubmission = ClientFactory.getSenderClient(config).send(submission);
assertNotNull(sentSubmission);
// When
final var subscriberClient = ClientFactory.getSubscriberClient(config);
final var sentSubmissionId = sentSubmission.getSubmissionId();
// accept and remove
subscriberClient.requestSubmission(sentSubmissionId).acceptSubmission(new IncorrectDataAuthenticationTag());
// check event log if accept event was sent and contains a problem
final Optional<EventLogEntry> acceptEvent = subscriberClient
.getEventLog(sentSubmission.getCaseId(), sentSubmission.getDestinationId())
.stream().filter(e -> e.getEvent().equals(Event.ACCEPT))
.findFirst();
assertTrue(acceptEvent.isPresent());
assertThat(acceptEvent.get().getProblems(), is(not(empty())));
// second attempt to receive the submission should return an empty result
// since the submission is gone after being accepted
final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> subscriberClient.requestSubmission(sentSubmissionId));
assertThat(exception.getMessage(), containsString("Submission not found"));
}
@Test
......@@ -127,8 +194,10 @@ public class EventLogIT extends IntegrationTestBase {
// Then
assertThat(senderClient.getStatusForSubmission(sentSubmission).getStatus(), is(SubmissionState.REJECTED));
// second attempt to receive and reject the submission should return an empty result since the submission is gone after being rejected
assertNull(subscriberClient.requestSubmission(sentSubmission.getSubmissionId()));
final SubmissionRequestException exception = assertThrows(SubmissionRequestException.class, () -> subscriberClient.requestSubmission(sentSubmission.getSubmissionId()));
assertThat(exception.getMessage(), containsString("Submission not found"));
}
@Test
......@@ -136,6 +205,7 @@ public class EventLogIT extends IntegrationTestBase {
// Given
final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true);
final SenderClient senderClient = ClientFactory.getSenderClient(config);
final var submission = SendableSubmission.Builder()
.setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID")))
......@@ -143,7 +213,7 @@ public class EventLogIT extends IntegrationTestBase {
.setJsonData("{ \"data\": \"Beispiel Fachdaten\" }")
.build();
final var sentSubmission = ClientFactory.getSenderClient(config).send(submission);
final var sentSubmission = senderClient.send(submission);
Assertions.assertNotNull(sentSubmission);
......@@ -153,7 +223,7 @@ public class EventLogIT extends IntegrationTestBase {
final UUID destinationId = sentSubmission.getDestinationId();
final UUID caseId = sentSubmission.getCaseId();
final List<EventLogEntry> senderEventLog = ClientFactory.getSenderClient(config).getEventLog(caseId, destinationId);
final List<EventLogEntry> senderEventLog = senderClient.getEventLog(caseId, destinationId);
final List<Event> senderEvents = senderEventLog.stream().map(EventLogEntry::getEvent).collect(Collectors.toList());
// Then
......@@ -167,6 +237,9 @@ public class EventLogIT extends IntegrationTestBase {
// Given
final ApplicationConfig config = getConfigWithCredentialsFromEnvironment("TESTING", true);
final SenderClient senderClient = ClientFactory.getSenderClient(config);
final SubscriberClient subscriberClient = ClientFactory.getSubscriberClient(config);
final var submission = SendableSubmission.Builder()
.setDestination(UUID.fromString(System.getenv("TEST_DESTINATION_ID")))
.setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service")
......@@ -174,22 +247,23 @@ public class EventLogIT extends IntegrationTestBase {
.addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "plain/text"))
.build();
final var sentSubmission = ClientFactory.getSenderClient(config).send(submission);
final var sentSubmission = senderClient.send(submission);
Assertions.assertNotNull(sentSubmission);
final var receivedSubmission = ClientFactory.getSubscriberClient(config)
final var receivedSubmission = subscriberClient
.requestSubmission(sentSubmission.getSubmissionId());
Assertions.assertNotNull(receivedSubmission);
// When
Awaitility.await().atMost(Duration.ofSeconds(30)).until(() ->
{
final UUID destinationId = receivedSubmission.getDestinationId();
final UUID caseId = receivedSubmission.getCaseId();
final List<EventLogEntry> subscriberEventLog = ClientFactory.getSubscriberClient(config).getEventLog(caseId, destinationId);
final List<EventLogEntry> subscriberEventLog = subscriberClient.getEventLog(caseId, destinationId);
final List<Event> subscriberEvents = subscriberEventLog.stream().map(EventLogEntry::getEvent).collect(Collectors.toList());
// Then
......
......@@ -8,7 +8,6 @@ import dev.fitko.fitconnect.api.config.SubscriberConfig;
import dev.fitko.fitconnect.api.domain.model.event.problems.Problem;
import dev.fitko.fitconnect.client.SubscriberClient;
import dev.fitko.fitconnect.client.factory.ClientFactory;
import org.junit.jupiter.api.BeforeAll;
import java.util.List;
import java.util.Map;
......@@ -23,8 +22,7 @@ public class IntegrationTestBase {
private static final String selfServicePortalUrl = "https://portal.auth-testing.fit-connect.fitko.dev";
private static final String submissionBaseUrl = "https://submission-api-testing.fit-connect.fitko.dev";
@BeforeAll
static void cleanupSubmissionByRejecting() {
public static void cleanupTestSubmissions() {
final UUID destinationId = UUID.fromString(System.getenv("TEST_DESTINATION_ID"));
final Problem problem = new Problem(SCHEMA_URL + "technical-error", "cleanup", "submission-cleanup", "other");
......@@ -32,7 +30,7 @@ public class IntegrationTestBase {
subscriberClient.getAvailableSubmissionsForDestination(destinationId).forEach((s -> {
try {
subscriberClient.requestSubmission(s.getSubmissionId()).rejectSubmission(List.of(problem));
subscriberClient.rejectSubmission(s, List.of(problem));
} catch (final Exception e) {
//continue
}
......
......@@ -19,7 +19,6 @@ import dev.fitko.fitconnect.api.services.crypto.CryptoService;
import dev.fitko.fitconnect.client.SenderClient;
import dev.fitko.fitconnect.client.factory.ClientFactory;
import dev.fitko.fitconnect.client.sender.model.Attachment;
import dev.fitko.fitconnect.client.sender.model.AttachmentPayload;
import dev.fitko.fitconnect.client.sender.model.EncryptedAttachment;
import dev.fitko.fitconnect.client.sender.model.SendableEncryptedSubmission;
import dev.fitko.fitconnect.client.sender.model.SendableSubmission;
......@@ -31,6 +30,7 @@ import org.apache.tika.mime.MimeTypes;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.File;
......@@ -51,6 +51,11 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
@EnableIfEnvironmentVariablesAreSet
public class SenderClientIT extends IntegrationTestBase {
@BeforeEach
public void cleanup(){
cleanupTestSubmissions();
}
@Test
void testSendAndConfirmCycle() {
......@@ -64,7 +69,7 @@ public class SenderClientIT extends IntegrationTestBase {
.addAttachment(Attachment.fromPath(Path.of("src/test/resources/attachment.txt"), "text/plain"))
.addAttachment(Attachment.fromByteArray("attachment data".getBytes(), "text/plain"))
.addAttachment(Attachment.fromString("attachment data", "text/plain"))
.setReplyChannel(ReplyChannel.fromEmail("test@mail.org"))
.setReplyChannel(ReplyChannel.fromDeMail("test@mail.org"))
.build();
final var sentSubmission = ClientFactory.getSenderClient(config).send(submission);
......@@ -82,7 +87,7 @@ public class SenderClientIT extends IntegrationTestBase {
assertThat(receivedSubmission.getDataSchemaUri(), is(URI.create("https://schema.fitko.de/fim/s00000000009_1.0.0.schema.json")));
assertThat(receivedSubmission.getDataMimeType(), is("application/json"));
assertThat(receivedSubmission.getAttachments(), hasSize(3));
assertThat(receivedSubmission.getMetadata().getReplyChannel(), is(ReplyChannel.fromEmail("test@mail.org")));
assertThat(receivedSubmission.getMetadata().getReplyChannel(), is(ReplyChannel.fromDeMail("test@mail.org")));
assertThat(new String(receivedSubmission.getAttachments().get(0).getDataAsBytes()), is("Test attachment"));
}
......@@ -120,25 +125,18 @@ public class SenderClientIT extends IntegrationTestBase {
data.setHash(dataHash);
data.setSubmissionSchema(submissionSchema);
final var attachmentPayload = AttachmentPayload.builder()
.encryptedData(encryptedAttachment)
.mimeType(MimeTypes.PLAIN_TEXT)
.attachmentId(UUID.randomUUID())
.hashedData(cryptoService.hashBytes(attachmentData.getBytes(StandardCharsets.UTF_8)))
.build();
final var publicServiceType = new PublicServiceType();
publicServiceType.setName("Test Service");
publicServiceType.setIdentifier("urn:de:fim:leika:leistung:99400048079000");
final var attachment = new ApiAttachment();
attachment.setAttachmentId(attachmentPayload.getAttachmentId());
attachment.setAttachmentId(UUID.randomUUID());
attachment.setPurpose(Purpose.ATTACHMENT);
attachment.setFilename(attachmentPayload.getFileName());
attachment.setMimeType(attachmentPayload.getMimeType());
attachment.setFilename(attachmentFile.getName());
attachment.setMimeType(MimeTypes.PLAIN_TEXT);
final var attachmentHash = new Hash();
attachmentHash.setContent(attachmentPayload.getHashedData());
attachmentHash.setContent(cryptoService.hashBytes(attachmentData.getBytes(StandardCharsets.UTF_8)));
attachmentHash.setSignatureType(SignatureType.SHA_512);
attachment.setHash(attachmentHash);
......@@ -159,7 +157,7 @@ public class SenderClientIT extends IntegrationTestBase {
.setServiceType("urn:de:fim:leika:leistung:99400048079000", "Test Service")
.setEncryptedMetadata(encryptedMetadata)
.setEncryptedData(encryptedData)
.addEncryptedAttachment(new EncryptedAttachment(attachmentPayload.getAttachmentId(), attachmentPayload.getEncryptedData()))
.addEncryptedAttachment(new EncryptedAttachment(attachment.getAttachmentId(), encryptedAttachment))
.build();
final var sentSubmission = ClientFactory.getSenderClient(config).send(submission);
......