com.metaeffekt.mirror.contents.base.MatchableDetailsAmbDataClass 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.mirror.contents.base;
import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.mirror.contents.advisory.AdvisoryEntry;
import com.metaeffekt.mirror.contents.store.*;
import com.metaeffekt.mirror.contents.vulnerability.Vulnerability;
import lombok.Getter;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.json.JSONArray;
import org.json.JSONObject;
import org.metaeffekt.core.inventory.processor.model.AbstractModelBase;
import org.metaeffekt.core.inventory.processor.model.AdvisoryMetaData;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.metaeffekt.core.inventory.processor.model.VulnerabilityMetaData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.stream.Collectors;
@Getter
public abstract class MatchableDetailsAmbDataClass> extends AmbDataClass {
private static final Logger LOG = LoggerFactory.getLogger(MatchableDetailsAmbDataClass.class);
protected final static Set CONVERSION_KEYS_AMB = new HashSet(AmbDataClass.CONVERSION_KEYS_AMB) {{
add(AdvisoryMetaData.Attribute.MATCHING_SOURCE.getKey());
add(AdvisoryMetaData.Attribute.DATA_SOURCE.getKey());
add(AdvisoryMetaData.Attribute.REFERENCED_IDS.getKey());
add(InventoryAttribute.VULNERABILITY_REFERENCED_CONTENT_IDS.getKey());
add(AdvisoryMetaData.Attribute.REFERENCED_SECURITY_ADVISORIES.getKey());
add(VulnerabilityMetaData.Attribute.REFERENCED_SECURITY_ADVISORIES.getKey());
add(AdvisoryMetaData.Attribute.REFERENCED_VULNERABILITIES.getKey());
add(VulnerabilityMetaData.Attribute.REFERENCED_VULNERABILITIES.getKey());
add(AdvisoryMetaData.Attribute.REFERENCED_OTHER.getKey());
add(VulnerabilityMetaData.Attribute.REFERENCED_OTHER.getKey());
}};
protected final static Set CONVERSION_KEYS_MAP = new HashSet(AmbDataClass.CONVERSION_KEYS_MAP) {{
add("source");
add("sourceImplementation");
add("dataFillingSources");
add("matchingSources");
add("referencedIds");
add("referencedSecurityAdvisories");
add("referencedVulnerabilities");
add("referencedOtherIds");
}};
protected final Map, Set> referencedVulnerabilities = new HashMap<>();
protected final Map, Set> referencedSecurityAdvisories = new HashMap<>();
protected final Map> referencedOtherIds = new HashMap<>();
protected final List matchingSources = new ArrayList<>();
protected final Set dataSources = new HashSet<>();
/**
* Artifacts that are affected by this entry.
* Will not be written back into the inventory as a field, but rather as references from the artifact to this entry.
*
* Key: Artifact field name
* Value: Set<Artifact> affected artifacts
*
*/
protected final Map> affectedArtifacts = new HashMap<>();
public abstract ContentIdentifierStore.ContentIdentifier getSourceIdentifier();
public DC addMatchingSource(DataSourceIndicator matchingSource) {
try {
if (matchingSource == null) {
LOG.warn("Attempted to add null matching source to {}", this.getClass().getSimpleName());
return (DC) this;
}
synchronized (this.matchingSources) {
this.matchingSources.add(matchingSource);
}
if (matchingSource.getMatchReason() instanceof DataSourceIndicator.ArtifactReason) {
final DataSourceIndicator.ArtifactReason artifactReason = (DataSourceIndicator.ArtifactReason) matchingSource.getMatchReason();
if (artifactReason.hasArtifact()) {
this.manuallyAffectsArtifact(Artifact.Attribute.VULNERABILITY.getKey(), artifactReason.getArtifact());
}
}
return (DC) this;
} catch (Exception e) {
LOG.error("Error while adding matching source [{}] to [{}]: {}", matchingSource, this.getClass().getSimpleName(), this.getId(), e);
return (DC) this;
}
}
public DC addDataSource(ContentIdentifierStore.ContentIdentifier dataSource) {
this.dataSources.add(dataSource);
return (DC) this;
}
public Set getAffectedArtifactsByKey(String key) {
synchronized (this.affectedArtifacts) {
return affectedArtifacts.getOrDefault(key, Collections.emptySet());
}
}
public Set getAffectedArtifactsByDefaultKey() {
return getAffectedArtifactsByKey(Artifact.Attribute.VULNERABILITY.getKey());
}
/**
* Manually append the id of this instance to the given artifact's attribute with the given key.
*
* If the goal is to append the id to the attribute {@link Artifact.Attribute#VULNERABILITY}, use the
* {@link MatchableDetailsAmbDataClass#matchingSources} instead, which will automatically append the id to the
* {@link Artifact.Attribute#VULNERABILITY} attribute if the indicator reason is an
* {@link DataSourceIndicator.ArtifactReason} instance.
*
* @param key The key of the attribute to append the id to.
* @param artifact The artifact to append the id to.
* @return This instance for chaining.
*/
public DC manuallyAffectsArtifact(String key, Artifact artifact) {
if (artifact != null) {
synchronized (this.affectedArtifacts) {
this.affectedArtifacts.computeIfAbsent(key, k -> new HashSet<>())
.add(artifact);
}
if (!Artifact.Attribute.VULNERABILITY.getKey().equals(key)) {
// some vulnerabilities are removed, such as the ones that are fixed by a KB (InventoryAttribute.VULNERABILITIES_FIXED_BY_KB),
// so we must add the affected vulnerability to the artifact here as well, not only in the VulnerabilityContextInventory.
final String presentVulnerabilities = artifact.get(key);
final Set vulnerabilities = StringUtils.hasText(presentVulnerabilities) ? Arrays.stream(presentVulnerabilities.split(", ")).collect(Collectors.toSet()) : new HashSet<>();
vulnerabilities.add(this.getId());
artifact.set(key, String.join(", ", vulnerabilities));
}
}
return (DC) this;
}
/**
* Manually append the id of this instance to the given artifact's attribute with the given key.
*
* If the goal is to append the id to the attribute {@link Artifact.Attribute#VULNERABILITY}, use the
* {@link MatchableDetailsAmbDataClass#matchingSources} instead, which will automatically append the id to the
* {@link Artifact.Attribute#VULNERABILITY} attribute if the indicator reason is an
* {@link DataSourceIndicator.ArtifactReason} instance.
*
* @param key The key of the attribute to append the id to.
* @param artifact The artifact to append the id to.
* @return This instance for chaining.
*/
public DC manuallyAffectsArtifact(AbstractModelBase.Attribute key, Artifact artifact) {
return manuallyAffectsArtifact(key.getKey(), artifact);
}
public DC removeAffectsArtifact(String key, Artifact artifact) {
if (artifact != null) {
synchronized (this.affectedArtifacts) {
this.affectedArtifacts.computeIfAbsent(key, k -> new HashSet<>())
.remove(artifact);
}
final String presentVulnerabilities = artifact.get(key);
final Set vulnerabilities = StringUtils.hasText(presentVulnerabilities) ? Arrays.stream(presentVulnerabilities.split(", ")).collect(Collectors.toSet()) : new HashSet<>();
vulnerabilities.remove(this.getId());
artifact.set(key, String.join(", ", vulnerabilities));
}
return (DC) this;
}
// START: MANAGE REFERENCED SECURITY ADVISORIES
public void addReferencedSecurityAdvisory(AdvisoryTypeIdentifier> source, String id) {
if (source == null || id == null) {
LOG.warn("Cannot add referenced security advisory with null source or id on advisory [{}]", this.id);
return;
}
synchronized (this.referencedSecurityAdvisories) {
this.referencedSecurityAdvisories.computeIfAbsent(source, k -> new LinkedHashSet<>()).add(id);
}
}
public void addReferencedSecurityAdvisories(Map, Set> referencedSecurityAdvisories) {
synchronized (this.referencedSecurityAdvisories) {
for (Map.Entry, Set> entry : referencedSecurityAdvisories.entrySet()) {
this.referencedSecurityAdvisories.computeIfAbsent(entry.getKey(), k -> new LinkedHashSet<>()).addAll(entry.getValue());
}
}
}
public void addReferencedSecurityAdvisories(AdvisoryTypeIdentifier> source, Set referencedSecurityAdvisories) {
synchronized (this.referencedSecurityAdvisories) {
this.referencedSecurityAdvisories.computeIfAbsent(source, k -> new LinkedHashSet<>()).addAll(referencedSecurityAdvisories);
}
}
public void addReferencedSecurityAdvisory(AdvisoryEntry advisory) {
if (advisory == null) {
LOG.warn("Cannot add referenced security advisory with null advisory on advisory [{}]", this.id);
return;
}
this.addReferencedSecurityAdvisory(advisory.getSourceIdentifier(), advisory.getId());
}
public void addReferencedSecurityAdvisories(Collection advisories) {
advisories.forEach(this::addReferencedSecurityAdvisory);
}
public void removeReferencedSecurityAdvisory(AdvisoryTypeIdentifier> source, String id) {
synchronized (this.referencedSecurityAdvisories) {
this.referencedSecurityAdvisories.computeIfPresent(source, (k, v) -> {
v.remove(id);
return v;
});
if (this.referencedSecurityAdvisories.get(source) == null || this.referencedSecurityAdvisories.get(source).isEmpty()) {
this.referencedSecurityAdvisories.remove(source);
}
}
}
public void removeReferencedSecurityAdvisory(AdvisoryEntry advisory) {
if (advisory == null) {
LOG.warn("Cannot remove referenced security advisory with null advisory on advisory [{}]", this.id);
return;
}
this.removeReferencedSecurityAdvisory(advisory.getSourceIdentifier(), advisory.getId());
}
public void removeReferencedSecurityAdvisories(Collection advisories) {
advisories.forEach(this::removeReferencedSecurityAdvisory);
}
// END: MANAGE REFERENCED SECURITY ADVISORIES
// START: MANAGE REFERENCED VULNERABILITIES
public void addReferencedVulnerability(VulnerabilityTypeIdentifier> source, String id) {
if (source == null || id == null) {
LOG.warn("Cannot add referenced vulnerability with null source or id on advisory [{}]", this.id);
return;
}
this.referencedVulnerabilities.computeIfAbsent(source, k -> new LinkedHashSet<>()).add(id);
}
public void addReferencedVulnerabilities(Map, Set> referencedVulnerabilities) {
for (Map.Entry, Set> entry : referencedVulnerabilities.entrySet()) {
this.referencedVulnerabilities.computeIfAbsent(entry.getKey(), k -> new LinkedHashSet<>()).addAll(entry.getValue());
}
}
public void addReferencedVulnerabilities(VulnerabilityTypeIdentifier> source, Collection referencedVulnerabilities) {
this.referencedVulnerabilities.computeIfAbsent(source, k -> new LinkedHashSet<>()).addAll(referencedVulnerabilities);
}
public void addReferencedVulnerability(Vulnerability vulnerability) {
if (vulnerability == null) {
LOG.warn("Cannot add referenced vulnerability with null vulnerability on advisory [{}]", this.id);
return;
}
this.addReferencedVulnerability(vulnerability.getSourceIdentifier(), vulnerability.getId());
}
public void addReferencedVulnerabilities(Collection vulnerabilities) {
vulnerabilities.forEach(this::addReferencedVulnerability);
}
public void removeReferencedVulnerability(VulnerabilityTypeIdentifier> source, String id) {
this.referencedVulnerabilities.computeIfPresent(source, (k, v) -> {
v.remove(id);
return v;
});
if (this.referencedVulnerabilities.get(source) == null || this.referencedVulnerabilities.get(source).isEmpty()) {
this.referencedVulnerabilities.remove(source);
}
}
public void removeReferencedVulnerability(Vulnerability vulnerability) {
if (vulnerability == null) {
LOG.warn("Cannot remove referenced vulnerability with null vulnerability on advisory [{}]", this.id);
return;
}
this.removeReferencedVulnerability(vulnerability.getSourceIdentifier(), vulnerability.getId());
}
public void removeReferencedVulnerabilities(Collection vulnerabilities) {
vulnerabilities.forEach(this::removeReferencedVulnerability);
}
// END: MANAGE REFERENCED VULNERABILITIES
// START: MANAGE OTHER REFERENCED IDS
public void addOtherReferencedId(OtherTypeIdentifier source, String id) {
if (source == null || id == null) {
LOG.warn("Cannot add other referenced id with null source or id on advisory [{}]", this.id);
return;
}
this.referencedOtherIds.computeIfAbsent(source, k -> new LinkedHashSet<>()).add(id);
}
public void addOtherReferencedIds(Map> referencedOtherIds) {
for (Map.Entry> entry : referencedOtherIds.entrySet()) {
this.referencedOtherIds.computeIfAbsent(entry.getKey(), k -> new LinkedHashSet<>()).addAll(entry.getValue());
}
}
public void addOtherReferencedIds(OtherTypeIdentifier source, Collection referencedOtherIds) {
this.referencedOtherIds.computeIfAbsent(source, k -> new LinkedHashSet<>()).addAll(referencedOtherIds);
}
public void removeOtherReferencedId(OtherTypeIdentifier source, String id) {
this.referencedOtherIds.computeIfPresent(source, (k, v) -> {
v.remove(id);
return v;
});
if (this.referencedOtherIds.get(source) == null || this.referencedOtherIds.get(source).isEmpty()) {
this.referencedOtherIds.remove(source);
}
}
// END: MANAGE OTHER REFERENCED
public Set getReferencedSecurityAdvisories(AdvisoryTypeIdentifier> source) {
synchronized (this.referencedSecurityAdvisories) {
return this.referencedSecurityAdvisories.getOrDefault(source, Collections.emptySet());
}
}
public Set getReferencedVulnerabilities(VulnerabilityTypeIdentifier> source) {
synchronized (this.referencedVulnerabilities) {
return this.referencedVulnerabilities.getOrDefault(source, Collections.emptySet());
}
}
public Set getReferencedOtherIds(OtherTypeIdentifier source) {
synchronized (this.referencedOtherIds) {
return this.referencedOtherIds.getOrDefault(source, Collections.emptySet());
}
}
/* DATA TYPE CONVERSION METHODS */
@Override
public void appendFromBaseModel(AMB amb) {
super.appendFromBaseModel(amb);
final String referencedVulnerabilities = amb.get(AdvisoryMetaData.Attribute.REFERENCED_VULNERABILITIES);
if (referencedVulnerabilities != null) {
this.addReferencedVulnerabilities(VulnerabilityTypeStore.get().fromJsonMultipleReferencedIds(new JSONArray(referencedVulnerabilities)));
}
final String referencedSecurityAdvisories = amb.get(AdvisoryMetaData.Attribute.REFERENCED_SECURITY_ADVISORIES);
if (referencedSecurityAdvisories != null) {
this.addReferencedSecurityAdvisories(AdvisoryTypeStore.get().fromJsonMultipleReferencedIds(new JSONArray(referencedSecurityAdvisories)));
}
{
final String legacyReferencedIds = amb.get(AdvisoryMetaData.Attribute.REFERENCED_IDS);
if (legacyReferencedIds != null) {
appendLegacyReferencedIds(ContentIdentifierStore.parseLegacyJsonReferencedIds(new JSONObject(legacyReferencedIds)));
}
}
{
final String legacyReferencedIds = amb.get(InventoryAttribute.VULNERABILITY_REFERENCED_CONTENT_IDS);
if (legacyReferencedIds != null) {
appendLegacyReferencedIds(ContentIdentifierStore.parseLegacyJsonReferencedIds(new JSONObject(legacyReferencedIds)));
}
}
final String matchingSource = amb.get(AdvisoryMetaData.Attribute.MATCHING_SOURCE);
if (matchingSource != null) {
DataSourceIndicator.fromJson(new JSONArray(matchingSource))
.forEach(this::addMatchingSource);
}
final String dataFillingSources = amb.get(AdvisoryMetaData.Attribute.DATA_SOURCE);
if (dataFillingSources != null) {
Arrays.stream(dataFillingSources.split(", ?"))
.filter(StringUtils::hasText)
.distinct()
.forEach(this::addDataSourceFromSourceString);
}
}
@Override
public void appendToBaseModel(AMB modelBase) {
super.appendToBaseModel(modelBase);
synchronized (this.referencedVulnerabilities) {
if (!this.referencedVulnerabilities.isEmpty()) {
modelBase.set(AdvisoryMetaData.Attribute.REFERENCED_VULNERABILITIES.getKey(), ContentIdentifierStore.toJson(this.referencedVulnerabilities).toString());
} else {
modelBase.set(AdvisoryMetaData.Attribute.REFERENCED_VULNERABILITIES.getKey(), null);
}
}
synchronized (this.referencedSecurityAdvisories) {
if (!this.referencedSecurityAdvisories.isEmpty()) {
modelBase.set(AdvisoryMetaData.Attribute.REFERENCED_SECURITY_ADVISORIES.getKey(), ContentIdentifierStore.toJson(this.referencedSecurityAdvisories).toString());
} else {
modelBase.set(AdvisoryMetaData.Attribute.REFERENCED_SECURITY_ADVISORIES.getKey(), null);
}
}
if (!this.referencedOtherIds.isEmpty()) {
modelBase.set(AdvisoryMetaData.Attribute.REFERENCED_OTHER.getKey(), ContentIdentifierStore.toJson(this.referencedOtherIds).toString());
} else {
modelBase.set(AdvisoryMetaData.Attribute.REFERENCED_OTHER.getKey(), null);
}
final ContentIdentifierStore.ContentIdentifier sourceIdentifier = this.getSourceIdentifier();
final String sourceKey = modelBase instanceof VulnerabilityMetaData ? VulnerabilityMetaData.Attribute.SOURCE.getKey() : AdvisoryMetaData.Attribute.SOURCE.getKey();
final String sourceImplementationKey = modelBase instanceof VulnerabilityMetaData ? VulnerabilityMetaData.Attribute.SOURCE_IMPLEMENTATION.getKey() : AdvisoryMetaData.Attribute.SOURCE_IMPLEMENTATION.getKey();
if (sourceIdentifier != null) {
modelBase.set(sourceKey, sourceIdentifier.getName());
modelBase.set(sourceImplementationKey, sourceIdentifier.getImplementation());
} else {
LOG.warn("[{}] does not have source to write into [{}] when converting to [{}]", this.getId(), sourceKey, modelBase.getClass().getSimpleName());
modelBase.set(sourceKey, null);
modelBase.set(sourceImplementationKey, null);
}
final JSONObject legacyReferencedIds = ContentIdentifierStore.mergeIntoLegacyJson(Arrays.asList(this.referencedVulnerabilities, this.referencedSecurityAdvisories, this.referencedOtherIds));
if (!legacyReferencedIds.isEmpty()) {
modelBase.set(InventoryAttribute.VULNERABILITY_REFERENCED_CONTENT_IDS.getKey(), legacyReferencedIds.toString());
modelBase.set(AdvisoryMetaData.Attribute.REFERENCED_IDS.getKey(), legacyReferencedIds.toString());
} else {
modelBase.set(InventoryAttribute.VULNERABILITY_REFERENCED_CONTENT_IDS.getKey(), null);
modelBase.set(AdvisoryMetaData.Attribute.REFERENCED_IDS.getKey(), null);
}
synchronized (this.matchingSources) {
if (!this.matchingSources.isEmpty()) {
modelBase.set(AdvisoryMetaData.Attribute.MATCHING_SOURCE.getKey(), DataSourceIndicator.toJson(this.matchingSources).toString());
// extract all CPE from the CPE sources into VulnerabilityMetaData.Attribute.PRODUCT_URIS
try {
final Set productUris = this.matchingSources.stream()
.filter(Objects::nonNull)
.map(DataSourceIndicator::getMatchReason)
.filter(s -> s instanceof DataSourceIndicator.ArtifactCpeReason)
.map(s -> ((DataSourceIndicator.ArtifactCpeReason) s).getCpe())
.collect(Collectors.toSet());
if (!productUris.isEmpty()) {
modelBase.set(VulnerabilityMetaData.Attribute.PRODUCT_URIS.getKey(), String.join(", ", productUris));
} else {
modelBase.set(VulnerabilityMetaData.Attribute.PRODUCT_URIS.getKey(), null);
}
} catch (Exception e) {
LOG.error("Error while converting to [{}] while extracting product URIs from CPE sources for [{}]: {}", modelBase.getClass().getSimpleName(), this.getId(), this.matchingSources, e);
}
} else {
modelBase.set(AdvisoryMetaData.Attribute.MATCHING_SOURCE.getKey(), null);
}
}
if (!this.dataSources.isEmpty()) {
modelBase.set(AdvisoryMetaData.Attribute.DATA_SOURCE.getKey(), this.dataSources.stream().map(ContentIdentifierStore.ContentIdentifier::getName).collect(Collectors.joining(", ")));
} else {
modelBase.set(AdvisoryMetaData.Attribute.DATA_SOURCE.getKey(), null);
}
}
@Override
public void appendFromDataClass(DC dataClass) {
super.appendFromDataClass(dataClass);
this.addReferencedVulnerabilities(dataClass.getReferencedVulnerabilities());
this.addReferencedSecurityAdvisories(dataClass.getReferencedSecurityAdvisories());
this.addOtherReferencedIds(dataClass.getReferencedOtherIds());
this.matchingSources.addAll(dataClass.getMatchingSources());
this.dataSources.addAll(dataClass.getDataSources());
}
@Override
public void appendFromMap(Map input) {
super.appendFromMap(input);
if (input.containsKey("referencedVulnerabilities") && input.get("referencedVulnerabilities") instanceof List) {
this.addReferencedVulnerabilities(VulnerabilityTypeStore.get().fromListMultipleReferencedIds((List