All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.metaeffekt.artifact.analysis.utils.JsonSchemaValidator Maven / Gradle / Ivy

There is a newer version: 0.134.0
Show newest version
/*
 * 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