com.metaeffekt.mirror.contents.base.DataSourceIndicator 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.vulnerability.CommonEnumerationUtil;
import com.metaeffekt.mirror.contents.msrcdata.MsrcProduct;
import com.metaeffekt.mirror.contents.store.*;
import com.metaeffekt.mirror.contents.vulnerability.Vulnerability;
import lombok.Getter;
import org.json.JSONArray;
import org.json.JSONObject;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import us.springett.parsers.cpe.Cpe;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
/**
* A data structure to represent the matching source of a vulnerability or advisory.
*/
@Getter
public class DataSourceIndicator {
private final static Logger LOG = LoggerFactory.getLogger(DataSourceIndicator.class);
private final ContentIdentifierStore.ContentIdentifier dataSource;
private final Reason matchReason;
public DataSourceIndicator(ContentIdentifierStore.ContentIdentifier dataSource, Reason matchReason) {
this.dataSource = dataSource;
this.matchReason = matchReason;
}
public JSONObject toJson() {
return new JSONObject()
.put("source", dataSource.name())
.put("implementation", dataSource.getImplementation())
.put("matches", matchReason.toJson());
}
public static DataSourceIndicator fromJson(JSONObject json) {
final String source = json.getString("source");
final String implementation = json.optString("implementation", null);
final ContentIdentifierStore.ContentIdentifier parsedSource = sourceFromSourceAndImplementation(source, implementation);
return new DataSourceIndicator(
parsedSource,
Reason.fromJson(json.getJSONObject("matches"))
);
}
public static List fromJson(JSONArray json) {
final List result = new ArrayList<>();
for (int i = 0; i < json.length(); i++) {
final Object o = json.get(i);
if (o instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) o;
DataSourceIndicator fromJson = fromJson(jsonObject);
result.add(fromJson);
} else {
LOG.warn("Unexpected JSON object in array on [{}#fromJson(JSONArray)]: {}", DataSourceIndicator.class, o);
}
}
return result;
}
public static JSONArray toJson(Collection indicators) {
if (indicators == null || indicators.isEmpty()) {
return new JSONArray();
}
try {
return new JSONArray(
indicators.stream()
.filter(Objects::nonNull)
.map(DataSourceIndicator::toJson)
.collect(Collectors.toList())
);
} catch (Exception e) {
LOG.error("Failed to convert indicators to JSON: {}", indicators, e);
return new JSONArray();
}
}
@Override
public String toString() {
return "DataSourceIndicator[" + dataSource + " --> " + (matchReason == null ? "unspecified" : matchReason.toJson()) + "]";
}
public static DataSourceIndicator msrcProduct(Artifact artifact, MsrcProduct msrcProduct, String... kbIds) {
return new DataSourceIndicator(AdvisoryTypeStore.MSRC, new MsrcProductReason(artifact, msrcProduct.getId(), kbIds));
}
public static DataSourceIndicator msrcProduct(Artifact artifact, MsrcProduct msrcProduct, Collection kbIds) {
return msrcProduct(artifact, msrcProduct, kbIds.toArray(new String[0]));
}
public static DataSourceIndicator cpe(Artifact artifact, ContentIdentifierStore.ContentIdentifier source, Cpe matchingCpe, String configuration) {
return new DataSourceIndicator(source, new ArtifactCpeReason(artifact, CommonEnumerationUtil.toCpe22UriOrFallbackToCpe23FS(matchingCpe), configuration));
}
public static DataSourceIndicator vulnerability(Vulnerability vulnerability) {
return new DataSourceIndicator(VulnerabilityTypeStore.CVE, new VulnerabilityReason(vulnerability));
}
public static DataSourceIndicator ghsa(Artifact artifact, String coordinates) {
return new DataSourceIndicator(AdvisoryTypeStore.GHSA, new ArtifactGhsaReason(artifact, coordinates));
}
public static DataSourceIndicator assessmentStatus(File originFile) {
return new DataSourceIndicator(OtherTypeStore.ASSESSMENT_STATUS, new AssessmentStatusReason(originFile == null ? "no-file" : originFile.getAbsolutePath()));
}
public static DataSourceIndicator sourcedArtifact(Artifact artifact, String reason) {
return new DataSourceIndicator(VulnerabilityTypeStore.CVE, new AnyArtifactOverwriteSourceReason(artifact, reason));
}
private static ContentIdentifierStore.ContentIdentifier sourceFromSourceAndImplementation(String source, String implementation) {
final AdvisoryTypeIdentifier> advisoryTypeIdentifier = AdvisoryTypeStore.get().fromNameAndImplementationWithoutCreation(source, implementation);
if (advisoryTypeIdentifier != null) {
return advisoryTypeIdentifier;
}
final VulnerabilityTypeIdentifier> vulnerabilityTypeIdentifier = VulnerabilityTypeStore.get().fromNameAndImplementationWithoutCreation(source, implementation);
if (vulnerabilityTypeIdentifier != null) {
return vulnerabilityTypeIdentifier;
}
final OtherTypeIdentifier otherTypeIdentifier = OtherTypeStore.get().fromNameAndImplementationWithoutCreation(source, implementation);
if (otherTypeIdentifier != null) {
return otherTypeIdentifier;
}
return AdvisoryTypeStore.get().fromNameAndImplementationWithoutCreation(source, implementation);
}
@Getter
public static class AssessmentStatusReason extends Reason {
public final static String TYPE = "assessment-status";
private final String originFile;
public AssessmentStatusReason(String originFile) {
super(TYPE);
this.originFile = originFile;
}
public String getOriginFileName() {
if (originFile == null || originFile.isEmpty() || originFile.equals("no-file")) {
return "no-file";
}
return new File(originFile).getName();
}
@Override
public JSONObject toJson() {
return super.toJson()
.put("originFile", originFile);
}
}
@Getter
public static class VulnerabilityReason extends Reason {
public final static String TYPE = "vulnerability";
private final String id;
public VulnerabilityReason(String id) {
super(TYPE);
this.id = id;
}
public VulnerabilityReason(Vulnerability vulnerability) {
super(TYPE);
this.id = vulnerability.getId();
}
@Override
public JSONObject toJson() {
return super.toJson()
.put("id", id);
}
}
@Getter
public static class ArtifactGhsaReason extends ArtifactReason {
public final static String TYPE = "artifact-ghsa";
private final String coordinates;
public ArtifactGhsaReason(Artifact artifact, String coordinates) {
super(TYPE, artifact);
this.coordinates = coordinates;
}
protected ArtifactGhsaReason(JSONObject artifactData, String coordinates) {
super(TYPE, artifactData);
this.coordinates = coordinates;
}
@Override
public JSONObject toJson() {
return super.toJson()
.put("coordinates", coordinates);
}
}
@Getter
public static class ArtifactCpeReason extends ArtifactReason {
public final static String TYPE = "artifact-cpe";
private final String cpe;
private final String configuration;
public ArtifactCpeReason(Artifact artifact, String cpe) {
super(TYPE, artifact);
this.cpe = cpe;
this.configuration = null;
}
protected ArtifactCpeReason(JSONObject artifactData, String cpe) {
super(TYPE, artifactData);
this.cpe = cpe;
this.configuration = null;
}
public ArtifactCpeReason(Artifact artifact, String cpe, String configuration) {
super(TYPE, artifact);
this.cpe = cpe;
this.configuration = configuration;
}
protected ArtifactCpeReason(JSONObject artifactData, String cpe, String configuration) {
super(TYPE, artifactData);
this.cpe = cpe;
this.configuration = configuration;
}
@Override
public JSONObject toJson() {
return super.toJson()
.put("cpe", cpe)
.put("configuration", configuration);
}
}
@Getter
public static class MsrcProductReason extends ArtifactReason {
public final static String TYPE = "msrc-product";
private final String msrcProductId;
private final String[] kbIds;
public MsrcProductReason(Artifact artifact, String msrcProductId, String[] kbIds) {
super(TYPE, artifact);
this.msrcProductId = msrcProductId;
this.kbIds = kbIds;
}
protected MsrcProductReason(JSONObject artifactData, String msrcProductId, String[] kbIds) {
super(TYPE, artifactData);
this.msrcProductId = msrcProductId;
this.kbIds = kbIds;
}
@Override
public JSONObject toJson() {
return super.toJson()
.put("msrcProductId", msrcProductId)
.put("kbIds", kbIds);
}
}
@Getter
public static class AnyReason extends Reason {
public final static String TYPE = "any";
private final String description;
public AnyReason(String description) {
super(TYPE);
this.description = description;
}
@Override
public JSONObject toJson() {
return super.toJson()
.put("description", description);
}
}
@Getter
public static class AnyArtifactOverwriteSourceReason extends ArtifactReason {
public final static String TYPE = "any-artifact-overwrite-source";
private final String source;
public AnyArtifactOverwriteSourceReason(Artifact artifact, String source) {
super(TYPE, artifact);
this.source = source;
}
protected AnyArtifactOverwriteSourceReason(JSONObject artifactData) {
super(TYPE, artifactData);
this.source = artifactData.optString("source", null);
}
@Override
public String overwriteSource() {
return source;
}
@Override
public JSONObject toJson() {
return super.toJson()
.put("source", source);
}
}
public static class AnyArtifactReason extends ArtifactReason {
public final static String TYPE = "any-artifact";
public AnyArtifactReason(Artifact artifact) {
super(TYPE, artifact);
}
protected AnyArtifactReason(JSONObject artifactData) {
super(TYPE, artifactData);
}
@Override
public JSONObject toJson() {
return super.toJson();
}
}
@Getter
public abstract static class ArtifactReason extends Reason {
protected final Artifact artifact;
protected final String artifactId;
protected final String artifactComponent;
protected final String artifactVersion;
protected ArtifactReason(String type, Artifact artifact) {
super(type);
if (artifact == null) {
LOG.warn("Artifact is null in [{}#ArtifactReason(String, Artifact)], using empty artifact", DataSourceIndicator.class);
artifact = new Artifact();
}
this.artifact = artifact;
this.artifactId = artifact.getId();
this.artifactComponent = artifact.getComponent();
this.artifactVersion = artifact.getVersion();
}
protected ArtifactReason(String type, JSONObject artifactData) {
super(type);
this.artifact = null;
this.artifactId = artifactData.optString("artifactId", null);
this.artifactComponent = artifactData.optString("artifactComponent", null);
this.artifactVersion = artifactData.optString("artifactVersion", null);
}
public boolean hasArtifact() {
return artifact != null;
}
@Override
public JSONObject toJson() {
return super.toJson()
.put("artifactId", artifactId)
.put("artifactComponent", artifactComponent)
.put("artifactVersion", artifactVersion);
}
public Artifact findArtifact(Set artifacts) {
if (artifact != null) {
return artifact;
}
return artifacts.stream()
.filter(this::isArtifact)
.findFirst()
.orElse(null);
}
public boolean isArtifact(Artifact artifact) {
if (artifact == null) {
return false;
}
if (this.artifact != null) {
return this.artifact.equals(artifact);
}
return Objects.equals(artifactId, artifact.getId()) &&
Objects.equals(artifactComponent, artifact.getComponent()) &&
Objects.equals(artifactVersion, artifact.getVersion());
}
}
@Getter
public abstract static class Reason {
protected final String type;
protected Reason(String type) {
this.type = type;
}
public JSONObject toJson() {
return new JSONObject().put("type", type);
}
public String overwriteSource() {
return null;
}
@Override
public String toString() {
return toJson().toString();
}
public static Reason fromJson(JSONObject json) {
if (!json.has("type")) throw new IllegalArgumentException("Missing type attribute in reason JSON: " + json);
final String type = json.getString("type");
switch (type) {
case VulnerabilityReason.TYPE:
return new VulnerabilityReason(json.getString("id"));
case ArtifactCpeReason.TYPE:
return new ArtifactCpeReason(
json,
json.optString("cpe", null),
json.optString("configuration", null)
);
case MsrcProductReason.TYPE:
return new MsrcProductReason(
json,
json.optString("msrcProductId", null),
json.getJSONArray("kbIds").toList().stream().map(Object::toString).toArray(String[]::new)
);
case ArtifactGhsaReason.TYPE:
return new ArtifactGhsaReason(
json,
json.optString("coordinates", null)
);
case AnyReason.TYPE:
return new AnyReason(json.optString("description", null));
case AnyArtifactReason.TYPE:
return new AnyArtifactReason(json);
case AssessmentStatusReason.TYPE:
return new AssessmentStatusReason(json.optString("originFile", null));
case AnyArtifactOverwriteSourceReason.TYPE:
return new AnyArtifactOverwriteSourceReason(json);
default:
throw new IllegalArgumentException("Unknown reason type: " + type + "\nIn reason JSON:" + json);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy