com.metaeffekt.artifact.analysis.utils.JsonSchemaValidator Maven / Gradle / Ivy
/*
* Copyright 2021-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.metaeffekt.artifact.analysis.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.vulnerabilitystatus.VulnerabilityStatus;
import com.networknt.schema.*;
import com.networknt.schema.resource.MapSchemaMapper;
import lombok.extern.slf4j.Slf4j;
import org.metaeffekt.core.inventory.processor.report.configuration.CentralSecurityPolicyConfiguration;
import java.io.*;
import java.nio.file.Files;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
public class JsonSchemaValidator {
private final static Map> REGISTERED_IGNORE_JSON_VALIDATION_ERRORS = new HashMap<>();
static {
REGISTERED_IGNORE_JSON_VALIDATION_ERRORS.put(CentralSecurityPolicyConfiguration.JsonSchemaValidationErrorsHandling.LENIENT, new HashSet<>(Collections.singletonList(
ValidatorTypeCode.ADDITIONAL_PROPERTIES
)));
REGISTERED_IGNORE_JSON_VALIDATION_ERRORS.put(CentralSecurityPolicyConfiguration.JsonSchemaValidationErrorsHandling.STRICT, new HashSet<>());
// check if there are any new CentralSecurityPolicyConfiguration.JsonSchemaValidationErrorsHandling values and log a warning
if (REGISTERED_IGNORE_JSON_VALIDATION_ERRORS.size() != CentralSecurityPolicyConfiguration.JsonSchemaValidationErrorsHandling.values().length) {
log.warn("There are new CentralSecurityPolicyConfiguration.JsonSchemaValidationErrorsHandling values that are not handled in the JsonSchemaValidator. " +
"Please update the REGISTERED_IGNORE_JSON_VALIDATION_ERRORS map accordingly.");
}
}
private final JsonSchema yamlValidationSchema;
private final JsonSchema jsonValidationSchema;
public JsonSchemaValidator(SpecVersion.VersionFlag schemaVersion, String schema) {
this.yamlValidationSchema = getJsonSchemaFactoryForMapper(new ObjectMapper(new YAMLFactory()), schemaVersion).getSchema(schema);
this.jsonValidationSchema = getJsonSchemaFactoryForMapper(new ObjectMapper(), schemaVersion).getSchema(schema);
}
public JsonSchemaValidator(SpecVersion.VersionFlag schemaVersion, InputStream schemaInputStream) throws IOException {
if (schemaInputStream == null) {
throw new IllegalArgumentException("Schema input stream must not be null.");
} else if (schemaVersion == null) {
throw new IllegalArgumentException("Schema version must not be null.");
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = schemaInputStream.read(buffer)) > -1) {
baos.write(buffer, 0, len);
}
baos.flush();
this.yamlValidationSchema = getJsonSchemaFactoryForMapper(new ObjectMapper(new YAMLFactory()), schemaVersion).getSchema(new ByteArrayInputStream(baos.toByteArray()));
this.jsonValidationSchema = getJsonSchemaFactoryForMapper(new ObjectMapper(), schemaVersion).getSchema(new ByteArrayInputStream(baos.toByteArray()));
}
public JsonSchemaValidator(SpecVersion.VersionFlag schemaVersion, File schemaFile) throws IOException {
this.yamlValidationSchema = getJsonSchemaFactoryForMapper(new ObjectMapper(new YAMLFactory()), schemaVersion).getSchema(Files.newInputStream(schemaFile.toPath()));
this.jsonValidationSchema = getJsonSchemaFactoryForMapper(new ObjectMapper(), schemaVersion).getSchema(Files.newInputStream(schemaFile.toPath()));
}
private JsonSchemaFactory getJsonSchemaFactoryForMapper(ObjectMapper jsonSchemaMapper, SpecVersion.VersionFlag schemaVersion) {
Map referenceMap = new HashMap<>();
referenceMap.put("https://www.metaeffekt.com/schema/artifact-analysis/latest/artifact-data.json", "resource:/specification/jsonschema/artifact-data.json");
referenceMap.put("https://www.metaeffekt.com/schema/artifact-analysis/latest/artifact-data-condition.json", "resource:/specification/jsonschema/artifact-data-condition.json");
referenceMap.put("https://www.metaeffekt.com/schema/artifact-analysis/latest/artifact-fields.json", "resource:/specification/jsonschema/artifact-fields.json");
referenceMap.put("https://www.metaeffekt.com/schema/artifact-analysis/latest/vulnerability-keywords.json", "resource:/specification/jsonschema/vulnerability-keywords.json");
referenceMap.put("https://www.metaeffekt.com/schema/artifact-analysis/latest/vulnerability-status-file.json", "resource:/specification/jsonschema/vulnerability-status-file.json");
referenceMap.put("https://www.metaeffekt.com/schema/artifact-analysis/latest/vulnerability-status.json", "resource:/specification/jsonschema/vulnerability-status.json");
referenceMap.put("https://www.metaeffekt.com/schema/artifact-analysis/latest/vulnerability-status-validation.json", "resource:/specification/jsonschema/vulnerability-status-validation.json");
referenceMap.put("https://www.metaeffekt.com/schema/artifact-analysis/latest/curated-versions-matching.json", "resource:/specification/jsonschema/curated-versions-matching.json");
// assert that all resources exist
referenceMap.forEach((key, value) -> {
if (jsonSchemaMapper.getClass().getClassLoader().getResource(value.replace("resource:/", "")) == null) {
throw new RuntimeException("Unable to find resource " + value + " for key " + key);
}
});
return JsonSchemaFactory.builder(JsonSchemaFactory.getInstance(schemaVersion))
.jsonMapper(jsonSchemaMapper)
.schemaMappers(s -> s.add(new MapSchemaMapper(referenceMap)))
.build();
}
public Set validateJson(String input) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(input);
return validateJsonNode(null, jsonNode);
}
public Set validateJson(File input) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(Files.newInputStream(input.toPath()));
return validateJsonNode(input, jsonNode);
}
public Set validateYaml(File input) throws IOException {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
JsonNode jsonNode = mapper.readTree(Files.newInputStream(input.toPath()));
return validateJsonNode(input, jsonNode);
}
private Set validateJsonNode(File input, JsonNode jsonNode) throws IOException {
try {
return new HashSet<>(yamlValidationSchema.validate(jsonNode));
} catch (JsonSchemaException e) {
if (input != null) {
throw new IOException("Unable to validate YAML file " + input.getAbsolutePath() + " due to SSL handshake error. " +
"This is most likely an issue with an unregistered objectMapper in the JsonSchemaValidator.", e);
} else {
throw new IOException("Unable to validate YAML due to SSL handshake error. " +
"This is most likely an issue with an unregistered objectMapper in the JsonSchemaValidator.", e);
}
}
}
public static void assertResourceSchemaAppliesToYamlFile(File file, String specificationResourcePath, SpecVersion.VersionFlag specificationVersion, String identifier) {
assertResourceSchemaAppliesToYamlFile(file, specificationResourcePath, specificationVersion, identifier, CentralSecurityPolicyConfiguration.JSON_SCHEMA_VALIDATION_ERRORS_DEFAULT);
}
public static void assertResourceSchemaAppliesToYamlFile(File file, String specificationResourcePath, SpecVersion.VersionFlag specificationVersion, String identifier, CentralSecurityPolicyConfiguration.JsonSchemaValidationErrorsHandling isStrictJsonSchemaValidation) {
assertResourceSchemaAppliesToYamlFile(file, specificationResourcePath, specificationVersion, identifier, REGISTERED_IGNORE_JSON_VALIDATION_ERRORS.getOrDefault(isStrictJsonSchemaValidation, Collections.emptySet()));
}
public static void assertResourceSchemaAppliesToYamlFile(File file, String specificationResourcePath, SpecVersion.VersionFlag specificationVersion, String identifier, Set allowErrorCodes) {
try {
Set validateMsg = new JsonSchemaValidator(
specificationVersion,
VulnerabilityStatus.class.getClassLoader().getResourceAsStream(specificationResourcePath)
).validateYaml(file);
final Set allowErrorCodesCodes = allowErrorCodes.stream().map(ValidatorTypeCode::getErrorCode).collect(Collectors.toSet());
validateMsg.removeIf(msg -> {
if (allowErrorCodesCodes.contains(msg.getCode())) {
log.warn("Ignoring schema validation: {} file does not match specification: {} - {}", identifier, file.getAbsolutePath(), msg);
return true;
}
return false;
});
if (!validateMsg.isEmpty()) {
throw new RuntimeException(identifier + " file does not match specification: " + file.getAbsolutePath() +
"\n " + validateMsg.stream().map(ValidationMessage::toString).collect(Collectors.joining("\n ")));
}
} catch (IOException e) {
throw new RuntimeException("Unable to parse Json Schema for " + identifier, e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy