com.metaeffekt.artifact.enrichment.vulnerability.VulnerabilityStatusPostProcessor 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.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