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

com.metaeffekt.artifact.analysis.vulnerability.enrichment.vulnerabilitystatus.VulnerabilityStatus Maven / Gradle / Ivy

The 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.vulnerability.enrichment.vulnerabilitystatus;

import com.metaeffekt.artifact.analysis.utils.*;
import com.metaeffekt.artifact.analysis.vulnerability.CommonEnumerationUtil;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.filter.FilterAttribute;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.vulnerabilitystatus.validation.VulnerabilityStatusValidation;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.vulnerabilitystatus.validation.VulnerabilityStatusValidationException;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.warnings.InventoryWarningEntry;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.warnings.InventoryWarnings;
import com.metaeffekt.mirror.contents.store.AdvisoryTypeIdentifier;
import com.metaeffekt.mirror.contents.vulnerability.Vulnerability;
import com.networknt.schema.SpecVersion;
import lombok.Getter;
import lombok.Setter;
import org.json.JSONArray;
import org.json.JSONObject;
import org.metaeffekt.core.inventory.processor.model.Inventory;
import org.metaeffekt.core.inventory.processor.model.VulnerabilityMetaData;
import org.metaeffekt.core.inventory.processor.report.configuration.CentralSecurityPolicyConfiguration;
import org.metaeffekt.core.security.cvss.CvssSource;
import org.metaeffekt.core.security.cvss.CvssVector;
import org.metaeffekt.core.security.cvss.KnownCvssEntities;
import org.metaeffekt.core.security.cvss.MultiScoreCvssVector;
import org.metaeffekt.core.security.cvss.v2.Cvss2;
import org.metaeffekt.core.security.cvss.v3.Cvss3P1;
import org.metaeffekt.core.security.cvss.v4P0.Cvss4P0;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import us.springett.parsers.cpe.Cpe;

import java.io.File;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Setter
@Getter
public class VulnerabilityStatus {

    private final static Logger LOG = LoggerFactory.getLogger(VulnerabilityStatus.class);

    public static boolean LOG_MATCHING_CRITERIA = false;

    private VulnerabilityStatusValidation validation;
    private final Set affectedVulnerabilitiesFilters = new HashSet<>();
    private final Set affectedVulnerabilities = new HashSet<>();
    private final Set affectedCpe = new HashSet<>();
    private final Set affectedCwe = new HashSet<>();

    private Scope scope = Scope.ARTIFACT;
    File originYamlFile;

    private String title;
    private String acceptedBy, acceptedDate, reportedBy, reportedDate;
    private Cvss2 cvss2, cvss2Lower, cvss2Higher;
    private Cvss3P1 cvss3, cvss3Lower, cvss3Higher;
    private Cvss4P0 cvss4, cvss4Lower, cvss4Higher;
    private final List reviewedAdvisories = new ArrayList<>();
    private final Set statusHistory = new TreeSet<>();


    public VulnerabilityStatus() {
    }

    public VulnerabilityStatus setCvss2(Cvss2 cvss2) {
        this.cvss2 = cvss2;
        return this;
    }

    public VulnerabilityStatus setCvss3P1(Cvss3P1 cvss3) {
        this.cvss3 = cvss3;
        return this;
    }

    public void setCvss3P1Lower(Cvss3P1 cvss3Lower) {
        this.cvss3Lower = cvss3Lower;
    }

    public void setCvss3P1Higher(Cvss3P1 cvss3Higher) {
        this.cvss3Higher = cvss3Higher;
    }

    public VulnerabilityStatus setCvss4(Cvss4P0 cvss4) {
        this.cvss4 = cvss4;
        return this;
    }

    public VulnerabilityStatus setScope(Scope scope) {
        this.scope = scope;
        this.statusHistory.forEach(e -> e.setScope(this.scope));
        return this;
    }

    public VulnerabilityStatus setScope(String scope) {
        this.scope = Scope.fromString(scope);
        this.statusHistory.forEach(e -> e.setScope(this.scope));
        return this;
    }

    public VulnerabilityStatus setAcceptedBy(String acceptedBy) {
        this.acceptedBy = acceptedBy;
        return this;
    }

    public VulnerabilityStatus setAcceptedDate(String acceptedDate) {
        this.acceptedDate = acceptedDate;
        return this;
    }

    public VulnerabilityStatus setReportedBy(String reportedBy) {
        this.reportedBy = reportedBy;
        return this;
    }

    public VulnerabilityStatus setReportedDate(String reportedDate) {
        this.reportedDate = reportedDate;
        return this;
    }

    public VulnerabilityStatus addReviewedAdvisoryEntry(String id, String comment) {
        return addReviewedAdvisoryEntry(new VulnerabilityStatusReviewedEntry(id, comment));
    }

    public VulnerabilityStatus addReviewedAdvisoryEntry(String id) {
        return addReviewedAdvisoryEntry(new VulnerabilityStatusReviewedEntry(id, null));
    }

    public VulnerabilityStatus addReviewedAdvisoryEntries(Collection entries) {
        entries.forEach(this::addReviewedAdvisoryEntry);
        return this;
    }

    public VulnerabilityStatus addReviewedAdvisoryEntry(VulnerabilityStatusReviewedEntry entry) {
        // check if the advisory has already been reviewed. in this case, add the comment to the existing entry
        final Optional existingEntry = reviewedAdvisories.stream()
                .filter(e -> e.getId().equals(entry.getId()))
                .findFirst();
        if (existingEntry.isPresent()) {
            final String existingComment = existingEntry.get().getComment();
            if (StringUtils.hasText(existingComment) && StringUtils.hasText(entry.getComment())) {
                existingEntry.get().setComment(existingComment + "; " + entry.getComment());
            } else if (StringUtils.hasText(entry.getComment())) {
                existingEntry.get().setComment(entry.getComment());
            } else {
                existingEntry.get().setComment(existingComment);
            }
        } else {
            reviewedAdvisories.add(entry.clone());
        }
        return this;
    }

    public VulnerabilityStatus addAffectedCpe(String cpe) {
        if (!cpe.startsWith("cpe")) {
            LOG.warn("CPE in status file might not be a valid CPE identifier [{}]", affectedVulnerabilities);
        }
        this.affectedCpe.add(cpe);
        return this;
    }

    public VulnerabilityStatus addAffectedVulnerability(String vulnerability) {
        this.affectedVulnerabilities.add(vulnerability);
        return this;
    }

    public VulnerabilityStatus addAffectedVulnerabilitiesFilter(FilterAttribute filter) {
        this.affectedVulnerabilitiesFilters.add(filter);
        return this;
    }

    public VulnerabilityStatus addAffectedCwe(String cwe) {
        if (!cwe.startsWith("CWE")) {
            LOG.warn("CWE in status file might not be a valid CWE identifier [{}]", cwe);
        }
        this.affectedCwe.add(cwe);
        return this;
    }

    public VulnerabilityStatus addHistoryEntry(VulnerabilityStatusHistoryEntry entry) {
        statusHistory.add(entry);
        return this;
    }

    public VulnerabilityStatus addHistoryEntries(Collection entries) {
        entries.forEach(this::addHistoryEntry);
        return this;
    }

    public VulnerabilityStatus removeHistoryEntry(VulnerabilityStatusHistoryEntry entry) {
        statusHistory.remove(entry);
        return this;
    }

    public VulnerabilityStatus clearHistoryEntries() {
        statusHistory.clear();
        return this;
    }

    public List getStatusHistory() {
        return Collections.unmodifiableList(new ArrayList<>(statusHistory));
    }

    public Set getStatusHistorySet() {
        return statusHistory;
    }

    public Set getStatusHistoryModifiable() {
        return statusHistory;
    }

    public VulnerabilityStatusHistoryEntry getLatestActiveStatusHistoryEntry() {
        return statusHistory.stream()
                .filter(VulnerabilityStatusHistoryEntry::isActive)
                .findFirst()
                .orElse(null);
    }

    public boolean isLatestStatusHistoryEntryOfType(String type) {
        if (StringUtils.isEmpty(type)) return false;
        final VulnerabilityStatusHistoryEntry latestEntry = getLatestActiveStatusHistoryEntry();
        if (latestEntry == null) return false;
        return type.equals(latestEntry.getStatus());
    }

    public Set getAffectedVulnerabilitiesWithoutWildcards() {
        return affectedVulnerabilities.stream().filter(c -> !WildcardUtilities.isWildcardPattern(c)).collect(Collectors.toSet());
    }

    public List getReviewedAdvisories(AdvisoryTypeIdentifier type) {
        return reviewedAdvisories.stream()
                .filter(a -> a.getAdvisor() == type)
                .sorted(Comparator.comparing(e -> e.getAdvisor().name()))
                .collect(Collectors.toList());
    }

    public Date getAcceptedDateAsDate() {
        return TimeUtils.tryParse(acceptedDate);
    }

    public Date getReportedDateAsDate() {
        return TimeUtils.tryParse(reportedDate);
    }

    public boolean hasReviewedAdvisories() {
        return !reviewedAdvisories.isEmpty();
    }

    public boolean hasReportedBy() {
        return StringUtils.hasText(reportedBy) || StringUtils.hasText(reportedDate);
    }

    public boolean hasAcceptedBy() {
        return StringUtils.hasText(acceptedBy) || StringUtils.hasText(acceptedDate);
    }

    public String generateAcceptedByDateString() {
        final StringJoiner builder = new StringJoiner(" ");

        if (StringUtils.hasText(acceptedBy)) builder.add(acceptedBy);
        else builder.add("no author");
        if (StringUtils.hasText(acceptedDate)) builder.add("(" + acceptedDate + ")");

        return builder.toString();
    }

    public String generateReportedByDateString() {
        final StringJoiner builder = new StringJoiner(" ");

        if (StringUtils.hasText(reportedBy)) builder.add(reportedBy);
        else builder.add("no author");
        if (StringUtils.hasText(reportedDate)) builder.add("(" + reportedDate + ")");

        return builder.toString();
    }

    public boolean hasAcceptedByInformation() {
        return StringUtils.hasText(acceptedBy) || StringUtils.hasText(acceptedDate);
    }

    public boolean hasReportedByInformation() {
        return StringUtils.hasText(reportedBy) || StringUtils.hasText(reportedDate);
    }

    public Cvss3P1 getCvss3P1() {
        return cvss3;
    }

    public Cvss3P1 getCvss3P1Lower() {
        return cvss3Lower;
    }

    public Cvss3P1 getCvss3P1Higher() {
        return cvss3Higher;
    }

    public boolean hasCvss2() {
        return isCvssDefined(cvss2);
    }

    public boolean hasCvss3P1() {
        return isCvssDefined(cvss3);
    }

    public boolean hasCvss4() {
        return isCvssDefined(cvss4);
    }

    public boolean hasCvss2Lower() {
        return isCvssDefined(cvss2Lower);
    }

    public boolean hasCvss2Higher() {
        return isCvssDefined(cvss2Higher);
    }

    public boolean hasCvss3P1Lower() {
        return isCvssDefined(cvss3Lower);
    }

    public boolean hasCvss3P1Higher() {
        return isCvssDefined(cvss3Higher);
    }

    public boolean hasAnyCvss2Information() {
        return hasCvss2() || hasCvss2Lower() || hasCvss2Higher();
    }

    public boolean hasAnyCvss3P1Information() {
        return hasCvss3P1() || hasCvss3P1Lower() || hasCvss3P1Higher();
    }

    public boolean hasAnyCvss4Information() {
        return hasCvss4() || hasCvss4Lower() || hasCvss4Higher();
    }

    public boolean hasCvss4Lower() {
        return isCvssDefined(cvss4Lower);
    }

    public boolean hasCvss4Higher() {
        return isCvssDefined(cvss4Higher);
    }

    private boolean isCvssDefined(MultiScoreCvssVector cvss) {
        return cvss != null && (cvss.isAnyBaseDefined() || cvss.isAnyTemporalDefined() || cvss.isAnyEnvironmentalDefined());
    }

    private boolean isCvssDefined(Cvss4P0 cvss) {
        return cvss != null && (cvss.isAnyBaseDefined() || cvss.isAnyThreatDefined() || cvss.isAnyEnvironmentalDefined());
    }

    private boolean isCvssDefined(CvssVector cvss) {
        return cvss != null && cvss.isAnyBaseDefined();
    }

    public void applyCvss2(Cvss2 cvss2) {
        if (cvss2 == null) return;
        cvss2.applyVector(this.cvss2);
        cvss2.applyVectorPartsIfHigher(cvss2Higher, CvssVector::getOverallScore);
        cvss2.applyVectorPartsIfLower(cvss2Lower, CvssVector::getOverallScore);
    }

    public void applyCvss3P1(Cvss3P1 cvss3) {
        if (cvss3 == null) return;
        cvss3.applyVector(this.cvss3);
        cvss3.applyVectorPartsIfHigher(cvss3Higher, CvssVector::getOverallScore);
        cvss3.applyVectorPartsIfLower(cvss3Lower, CvssVector::getOverallScore);
    }

    public void applyCvss4(Cvss4P0 cvss4) {
        if (cvss4 == null) return;
        cvss4.applyVector(this.cvss4);
        cvss4.applyVectorPartsIfHigher(cvss4Higher, CvssVector::getOverallScore);
        cvss4.applyVectorPartsIfLower(cvss4Lower, CvssVector::getOverallScore);
    }

    public boolean isScope(Scope scope) {
        return this.scope == scope;
    }

    public List getLabelFilteredStatusHistory(Collection activeLabels) {
        return statusHistory.stream()
                .map(VulnerabilityStatusHistoryEntry::clone)
                .peek(entry -> entry.setActive(entry.isIncluded(activeLabels)))
                .sorted()
                .collect(Collectors.toList());
    }

    public List getLabelFilteredStatusHistory(String[] activeLabels) {
        return getLabelFilteredStatusHistory(Arrays.asList(activeLabels));
    }

    public MatchType affectsRetainCondition(Vulnerability vulnerability) {
        if (vulnerability == null) {
            throw new IllegalArgumentException("VulnerabilityMetaData must not be null");
        }

        return affectsRetainCondition(
                vulnerability,
                CommonEnumerationUtil.parseCpes(vulnerability.getAdditionalAttribute(VulnerabilityMetaData.Attribute.PRODUCT_URIS)),
                vulnerability.getCwes()
        );
    }

    public boolean affects(Vulnerability vulnerability) {
        if (vulnerability == null) {
            throw new IllegalArgumentException("VulnerabilityMetaData must not be null");
        }

        return affects(
                vulnerability,
                CommonEnumerationUtil.parseCpes(vulnerability.getAdditionalAttribute(VulnerabilityMetaData.Attribute.PRODUCT_URIS)),
                vulnerability.getCwes()
        );
    }

    public boolean affects(Vulnerability vulnerability, Collection vmdCpe, Collection vmdCwe) {
        return affectsRetainCondition(vulnerability, vmdCpe, vmdCwe) != null;
    }

    public MatchType affectsRetainCondition(Vulnerability vulnerability, Collection vmdCpe, Collection vmdCwe) {
        if (scope == Scope.INVENTORY) {
            return MatchType.INVENTORY_SCOPE;
        }

        if (vulnerability.getId() != null) {
            if (affectsByVulnerability(vulnerability)) {
                return MatchType.VULNERABILITY_NAME;
            } else {
                if (affectsByVulnerabilityFilter(vulnerability)) {
                    return MatchType.FILTER_ATTRIBUTE;
                }
            }
        }

        if (vmdCpe != null) {
            if (affectsByCpe(vmdCpe)) {
                return MatchType.CPE;
            }
        }

        if (vmdCwe != null) {
            if (affectsByCwe(vmdCwe)) {
                return MatchType.CWE;
            }
        }

        return null;
    }

    private boolean affectsByCpe(Collection vmdCpe) {
        for (String cpe : affectedCpe) {
            final Cpe compareCpe = CommonEnumerationUtil.parseCpe(cpe).orElse(null);
            if (compareCpe == null) {
                LOG.warn("CPE [{}] not valid identifier", cpe);
            }

            for (Cpe vmdCpeEntry : vmdCpe) {
                if (CommonEnumerationUtil.compareCpeUsingWildcards(vmdCpeEntry, compareCpe)) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean affectsByCwe(Collection vmdCwes) {
        for (String cwe : vmdCwes) {
            if (affectedCwe.contains(cwe)) {
                return true;
            }
        }

        return false;
    }

    private boolean affectsByVulnerability(Vulnerability vulnerability) {
        if (affectedVulnerabilities.contains(vulnerability.getId())) {
            if (affectedVulnerabilitiesFilters.isEmpty()) {
                return true;
            } else {
                return affectsByVulnerabilityFilter(vulnerability);
            }
        }

        // cve identifiers might contain wildcards; check again using regex
        for (Pattern pattern : affectedVulnerabilities.stream()
                .filter(WildcardUtilities::isWildcardPattern)
                .map(WildcardUtilities::convertWildcardStringToPattern)
                .collect(Collectors.toList())) {

            if (pattern.matcher(vulnerability.getId()).matches()) {
                if (affectedVulnerabilitiesFilters.isEmpty()) {
                    return true;
                } else {
                    return affectsByVulnerabilityFilter(vulnerability);
                }
            }
        }

        return false;
    }

    private boolean affectsByVulnerabilityFilter(Vulnerability vulnerability) {
        if (affectedVulnerabilitiesFilters.isEmpty()) {
            return false;
        }

        if (LOG_MATCHING_CRITERIA) {
            LOG.info("Checking vulnerability [{}] against filter attributes: {}",
                    vulnerability.getId(),
                    affectedVulnerabilitiesFilters.stream().map(FilterAttribute::toString).map(fa -> fa.replace("\n", "\\n")).collect(Collectors.joining(", "))
            );
        }

        for (FilterAttribute filter : affectedVulnerabilitiesFilters) {
            if (filter.matches(vulnerability)) {
                if (LOG_MATCHING_CRITERIA) {
                    LOG.info("Vulnerability [{}] matches filter attribute: {}", vulnerability.getId(), filter);
                }
                return true;
            }
        }

        return false;
    }

    public void reorderChronologically(Vulnerability vulnerability, boolean isInsignificant, double insignificantThreshold) {
        final List reordered = VulnerabilityStatusHistoryEntry.reorderChronologically(this, vulnerability, isInsignificant, insignificantThreshold);
        this.statusHistory.clear();
        this.statusHistory.addAll(reordered);
    }

    public void checkValidation(Inventory inventory, Vulnerability vulnerability, boolean failOnValidationError) {
        if (this.validation != null) {
            if (inventory == null) {
                LOG.warn("Inventory is null, skipping validation on status file {}", originYamlFile);
            }
            try {
                this.validation.validateInventory(inventory, vulnerability.getId());

            } catch (VulnerabilityStatusValidationException e) {
                final StringBuilder errorMessage = new StringBuilder();
                errorMessage.append("Vulnerability status validation failed on vulnerability ")
                        .append(vulnerability.getId()).append(": ")
                        .append(e.getMessage());
                if (originYamlFile != null) {
                    errorMessage.append("\nfrom status file ").append(originYamlFile);
                }

                if (failOnValidationError) {
                    throw new RuntimeException(errorMessage.toString(), e);
                }

                LOG.error(errorMessage.toString(), e);
                new InventoryWarnings(inventory).addVulnerabilityWarning(new InventoryWarningEntry<>(vulnerability.toBaseModel(),
                        "Vulnerability status validation failed: " + e.getMessage(),
                        originYamlFile.getAbsolutePath()
                ));
            }
        }
    }

    public void applyToVulnerability(Vulnerability vulnerability) {
        if (vulnerability == null) {
            throw new IllegalArgumentException("Vulnerability must not be null");
        }

        vulnerability.setVulnerabilityStatus(this);

        vulnerability.clearNonTransferableStatusDetails();

        // reviewed advisories
        if (!this.reviewedAdvisories.isEmpty()) {
            final JSONArray outputReviewedAdvisoriesJson = VulnerabilityStatusReviewedEntry.toJsonArray(this.reviewedAdvisories);
            if (!outputReviewedAdvisoriesJson.isEmpty()) {
                vulnerability.setAdditionalAttribute(InventoryAttribute.REVIEWED_ADVISORIES, outputReviewedAdvisoriesJson.toString());
            }
        }
        /*if (this.hasReviewedAdvisories()) {
            VulnerabilityStatusReviewedEntry.appendToVulnerability(vulnerability, this.getReviewedAdvisories());
        }*/


        // CVSS
        setCvssVectorIfPresent(vulnerability, Cvss2.class, this::hasCvss2, this::getCvss2, KnownCvssEntities.ASSESSMENT_ALL);
        setCvssVectorIfPresent(vulnerability, Cvss2.class, this::hasCvss2Lower, this::getCvss2Lower, KnownCvssEntities.ASSESSMENT_LOWER);
        setCvssVectorIfPresent(vulnerability, Cvss2.class, this::hasCvss2Higher, this::getCvss2Higher, KnownCvssEntities.ASSESSMENT_HIGHER);

        setCvssVectorIfPresent(vulnerability, Cvss3P1.class, this::hasCvss3P1, this::getCvss3P1, KnownCvssEntities.ASSESSMENT_ALL);
        setCvssVectorIfPresent(vulnerability, Cvss3P1.class, this::hasCvss3P1Lower, this::getCvss3P1Lower, KnownCvssEntities.ASSESSMENT_LOWER);
        setCvssVectorIfPresent(vulnerability, Cvss3P1.class, this::hasCvss3P1Higher, this::getCvss3P1Higher, KnownCvssEntities.ASSESSMENT_HIGHER);

        setCvssVectorIfPresent(vulnerability, Cvss4P0.class, this::hasCvss4, this::getCvss4, KnownCvssEntities.ASSESSMENT_ALL);
        setCvssVectorIfPresent(vulnerability, Cvss4P0.class, this::hasCvss4Lower, this::getCvss4Lower, KnownCvssEntities.ASSESSMENT_LOWER);
        setCvssVectorIfPresent(vulnerability, Cvss4P0.class, this::hasCvss4Higher, this::getCvss4Higher, KnownCvssEntities.ASSESSMENT_HIGHER);

        // history entries
        final List exportHistory = this.getStatusHistory();
        if (!exportHistory.isEmpty()) {
            final JSONArray historyJson = exportHistory.stream()
                    .map(VulnerabilityStatusHistoryEntry::toJson)
                    .collect(CustomCollectors.toJsonArray());
            vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_HISTORY.getKey(), historyJson.toString());
        }

        // accepted / reported by
        if (this.acceptedBy != null || this.acceptedDate != null) {
            vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_ACCEPTED.getKey(), this.generateAcceptedByDateString());
        } else {
            vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_ACCEPTED.getKey(), null);
        }
        if (this.reportedBy != null || this.reportedDate != null) {
            vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_REPORTED.getKey(), this.generateReportedByDateString());
        } else {
            vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_REPORTED.getKey(), null);
        }

        // title
        if (this.title != null) {
            vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_TITLE.getKey(), this.title);
        } else {
            vulnerability.setAdditionalAttribute(InventoryAttribute.STATUS_TITLE.getKey(), null);
        }

        // fill other fields specific to the latest entry, required for report generation
        final VulnerabilityStatusHistoryEntry latestHistoryEntry = this.getLatestActiveStatusHistoryEntry();
        if (latestHistoryEntry != null) {
            vulnerability.setAdditionalAttribute(VulnerabilityMetaData.Attribute.STATUS, latestHistoryEntry.getStatus());
            vulnerability.setAdditionalAttribute(VulnerabilityMetaData.Attribute.RATIONALE, latestHistoryEntry.getRationale());
            vulnerability.setAdditionalAttribute(VulnerabilityMetaData.Attribute.RISK, latestHistoryEntry.getRisk());
            vulnerability.setAdditionalAttribute(InventoryAttribute.MEASURES.getKey(), latestHistoryEntry.getMeasures());
        } else {
            vulnerability.setAdditionalAttribute(VulnerabilityMetaData.Attribute.STATUS, null);
            vulnerability.setAdditionalAttribute(VulnerabilityMetaData.Attribute.RATIONALE, null);
            vulnerability.setAdditionalAttribute(VulnerabilityMetaData.Attribute.RISK, null);
            vulnerability.setAdditionalAttribute(InventoryAttribute.MEASURES.getKey(), null);
        }
    }

    public void appendToVulnerabilityStatus(VulnerabilityStatus appendToStatus, Collection activeLabels) {
        appendAllExceptStatusHistoryToVulnerabilityStatus(appendToStatus);
        appendStatusHistoryOnlyToVulnerabilityStatus(appendToStatus, activeLabels);
    }

    public void appendAllExceptStatusHistoryToVulnerabilityStatus(VulnerabilityStatus appendToStatus) {
        // CVSS
        setCvssVectorIfPresent(this::hasCvss2, this::getCvss2, appendToStatus::setCvss2);
        setCvssVectorIfPresent(this::hasCvss2Lower, this::getCvss2Lower, appendToStatus::setCvss2Lower);
        setCvssVectorIfPresent(this::hasCvss2Higher, this::getCvss2Higher, appendToStatus::setCvss2Higher);

        setCvssVectorIfPresent(this::hasCvss3P1, this::getCvss3P1, appendToStatus::setCvss3P1);
        setCvssVectorIfPresent(this::hasCvss3P1Lower, this::getCvss3P1Lower, appendToStatus::setCvss3P1Lower);
        setCvssVectorIfPresent(this::hasCvss3P1Higher, this::getCvss3P1Higher, appendToStatus::setCvss3P1Higher);

        setCvssVectorIfPresent(this::hasCvss4, this::getCvss4, appendToStatus::setCvss4);
        setCvssVectorIfPresent(this::hasCvss4Lower, this::getCvss4Lower, appendToStatus::setCvss4Lower);
        setCvssVectorIfPresent(this::hasCvss4Higher, this::getCvss4Higher, appendToStatus::setCvss4Higher);

        // reviewed advisories
        if (hasReviewedAdvisories()) {
            appendToStatus.addReviewedAdvisoryEntries(reviewedAdvisories);
        }

        // accepted / reported by
        if (acceptedBy != null) {
            appendToStatus.setAcceptedBy(acceptedBy);
        }
        if (acceptedDate != null) {
            appendToStatus.setAcceptedDate(acceptedDate);
        }
        if (reportedBy != null) {
            appendToStatus.setReportedBy(reportedBy);
        }
        if (reportedDate != null) {
            appendToStatus.setReportedDate(reportedDate);
        }

        // title
        if (title != null) {
            appendToStatus.setTitle(title);
        }

        // affects fields
        appendToStatus.getAffectedCpe().addAll(this.affectedCpe);
        appendToStatus.getAffectedVulnerabilities().addAll(this.affectedVulnerabilities);
        appendToStatus.getAffectedVulnerabilitiesFilters().addAll(this.affectedVulnerabilitiesFilters);
        appendToStatus.getAffectedCwe().addAll(this.affectedCwe);
    }

    public void appendStatusHistoryOnlyToVulnerabilityStatus(VulnerabilityStatus appendToStatus, Collection activeLabels) {
        // history entries
        final List exportHistory = getLabelFilteredStatusHistory(activeLabels);
        appendToStatus.addHistoryEntries(exportHistory);
    }

    private static  void setCvssVectorIfPresent(Supplier hasCvss, Supplier getCvss, Consumer setStatusCvss) {
        if (hasCvss.get()) {
            setStatusCvss.accept(getCvss.get());
        }
    }

    private static  void setCvssVectorIfPresent(Vulnerability vulnerability, Class clazz, Supplier hasCvss, Supplier getCvss, CvssSource.CvssEntity entity) {
        if (hasCvss.get()) {
            final T vector = (T) getCvss.get().deriveAddSource(new CvssSource(KnownCvssEntities.ASSESSMENT, entity, clazz));
            vulnerability.getCvssVectors().addCvssVector(vector);
        }
    }

    public JSONObject toJson() {
        final JSONObject json = new JSONObject();

        json.put("cvss2", cvss2 != null ? String.valueOf(cvss2) : null);
        json.put("cvss2Lower", cvss2Lower != null ? String.valueOf(cvss2Lower) : null);
        json.put("cvss2Higher", cvss2Higher != null ? String.valueOf(cvss2Higher) : null);
        json.put("cvss3", cvss3 != null ? String.valueOf(cvss3) : null);
        json.put("cvss3Lower", cvss3Lower != null ? String.valueOf(cvss3Lower) : null);
        json.put("cvss3Higher", cvss3Higher != null ? String.valueOf(cvss3Higher) : null);
        json.put("cvss4", cvss4 != null ? String.valueOf(cvss4) : null);
        json.put("cvss4Lower", cvss4Lower != null ? String.valueOf(cvss4Lower) : null);
        json.put("cvss4Higher", cvss4Higher != null ? String.valueOf(cvss4Higher) : null);

        final JSONArray reviewed = reviewedAdvisories.stream().map(VulnerabilityStatusReviewedEntry::toJson).collect(JSONArray::new, JSONArray::put, JSONArray::putAll);
        json.put("reviewedAdvisories", reviewed);
        final JSONArray history = statusHistory.stream().map(VulnerabilityStatusHistoryEntry::toJson).collect(JSONArray::new, JSONArray::put, JSONArray::putAll);
        json.put("statusHistory", history);

        json.put("acceptedBy", acceptedBy);
        json.put("acceptedDate", acceptedDate);
        json.put("reportedBy", reportedBy);
        json.put("reportedDate", reportedDate);
        json.put("title", title);

        final JSONArray affectedVulnerabilities = this.affectedVulnerabilities.stream().collect(JSONArray::new, JSONArray::put, JSONArray::putAll);
        json.put("affectedVulnerabilities", affectedVulnerabilities);
        final JSONArray affectedCpe = this.affectedCpe.stream().collect(JSONArray::new, JSONArray::put, JSONArray::putAll);
        json.put("affectedCpe", affectedCpe);
        final JSONArray affectedCwe = this.affectedCwe.stream().collect(JSONArray::new, JSONArray::put, JSONArray::putAll);
        json.put("affectedCwe", affectedCwe);

        json.put("scope", scope);

        return json;
    }

    public void appendFromJson(final JSONObject json) {
        this.setCvss2(new Cvss2(json.optString("cvss2", null)));
        this.setCvss2Lower(new Cvss2(json.optString("cvss2Lower", null)));
        this.setCvss2Higher(new Cvss2(json.optString("cvss2Higher", null)));
        this.setCvss3P1(new Cvss3P1(json.optString("cvss3", null)));
        this.setCvss3P1Lower(new Cvss3P1(json.optString("cvss3Lower", null)));
        this.setCvss3P1Higher(new Cvss3P1(json.optString("cvss3Higher", null)));
        this.setCvss4(new Cvss4P0(json.optString("cvss4", null)));
        this.setCvss4Lower(new Cvss4P0(json.optString("cvss4Lower", null)));
        this.setCvss4Higher(new Cvss4P0(json.optString("cvss4Higher", null)));

        final JSONArray reviewed = json.optJSONArray("reviewedAdvisories");
        if (reviewed != null) {
            for (int i = 0; i < reviewed.length(); i++) {
                this.getReviewedAdvisories().add(VulnerabilityStatusReviewedEntry.fromMap(reviewed.getJSONObject(i).toMap()));
            }
        }

        final JSONArray history = json.optJSONArray("statusHistory");
        if (history != null) {
            for (int i = 0; i < history.length(); i++) {
                this.addHistoryEntry(VulnerabilityStatusHistoryEntry.fromMap(history.getJSONObject(i).toMap()));
            }
        }

        this.setAcceptedBy(json.optString("acceptedBy", null));
        this.setAcceptedDate(json.optString("acceptedDate", null));
        this.setReportedBy(json.optString("reportedBy", null));
        this.setReportedDate(json.optString("reportedDate", null));
        this.setTitle(json.optString("title", null));

        final JSONArray affectedVulnerabilities = json.optJSONArray("affectedVulnerabilities");
        if (affectedVulnerabilities != null) {
            for (int i = 0; i < affectedVulnerabilities.length(); i++) {
                this.addAffectedVulnerability(affectedVulnerabilities.getString(i));
            }
        }
        final JSONArray affectedCpe = json.optJSONArray("affectedCpe");
        if (affectedCpe != null) {
            for (int i = 0; i < affectedCpe.length(); i++) {
                this.addAffectedCpe(affectedCpe.getString(i));
            }
        }
        final JSONArray affectedCwe = json.optJSONArray("affectedCwe");
        if (affectedCwe != null) {
            for (int i = 0; i < affectedCwe.length(); i++) {
                this.addAffectedCwe(affectedCwe.getString(i));
            }
        }

        this.setScope(VulnerabilityStatus.Scope.fromString(json.optString("scope", null)));
    }

    @Override
    public String toString() {
        return "VulnerabilityStatus{" +
                "statusHistory=" + statusHistory +
                ", validation=" + validation +
                ", affectedVulnerabilities=" + affectedVulnerabilities +
                ", affectedCpe=" + affectedCpe +
                ", affectedCwe=" + affectedCwe +
                ", reviewedAdvisories=" + reviewedAdvisories +
                ", title='" + title + '\'' +
                ", acceptedBy='" + acceptedBy + '\'' +
                ", acceptedDate='" + acceptedDate + '\'' +
                ", reportedBy='" + reportedBy + '\'' +
                ", reportedDate='" + reportedDate + '\'' +
                ", scope=" + scope +
                ", originYamlFile=" + originYamlFile +
                '}';
    }

    public static List mergeStatusHistoryEntries(Collection> entries) {
        return entries.stream()
                .filter(Objects::nonNull)
                .flatMap(Collection::stream)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
    }

    public static Set findAffectedEntries(Collection vulnerabilityStatusHistories, Vulnerability vulnerability) {
        return vulnerabilityStatusHistories.stream()
                .filter(Objects::nonNull)
                .filter(vsh -> vsh.affects(vulnerability))
                .collect(Collectors.toSet());
    }

    public static Map> findAffectedEntriesRetainMatchingCondition(Collection vulnerabilityStatuses, Vulnerability vulnerability) {
        final Map> result = new LinkedHashMap<>();

        for (VulnerabilityStatus vulnerabilityStatus : vulnerabilityStatuses) {
            final MatchType matchType = vulnerabilityStatus.affectsRetainCondition(vulnerability);
            if (matchType != null) {
                result.computeIfAbsent(matchType, k -> new ArrayList<>()).add(vulnerabilityStatus);

                if (LOG_MATCHING_CRITERIA) {
                    LOG.info("Vulnerability [{}] uses match type [{}] for status [{}]", vulnerability.getId(), matchType,
                            vulnerabilityStatus.getOriginYamlFile() == null ? "with " + vulnerabilityStatus.statusHistory.size() + " status history entries " : vulnerabilityStatus.getOriginYamlFile().getName()
                    );
                }
            }
        }

        return result;
    }

    /**
     * Attempts to assert that the provided file is a valid vulnerability status file.
* It will use the map and array schema to validate the file, returning if one of them applies and throwing the array schema exception if none applies. * * @param file the file to validate * @param jsonSchemaValidationErrorsHandling Error handling mode. */ public static void assertVulnerabilityStatusFileValid(File file, CentralSecurityPolicyConfiguration.JsonSchemaValidationErrorsHandling jsonSchemaValidationErrorsHandling) { try { JsonSchemaValidator.assertResourceSchemaAppliesToYamlFile( file, "specification/jsonschema/vulnerability-status.json", SpecVersion.VersionFlag.V201909, "Vulnerability Status YAML", jsonSchemaValidationErrorsHandling ); } catch (RuntimeException mapTypeException) { JsonSchemaValidator.assertResourceSchemaAppliesToYamlFile( file, "specification/jsonschema/vulnerability-status-array.json", SpecVersion.VersionFlag.V201909, "Vulnerability Status ARRAY YAML", jsonSchemaValidationErrorsHandling ); } } public enum Scope { ARTIFACT, INVENTORY; public static Scope fromString(String scope) { if (scope == null) { return ARTIFACT; } switch (scope.trim().toLowerCase()) { case "inventory": return INVENTORY; case "artifact": default: return ARTIFACT; } } } public enum MatchType { INVENTORY_SCOPE, VULNERABILITY_NAME, CPE, CWE, FILTER_ATTRIBUTE; public static > T findHighestPriority(Map affectedEntries) { if (affectedEntries.containsKey(INVENTORY_SCOPE)) { return affectedEntries.get(INVENTORY_SCOPE); } if (affectedEntries.containsKey(VULNERABILITY_NAME)) { return affectedEntries.get(VULNERABILITY_NAME); } if (affectedEntries.containsKey(CPE)) { return affectedEntries.get(CPE); } if (affectedEntries.containsKey(CWE)) { return affectedEntries.get(CWE); } if (affectedEntries.containsKey(FILTER_ATTRIBUTE)) { return affectedEntries.get(FILTER_ATTRIBUTE); } return null; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy