Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -837,8 +837,14 @@ public static Service convert(final QueryManager qm, final ServiceComponent serv
public static org.cyclonedx.model.vulnerability.Vulnerability convert(final QueryManager qm, final CycloneDXExporter.Variant variant,
final Finding finding) {
final Component component = qm.getObjectByUuid(Component.class, (String)finding.getComponent().get("uuid"));
if (component == null) {
return null;
}
final Project project = component.getProject();
final Vulnerability vulnerability = qm.getObjectByUuid(Vulnerability.class, (String)finding.getVulnerability().get("uuid"));
if (vulnerability == null) {
return null;
}

final org.cyclonedx.model.vulnerability.Vulnerability cdxVulnerability = new org.cyclonedx.model.vulnerability.Vulnerability();
cdxVulnerability.setBomRef(vulnerability.getUuid().toString());
Expand Down Expand Up @@ -928,10 +934,7 @@ public static org.cyclonedx.model.vulnerability.Vulnerability convert(final Quer
}

if (CycloneDXExporter.Variant.VEX == variant || CycloneDXExporter.Variant.VDR == variant) {
final Analysis analysis = qm.getAnalysis(
qm.getObjectByUuid(Component.class, component.getUuid()),
qm.getObjectByUuid(Vulnerability.class, vulnerability.getUuid())
);
final Analysis analysis = qm.getAnalysis(component, vulnerability);
if (analysis != null) {
final org.cyclonedx.model.vulnerability.Vulnerability.Analysis cdxAnalysis = new org.cyclonedx.model.vulnerability.Vulnerability.Analysis();
if (analysis.getAnalysisResponse() != null) {
Expand Down Expand Up @@ -964,6 +967,7 @@ public static List<org.cyclonedx.model.vulnerability.Vulnerability> generateVuln
final var vulnerabilitiesSeen = new HashSet<org.cyclonedx.model.vulnerability.Vulnerability>();
return findings.stream()
.map(finding -> convert(qm, variant, finding))
.filter(Objects::nonNull)
.filter(vulnerabilitiesSeen::add)
.toList();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* This file is part of Dependency-Track.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/
package org.dependencytrack.parser.cyclonedx;

import org.dependencytrack.PersistenceCapableTest;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.Finding;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.Severity;
import org.dependencytrack.model.Vulnerability;
import org.dependencytrack.parser.cyclonedx.util.ModelConverter;
import org.dependencytrack.tasks.scanners.AnalyzerIdentity;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;

class ModelConverterTest extends PersistenceCapableTest {

@Test
void generateVulnerabilitiesSkipsFindingWhoseComponentWasDeleted() {
final Project project = qm.createProject("acme-app", null, "1.0.0", null, null, null, true, false);

Vulnerability vulnerability = new Vulnerability();
vulnerability.setVulnId("INT-001");
vulnerability.setSource(Vulnerability.Source.INTERNAL);
vulnerability.setSeverity(Severity.HIGH);
vulnerability = qm.createVulnerability(vulnerability, false);

Component component = new Component();
component.setProject(project);
component.setName("acme-lib");
component.setVersion("1.0.0");
component = qm.createComponent(component, false);
qm.addVulnerability(vulnerability, component, AnalyzerIdentity.INTERNAL_ANALYZER);

// Resolve the findings up front, mirroring what the exporter does before conversion.
final List<Finding> findings = qm.getFindings(project, true);
assertThat(findings).hasSize(1);

// Simulate the component being deleted in the window between the findings query
// and the export (e.g. a concurrent re-analysis or manual deletion).
qm.recursivelyDelete(component, false);

// Before the fix this threw a NullPointerException and aborted the whole export.
assertThatNoException().isThrownBy(() ->
ModelConverter.generateVulnerabilities(qm, CycloneDXExporter.Variant.VEX, findings));

final List<org.cyclonedx.model.vulnerability.Vulnerability> result =
ModelConverter.generateVulnerabilities(qm, CycloneDXExporter.Variant.VEX, findings);
assertThat(result).isEmpty();
}

@Test
void generateVulnerabilitiesConvertsFindingWithResolvableComponent() {
final Project project = qm.createProject("acme-app", null, "1.0.0", null, null, null, true, false);

Vulnerability vulnerability = new Vulnerability();
vulnerability.setVulnId("INT-001");
vulnerability.setSource(Vulnerability.Source.INTERNAL);
vulnerability.setSeverity(Severity.HIGH);
vulnerability = qm.createVulnerability(vulnerability, false);

Component component = new Component();
component.setProject(project);
component.setName("acme-lib");
component.setVersion("1.0.0");
component = qm.createComponent(component, false);
qm.addVulnerability(vulnerability, component, AnalyzerIdentity.INTERNAL_ANALYZER);

final List<Finding> findings = qm.getFindings(project, true);

final List<org.cyclonedx.model.vulnerability.Vulnerability> result =
ModelConverter.generateVulnerabilities(qm, CycloneDXExporter.Variant.VEX, findings);
assertThat(result).hasSize(1);
assertThat(result.get(0).getId()).isEqualTo("INT-001");
}
}