dev.fitko.fitconnect.core.validation.DefaultValidationService Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of client Show documentation
Show all versions of client Show documentation
Library that provides client access to the FIT-Connect api-endpoints for sending, subscribing and
routing
package dev.fitko.fitconnect.core.validation;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonFactoryBuilder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.StreamReadConstraints;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.networknt.schema.JsonSchemaFactory;
import com.networknt.schema.SchemaValidatorsConfig;
import com.networknt.schema.SpecVersion;
import com.networknt.schema.ValidationMessage;
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.attachment.Fragment;
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.DataSchemaViolation;
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.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.attachment.Purpose;
import dev.fitko.fitconnect.api.domain.model.metadata.data.Data;
import dev.fitko.fitconnect.api.domain.model.metadata.data.MimeType;
import dev.fitko.fitconnect.api.domain.model.metadata.data.SubmissionSchema;
import dev.fitko.fitconnect.api.domain.model.reply.Reply;
import dev.fitko.fitconnect.api.domain.model.reply.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.validation.ValidationResult;
import dev.fitko.fitconnect.api.exceptions.internal.DataIntegrityException;
import dev.fitko.fitconnect.api.exceptions.internal.SchemaNotFoundException;
import dev.fitko.fitconnect.api.exceptions.internal.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.jwkvalidator.JWKValidator;
import dev.fitko.fitconnect.jwkvalidator.exceptions.JWKValidationException;
import dev.fitko.fitconnect.jwkvalidator.exceptions.LogLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
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.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static dev.fitko.fitconnect.core.crypto.constants.CryptoConstants.HASH_OF_ZERO_BYTES;
import static dev.fitko.fitconnect.core.utils.EventLogUtil.getAuthenticationTagFromEncryptedData;
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 = createObjectMapper();
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);
private static final SchemaValidatorsConfig VALIDATORS_CONFIG = new SchemaValidatorsConfig();
public static final String VALID_SCHEMA_URL_EXPRESSION = "https://schema\\.fitko\\.de/fit-connect/metadata/1\\.\\d+\\.\\d+/metadata.schema.json";
private final MessageDigestService messageDigestService;
private final SchemaProvider schemaProvider;
private final ApplicationConfig config;
private final JWKValidator jwkValidator;
public DefaultValidationService(final ApplicationConfig config, final MessageDigestService messageDigestService,
final SchemaProvider schemaProvider, final List trustedRootCertificates) {
this.config = config;
this.messageDigestService = messageDigestService;
this.schemaProvider = schemaProvider;
jwkValidator = createValidator(config, trustedRootCertificates);
VALIDATORS_CONFIG.setFormatAssertionsEnabled(true);
VALIDATORS_CONFIG.setLocale(Locale.US);
}
@Override
public ValidationResult validatePublicKey(final RSAKey publicKey, final KeyOperation keyOperation) {
try {
return validateKey(publicKey, keyOperation);
} catch (final Exception e) {
return ValidationResult.error(e);
}
}
@Override
public ValidationResult validateSetEventSchema(final String setEventPayload) {
try {
final JsonNode inputNode = MAPPER.readTree(setEventPayload);
final URI schemaUri = URI.create(inputNode.get(EventClaimFields.CLAIM_SCHEMA).asText());
if (schemaProvider.isAllowedSetSchema(schemaUri)) {
return validate2020JsonSchema(schemaProvider.loadSetSchema(config.getSetSchemaWriteVersion()), inputNode);
} else {
return ValidationResult.error(new SchemaNotFoundException("SET payload schema not supported: " + schemaUri));
}
} catch (final JsonProcessingException | IllegalArgumentException e) {
return ValidationResult.error(e);
}
}
@Override
public ValidationResult validateMetadataSchema(final Metadata metadata) {
if (metadata.getSchema() != null && !metadata.getSchema().isEmpty() && !metadata.getSchema().matches(VALID_SCHEMA_URL_EXPRESSION)) {
// 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 {
final String metadataJson = MAPPER.writeValueAsString(metadata);
final JsonNode inputNode = MAPPER.readTree(metadataJson);
return validate2020JsonSchema(schemaProvider.loadMetadataSchema(config.getMetadataSchemaWriteVersion()), inputNode);
} catch (final JsonProcessingException | SchemaNotFoundException e) {
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#schema-pr%C3%BCfung
return ValidationResult.withErrorAndProblem(e, new MetadataSchemaViolation());
}
}
@Override
public ValidationResult validateDestinationSchema(final Map destinationPayload) {
try {
final String destinationPayloadJson = MAPPER.writeValueAsString(destinationPayload);
final JsonNode inputNode = MAPPER.readTree(destinationPayloadJson);
final String schema = schemaProvider.loadDestinationSchema(config.getDestinationSchemaUri());
return returnValidationResult(SCHEMA_FACTORY_DRAFT_2007.getSchema(schema, VALIDATORS_CONFIG).validate(inputNode));
} catch (final JsonProcessingException e) {
return ValidationResult.error(e);
}
}
@Override
public ValidationResult validateHashIntegrity(final String originalHexHash, final byte[] data) {
try {
final byte[] originalHash = messageDigestService.fromHexString(originalHexHash);
final boolean hashesAreNotEqual = !messageDigestService.verify(originalHash, data);
if (hashesAreNotEqual) {
return ValidationResult.error(new DataIntegrityException("Metadata contains invalid hash value"));
}
return ValidationResult.ok();
} catch (final IllegalArgumentException | NullPointerException e) {
return ValidationResult.error(e);
}
}
@Override
public ValidationResult validateHashIntegrity(final String originalHexHash, final InputStream inputStream) {
try {
final byte[] originalHash = messageDigestService.fromHexString(originalHexHash);
final boolean hashesAreNotEqual = !messageDigestService.verify(originalHash, inputStream);
if (hashesAreNotEqual) {
return ValidationResult.error(new DataIntegrityException("Metadata contains invalid hash value"));
}
return ValidationResult.ok();
} catch (final IllegalArgumentException | NullPointerException e) {
return ValidationResult.error(e);
}
}
@Override
public ValidationResult validateSubmissionDataSchema(final byte[] json, final URI schemaUri) {
if (config.isSkipSubmissionDataValidation()) {
LOGGER.warn("Submission data validation is deactivated. This should be done only on secure test environments.");
return ValidationResult.ok();
}
final String schema = schemaProvider.loadSubmissionDataSchema(schemaUri);
try {
return returnValidationResult(SCHEMA_FACTORY_DRAFT_2020.getSchema(schema, VALIDATORS_CONFIG).validate(MAPPER.readTree(json)));
} catch (final IOException e) {
return ValidationResult.error(e);
}
}
@Override
public ValidationResult validateJsonFormat(final byte[] json) {
try {
MAPPER.readTree(json);
} catch (final IOException e) {
return ValidationResult.error(e);
}
return ValidationResult.ok();
}
@Override
public ValidationResult validateXmlFormat(final byte[] xml) {
try {
final XMLReader xmlReader = getXmlReader();
xmlReader.parse(new InputSource(new ByteArrayInputStream(xml)));
} catch (final ParserConfigurationException | IOException | SAXException e) {
return ValidationResult.error(e);
}
return ValidationResult.ok();
}
@Override
public ValidationResult validateCallback(final String hmac, final Long timestampInSec, final String httpBody, final String callbackSecret) {
final ZonedDateTime providedTimeInSeconds = ZonedDateTime.ofInstant(Instant.ofEpochSecond(timestampInSec), ZoneId.systemDefault());
final ZonedDateTime currentTimeFiveMinutesAgo = ZonedDateTime.now().minusMinutes(5);
if (providedTimeInSeconds.isBefore(currentTimeFiveMinutesAgo)) {
return ValidationResult.error(new ValidationException("Timestamp provided by callback is expired."));
}
final String expectedHmac = messageDigestService.calculateHMAC(timestampInSec + "." + httpBody, callbackSecret);
if (!hmac.equals(expectedHmac)) {
return ValidationResult.error(new ValidationException("HMAC provided by callback does not match the expected result."));
}
return ValidationResult.ok();
}
@Override
public ValidationResult validateData(final byte[] decryptedData, final String encryptedData, final Metadata metadata, final String eventAuthenticationTags) {
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen-1
final var dataAuthTag = getAuthenticationTagFromEncryptedData(encryptedData);
if (!eventAuthenticationTags.equals(dataAuthTag)) {
return ValidationResult.problem(new IncorrectDataAuthenticationTag());
}
final Data data = metadata.getContentStructure().getData();
if (validateIfDataIsNotTransferredAsAttachment(metadata, data)) {
// 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(decryptedData);
if (jsonValidation.hasError()) {
return ValidationResult.problem(new DataJsonSyntaxViolation());
}
final ValidationResult dataSchemaValidation = validateSubmissionDataSchema(decryptedData, data.getSubmissionSchema().getSchemaUri());
if (dataSchemaValidation.hasError()) {
return ValidationResult.problem(new DataSchemaViolation());
}
}
if (data.getSubmissionSchema().getMimeType().equals(MimeType.APPLICATION_XML)) {
final ValidationResult xmlValidation = validateXmlFormat(decryptedData);
if (xmlValidation.hasError()) {
return ValidationResult.problem(new DataXmlSyntaxViolation());
}
}
return ValidationResult.ok();
}
@Override
public ValidationResult validateAttachments(final List attachmentsForValidation, final AuthenticationTags authenticationTags) {
final Map eventAuthTags = authenticationTags.getAttachments();
final List validationProblems = new ArrayList<>();
for (AttachmentForValidation attachment : attachmentsForValidation) {
if (attachment.hasFragmentedPayload()) {
final ValidationResult validationResult = validateFragmentedAttachment(attachment, eventAuthTags, validationProblems);
if (validationResult.hasError()) {
return validationResult;
}
} else {
validateAttachment(attachment, eventAuthTags, validationProblems);
}
}
return validationProblems.isEmpty() ? ValidationResult.ok() : ValidationResult.problems(validationProblems);
}
private void validateAttachment(AttachmentForValidation attachment, Map eventAuthTags, List validationProblems) {
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);
if (!authTagFromEvent.equals(attachment.getAuthTag())) {
validationProblems.add(new IncorrectAttachmentAuthenticationTag(attachmentId));
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#attachment-hash
final ValidationResult validationResult = validateHashIntegrity(attachment.getHash(), attachment.getDecryptedData());
if (validationResult.hasError()) {
validationProblems.add(new AttachmentHashMismatch(attachment.getAttachmentId()));
}
}
private ValidationResult validateFragmentedAttachment(AttachmentForValidation attachment, Map eventAuthTags, List validationProblems) {
for (final Fragment fragment : attachment.getFragments()) {
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen-2
final String authTagFromEvent = eventAuthTags.get(fragment.getFragmentId());
if (!authTagFromEvent.equals(fragment.getAuthTag())) {
validationProblems.add(new IncorrectAttachmentAuthenticationTag(fragment.getFragmentId()));
}
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#attachment-hash
try (InputStream is = Files.newInputStream(attachment.getDataFile().toPath())) {
final ValidationResult validationResult = validateHashIntegrity(attachment.getHash(), is);
if (validationResult.hasError()) {
validationProblems.add(new AttachmentHashMismatch(attachment.getAttachmentId()));
}
} catch (final IOException e) {
return ValidationResult.error(e);
}
return ValidationResult.ok();
}
@Override
public ValidationResult validateSubmissionMetadata(final Metadata metadata, final Submission submission, final Destination destination, final AuthenticationTags eventAuthenticationTags) {
return validateMetadata(metadata, submission.getEncryptedMetadata(), destination, submission.getServiceType(), submission.getAttachments(), eventAuthenticationTags);
}
@Override
public ValidationResult validateReplyMetadata(final Metadata metadata, final Reply reply, final Destination destination, final AuthenticationTags eventAuthenticationTags) {
return validateMetadata(metadata, reply.getEncryptedMetadata(), destination, null, reply.getAttachments(), eventAuthenticationTags);
}
private ValidationResult validateMetadata(final Metadata metadata, final String encryptedMetadata, final Destination destination, final ServiceType serviceType, final List attachments, final AuthenticationTags eventAuthenticationTags) {
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#authentication-tag-pr%C3%BCfen
final var metadataAuthTag = getAuthenticationTagFromEncryptedData(encryptedMetadata);
if (!eventAuthenticationTags.getMetadata().equals(metadataAuthTag)) {
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;
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#fachdatenschema
if (destination.getServices().isEmpty()) {
return ValidationResult.problem(new UnsupportedDataSchema());
}
final SubmissionSchema submissionSchema = metadata.getContentStructure().getData().getSubmissionSchema();
final boolean matchingSchemas = matchingDestinationAndSubmissionSchema(destination, submissionSchema.getSchemaUri());
if (!matchingSchemas) {
return ValidationResult.problem(new UnsupportedDataSchema());
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#verwaltungsleistung-abgleichen-ii
if (serviceType != null) {
final var destinationService = destination.getServices().stream()
.map(DestinationService::getIdentifier)
.filter(identifier -> identifier.equals(serviceType.getIdentifier()))
.findFirst();
if (destinationService.isEmpty()) {
return ValidationResult.problem(new UnsupportedService());
}
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#liste-der-anlagen-abgleichen
final ValidationResult attachmentValidation = checkAttachmentCount(attachments, metadata, eventAuthenticationTags);
if (attachmentValidation.hasProblems()) {
return attachmentValidation;
}
// https://docs.fitko.de/fit-connect/docs/receiving/verification/#r%C3%BCckkanal
if (metadata.getReplyChannel() != null && serviceType != null) {
final Optional matchingServiceReplyChannel = destination.getServices().stream()
.filter(service -> service.getIdentifier().equals(serviceType.getIdentifier()))
.filter(service -> service.getSubmissionSchemas().stream().anyMatch(schema -> schema.equals(submissionSchema)))
.map(DestinationService::getReplyChannels)
.filter(Objects::nonNull)
.filter(destinationAllowsSubmissionReplyChannel(metadata.getReplyChannel()))
.findFirst();
if (matchingServiceReplyChannel.isEmpty()) {
return ValidationResult.problem(new UnsupportedReplyChannel());
}
}
return ValidationResult.ok();
}
private boolean validateIfDataIsNotTransferredAsAttachment(Metadata metadata, Data data) {
final boolean noAttachmentWithDataPurpose = metadata.getContentStructure()
.getAttachments()
.stream()
.map(ApiAttachment::getPurpose)
.noneMatch(purpose -> purpose.equals(Purpose.DATA));
return noAttachmentWithDataPurpose && !data.getHash().getContent().equals(HASH_OF_ZERO_BYTES);
}
private Predicate destinationAllowsSubmissionReplyChannel(final ReplyChannel submissionReplyChannel) {
return replyChannel -> {
final var destinationReplyChannelTypes = getNonNullReplyChannels(replyChannel);
final var submissionReplyChannelTypes = getNonNullReplyChannels(submissionReplyChannel);
return new HashSet<>(destinationReplyChannelTypes).containsAll(submissionReplyChannelTypes);
};
}
private List> getNonNullReplyChannels(final ReplyChannel replyChannel) {
return Stream.of(replyChannel.getEMail(),
replyChannel.getDeMail(),
replyChannel.getFink(),
replyChannel.getElster(),
replyChannel.getFitConnect(),
replyChannel.getIdBundDeMailbox())
.filter(Objects::nonNull)
// Getting the type is sufficient. We do not want an actual content comparison of objects ,
// since the destinations reply channel content will be null even if they are set.
.map(Object::getClass).collect(Collectors.toList());
}
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, VALIDATORS_CONFIG).validate(inputNode));
}
private ValidationResult returnValidationResult(final Set errors) {
if (errors.isEmpty()) {
return ValidationResult.ok();
}
return ValidationResult.withErrorAndProblem(new ValidationException(errorsToSingleString(errors)), new MetadataSchemaViolation());
}
private ValidationResult validateKey(final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException {
if (config.isAllowInsecurePublicKey()) {
return validateWithoutCertChain(publicKey, purpose);
} else {
return validateWithCertChain(publicKey, purpose);
}
}
private ValidationResult validateWithoutCertChain(final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException {
LOGGER.debug("Validating public key {} without XC5 certificate chain", publicKey.getKeyID());
jwkValidator.validate(publicKey, purpose);
return ValidationResult.ok();
}
private ValidationResult validateWithCertChain(final RSAKey publicKey, final KeyOperation purpose) throws JWKValidationException {
LOGGER.info("Validating public key with x5c certificate chain checks");
if (publicKey.getParsedX509CertChain() == null) {
throw new IllegalStateException("Public key with id '" + publicKey.getKeyID() + "' does not contain an x5c certificate chain");
}
jwkValidator.validate(publicKey, purpose);
return ValidationResult.ok();
}
private String errorsToSingleString(final Set errors) {
return errors.stream()
.map(ValidationMessage::getMessage)
.collect(Collectors.joining("\n"));
}
private static XMLReader getXmlReader() throws ParserConfigurationException, SAXException {
final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
return saxParserFactory.newSAXParser().getXMLReader();
}
private boolean invalidJsonSyntax(final Metadata metadata) {
try {
return metadata == null || validateJsonFormat(MAPPER.writeValueAsBytes(metadata)).hasError();
} catch (final JsonProcessingException | NullPointerException e) {
return true;
}
}
private ValidationResult checkAttachmentCount(final List attachmentIds, final Metadata metadata, final AuthenticationTags authenticationTags) {
final List metadataAttachments = metadata.getContentStructure().getAttachments();
final Map attachmentAuthTags = authenticationTags.getAttachments();
if ((isNull(attachmentIds) || attachmentIds.isEmpty()) && (isNull(attachmentAuthTags) || attachmentAuthTags.isEmpty())) {
return ValidationResult.ok();
}
final List actualIdsIncludingFragments = new ArrayList<>();
for (final ApiAttachment metadataAttachment : metadataAttachments) {
final List fragments = metadataAttachment.getFragments();
if (metadataAttachment.hasFragments()) {
actualIdsIncludingFragments.addAll(fragments);
} else {
actualIdsIncludingFragments.add(metadataAttachment.getAttachmentId());
}
}
if (actualIdsIncludingFragments.size() != attachmentIds.size()
|| attachmentAuthTags.size() != attachmentIds.size()
|| !attachmentAuthTags.keySet().containsAll(attachmentIds)
|| !attachmentIds.stream().allMatch(id -> actualIdsIncludingFragments.stream().anyMatch(actualId -> actualId.equals(id)))
|| !attachmentIds.stream().allMatch(attachmentAuthTags::containsKey)) {
return ValidationResult.problem(new AttachmentsMismatch());
}
return ValidationResult.ok();
}
private JWKValidator createValidator(final ApplicationConfig config, final List trustedRootCertificates) {
if (config.isAllowInsecurePublicKey()) {
return JWKValidator.withoutX5CValidation()
.withErrorLogLevel(LogLevel.WARN)
.build();
}
return JWKValidator.withRecommendedDefaults()
.withProxy(config.getHttpConfig().getProxyConfig().getHttpProxy())
.withRootCertificatesAsPEM(trustedRootCertificates)
.build();
}
private static ObjectMapper createObjectMapper() {
final JsonFactory jsonFactory = new JsonFactoryBuilder().streamReadConstraints(
StreamReadConstraints.builder()
.maxStringLength(Integer.MAX_VALUE)
.build()).build();
return new ObjectMapper(jsonFactory)
.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"))
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
}