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

com.metaeffekt.artifact.enrichment.vulnerability.VulnerabilityStatusPostProcessor Maven / Gradle / Ivy

There is a newer version: 0.132.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.enrichment.vulnerability;

import com.metaeffekt.artifact.analysis.utils.LazySupplier;
import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.filter.FunctionCallFilterAttribute;
import com.metaeffekt.artifact.enrichment.InventoryEnricher;
import com.metaeffekt.mirror.contents.advisory.MsrcAdvisorEntry;
import com.metaeffekt.mirror.contents.base.VulnerabilityContextInventory;
import com.metaeffekt.mirror.contents.msrcdata.MsrcProduct;
import com.metaeffekt.mirror.contents.vulnerability.Vulnerability;
import com.metaeffekt.mirror.query.GhsaAdvisorIndexQuery;
import com.metaeffekt.mirror.query.MsrcProductIndexQuery;
import j2html.tags.specialized.ATag;
import j2html.tags.specialized.SpanTag;
import j2html.tags.specialized.UlTag;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
import org.json.JSONObject;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.metaeffekt.core.inventory.processor.model.Inventory;
import org.metaeffekt.core.inventory.processor.model.VulnerabilityMetaData;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static j2html.TagCreator.*;

@Slf4j
@Setter
public class VulnerabilityStatusPostProcessor {

    private final static Pattern VARIABLE_PATTERN = Pattern.compile("\\$\\{[a-zA-Z0-9.]+}");

    private LazySupplier ghsaAdvisorIndexQuery;
    private LazySupplier msrcProductIndexQuery;

    public void resolveVariablesHandler(VulnerabilityContextInventory vInventory, Vulnerability vulnerability, String string, Consumer writeToConsumer) {
        resolveVariablesHandler(vInventory, vulnerability, string, writeToConsumer, null);
    }

    public void resolveVariablesHandler(VulnerabilityContextInventory vInventory, Vulnerability vulnerability, String string, Consumer writeToConsumer, Function variableContentEscapeFunction) {
        if (string != null) {
            final String resolvedString = resolveVariables(vInventory, vulnerability, string, variableContentEscapeFunction);
            if (resolvedString != null && !resolvedString.equals(string)) {
                writeToConsumer.accept(resolvedString);
            }
        }
    }

    private String resolveVariables(VulnerabilityContextInventory vInventory, Vulnerability vulnerability, String variableString, Function variableContentEscapeFunction) {
        final Set variables = extractVariables(variableString);
        final Map resolvedVariables = resolveVariableValues(vInventory, vulnerability, variables);
        return replaceVariables(variableString, resolvedVariables, variableContentEscapeFunction);
    }

    private Map resolveVariableValues(VulnerabilityContextInventory vInventory, Vulnerability vulnerability, Set variables) {
        final Map resolvedVariables = new HashMap<>();

        for (String variable : variables) {
            final String resolvedValue = resolveVariable(vInventory, vulnerability, variable);
            if (resolvedValue == null) {
                log.warn("Unable to resolve variable: {}", variable);
                resolvedVariables.put(variable, "");
            } else {
                resolvedVariables.put(variable, resolvedValue);
            }
        }

        return resolvedVariables;
    }

    private String resolveVariable(VulnerabilityContextInventory vInventory, Vulnerability vulnerability, String variable) {
        final String variableName = variable.substring(2, variable.length() - 1);
        final String[] variableAccessPath = variableName.split("\\.");

        final String segment = getVariableAccessPath(variableAccessPath, 0);

        switch (segment) {
            case "msrc":
                return resolveMsrcVariable(vInventory, vulnerability, variableAccessPath);
            case "vulnerability":
                return resolveVulnerabilityVariable(vInventory, vulnerability, variableAccessPath);
            case "artifact":
                return resolveArtifactVariable(vInventory, vulnerability, variableAccessPath);
            case "advisor":
                return resolveAdvisorVariable(vInventory, vulnerability, variableAccessPath);
            case "date":
                return null;
        }

        log.warn("Unable to resolve variable: {}", variable);
        return "";
    }

    private String resolveAdvisorVariable(VulnerabilityContextInventory vInventory, Vulnerability vulnerability, String[] variableAccessPath) {
        final String segment = getVariableAccessPath(variableAccessPath, 1);

        switch (segment) {
            case "ids":
                return String.valueOf(FunctionCallFilterAttribute.valueProviderVulnerability(vulnerability, "advisor-ids"));
            case "providers":
                return String.valueOf(FunctionCallFilterAttribute.valueProviderVulnerability(vulnerability, "advisor-providers"));
            case "types":
                return String.valueOf(FunctionCallFilterAttribute.valueProviderVulnerability(vulnerability, "advisor-types"));
        }

        return null;
    }

    private String resolveArtifactVariable(VulnerabilityContextInventory vInventory, Vulnerability vulnerability, String[] variableAccessPath) {
        final String segment = getVariableAccessPath(variableAccessPath, 1);
        final Set affectedArtifacts = vulnerability.getAffectedArtifactsByDefaultKey();

        if (segment.equals("id")) {
            return affectedArtifacts.stream()
                    .map(Artifact::getId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.joining(", "));
        }

        return null;
    }

    private String resolveVulnerabilityVariable(VulnerabilityContextInventory vInventory, Vulnerability vulnerability, String[] variableAccessPath) {
        final String segment = getVariableAccessPath(variableAccessPath, 1);

        if (segment.equals("name")) {
            return vulnerability.getId();
        }

        return null;
    }

    private String resolveMsrcVariable(VulnerabilityContextInventory vInventory, Vulnerability vulnerability, String[] variableAccessPath) {
        if (isVariableAccessPathPrefix(variableAccessPath, 1, "product")) {
            final Set artifactProductIds = MsrcAdvisorEntry.getAllMsrcProductIds(vulnerability.getAffectedArtifactsByDefaultKey());

            if (!artifactProductIds.isEmpty()) {
                if (isVariableAccessPathPrefix(variableAccessPath, 2, "name")) {
                    return artifactProductIds.stream()
                            .map(id -> this.msrcProductIndexQuery.get().findProductByIdOrName(id))
                            .map(MsrcProduct::getName)
                            .collect(Collectors.joining(", "));
                } else if (isVariableAccessPathPrefix(variableAccessPath, 2, "id")) {
                    return String.join(", ", artifactProductIds);
                }
            }

        } else if (isVariableAccessPathPrefix(variableAccessPath, 1, "patch")) {
            final String fixingKbIdentifiers = vulnerability.getAdditionalAttribute(InventoryAttribute.MS_FIXING_KB_IDENTIFIER);
            final JSONObject json;

            if (StringUtils.hasText(fixingKbIdentifiers)) {
                if (fixingKbIdentifiers.startsWith("{")) {
                    json = new JSONObject(fixingKbIdentifiers);
                } else {
                    json = new JSONObject();
                    json.put("", fixingKbIdentifiers);
                }
            } else {
                json = new JSONObject();
            }

            if (isVariableAccessPathPrefix(variableAccessPath, 2, "kblist")) {
                if (isVariableAccessPathPrefix(variableAccessPath, 3, "csv")) {
                    final StringBuilder fixingProducts = new StringBuilder();
                    for (String productId : json.keySet()) {
                        final JSONArray kbIds = json.getJSONArray(productId);

                        if (fixingProducts.length() > 0) {
                            fixingProducts.append("; ");
                        }

                        final StringJoiner stringKbIds = new StringJoiner(", ");
                        for (int i = 0; i < kbIds.length(); i++) {
                            stringKbIds.add(kbIds.getString(i));
                        }
                        if (StringUtils.hasText(productId)) {
                            fixingProducts.append(productId).append(": ");
                        }
                        fixingProducts.append(stringKbIds);
                    }

                    return fixingProducts.toString();

                } else if (isVariableAccessPathPrefix(variableAccessPath, 3, "md")) {
                    // generates markdown content like so: - 11569: 5025229 (MS Support, Update Catalog), 5027222 (MS Support, Update Catalog)
                    // where the MS Support and Update Catalog are links to: https://support.microsoft.com/en-us/help/5025229 and https://catalog.update.microsoft.com/Search.aspx?q=KB5025229

                    final StringJoiner fixingProducts = new StringJoiner("\n");
                    for (String productId : json.keySet()) {
                        final JSONArray kbIds = json.getJSONArray(productId);

                        final StringJoiner stringKbIds = new StringJoiner(", ");
                        for (int i = 0; i < kbIds.length(); i++) {
                            final String kbId = kbIds.getString(i);
                            final String kbIdLink = String.format("([MS Support](https://support.microsoft.com/en-us/help/%s), [Update Catalog](https://catalog.update.microsoft.com/Search.aspx?q=KB%s))", kbId, kbId);
                            stringKbIds.add(kbId + " " + kbIdLink);
                        }
                        fixingProducts.add("- **" + productId + "**: " + stringKbIds);
                    }

                    return fixingProducts.toString();

                } else if (isVariableAccessPathPrefix(variableAccessPath, 3, "html")) {
                    final UlTag list = ul();

                    for (String productId : json.keySet()) {
                        final JSONArray kbIds = json.getJSONArray(productId);
                        if (kbIds.isEmpty()) {
                            continue;
                        }

                        final SpanTag spanKbIds = span(b(productId), text(": "));
                        for (int i = 0; i < kbIds.length(); i++) {
                            final String kbId = kbIds.getString(i);
                            final ATag msSupportLink = a("MS Support").withHref("https://support.microsoft.com/en-us/help/" + kbId);
                            final ATag updateCatalogLink = a("Update Catalog").withHref("https://catalog.update.microsoft.com/Search.aspx?q=KB" + kbId);

                            spanKbIds.withText(kbId + " (").with(msSupportLink).withText(", ").with(updateCatalogLink).withText(")");
                            if (i < kbIds.length() - 1) {
                                spanKbIds.withText(", ");
                            }
                        }
                        list.with(li(spanKbIds));
                    }

                    return list.render();
                }
            }
        }

        return null;
    }

    private List findAffectedArtifacts(Inventory inventory, VulnerabilityMetaData vmd) {
        return inventory.getArtifacts().stream()
                .filter(artifact -> InventoryEnricher.splitVulnerabilitiesCsv(artifact.getVulnerability()).contains(vmd.get(VulnerabilityMetaData.Attribute.NAME)))
                .collect(Collectors.toList());
    }

    public static boolean isVariableAccessPathPrefix(String[] parts, int offset, String... check) {
        if (parts.length < offset + check.length) {
            return false;
        }

        for (int i = 0; i < check.length; i++) {
            if (!parts[offset + i].equals(check[i])) {
                return false;
            }
        }

        return true;
    }

    public static String getVariableAccessPath(String[] parts, int offset) {
        return parts.length <= offset ? "" : parts[offset];
    }

    private String replaceVariables(String string, Map resolvedVariables, Function variableContentEscapeFunction) {
        String resolvedString = string;

        for (Map.Entry resolvedVariable : resolvedVariables.entrySet()) {
            resolvedString = resolvedString.replace(resolvedVariable.getKey(), variableContentEscapeFunction != null ? variableContentEscapeFunction.apply(resolvedVariable.getValue()) : resolvedVariable.getValue());
        }

        return resolvedString;
    }

    public static String escapeJsonContent(String string) {
        return string.replace("\\", "\\\\")
                .replace("\"", "\\\"")
                .replace("\n", "\\n")
                .replace("\r", "\\r")
                .replace("\t", "\\t");
    }

    /**
     * Extracts all variables patterns from the string. A variable pattern has the following form:
     * ${variable.access.path}. The variable access path is a dot separated path to a variable.
* The variables may appear anywhere in the string, but may not be nested. * * @param string The string to extract the variables from. * * @return Set of variable patterns. */ public static Set extractVariables(String string) { final Matcher matcher = VARIABLE_PATTERN.matcher(string); final Set variables = new HashSet<>(); while (matcher.find()) { variables.add(matcher.group()); } return variables; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy