Skip to content

Commit eb28ff0

Browse files
committed
Enable source filtering in SARIF format for /finding/project/{UUID}
Signed-off-by: Damian Sniezek <[email protected]>
1 parent a1092dc commit eb28ff0

File tree

4 files changed

+337
-196
lines changed

4 files changed

+337
-196
lines changed

src/main/java/org/dependencytrack/resources/v1/FindingResource.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ public Response getFindingsByProject(@Parameter(description = "The UUID of the p
123123
if (project != null) {
124124
if (qm.hasAccess(super.getPrincipal(), project)) {
125125
//final long totalCount = qm.getVulnerabilityCount(project, suppressed);
126-
final List<Finding> findings = qm.getFindings(project, suppressed);
126+
List<Finding> findings = qm.getFindings(project, suppressed);
127+
if (source != null) {
128+
findings = findings.stream().filter(finding -> source.name().equals(finding.getVulnerability().get("source"))).collect(Collectors.toList());
129+
}
127130
if (acceptHeader != null && acceptHeader.contains(MEDIA_TYPE_SARIF_JSON)) {
128131
try {
129132
return Response.ok(generateSARIF(findings), MEDIA_TYPE_SARIF_JSON)
@@ -133,10 +136,6 @@ public Response getFindingsByProject(@Parameter(description = "The UUID of the p
133136
LOGGER.error(ioException.getMessage(), ioException);
134137
return Response.status(Status.INTERNAL_SERVER_ERROR).entity("An error occurred while generating SARIF file").build();
135138
}
136-
}
137-
if (source != null) {
138-
final List<Finding> filteredList = findings.stream().filter(finding -> source.name().equals(finding.getVulnerability().get("source"))).collect(Collectors.toList());
139-
return Response.ok(filteredList).header(TOTAL_COUNT_HEADER, filteredList.size()).build();
140139
} else {
141140
return Response.ok(findings).header(TOTAL_COUNT_HEADER, findings.size()).build();
142141
}

src/test/java/org/dependencytrack/resources/v1/FindingResourceTest.java

Lines changed: 35 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,26 @@
3838
import org.dependencytrack.model.RepositoryType;
3939
import org.dependencytrack.model.Severity;
4040
import org.dependencytrack.model.Vulnerability;
41+
import org.dependencytrack.model.Vulnerability.Source;
4142
import org.dependencytrack.tasks.scanners.AnalyzerIdentity;
4243
import org.glassfish.jersey.server.ResourceConfig;
4344
import org.junit.jupiter.api.Assertions;
4445
import org.junit.jupiter.api.Test;
4546
import org.junit.jupiter.api.extension.RegisterExtension;
47+
import org.junit.jupiter.params.ParameterizedTest;
48+
import org.junit.jupiter.params.provider.MethodSource;
49+
import org.junit.jupiter.params.provider.Arguments;
50+
import java.util.stream.Stream;
51+
import static org.apache.commons.io.IOUtils.resourceToString;
52+
import java.nio.charset.StandardCharsets;
4653

4754
import java.util.Date;
4855
import java.util.List;
4956
import java.util.UUID;
5057

5158
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
52-
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
5359
import static org.assertj.core.api.Assertions.assertThat;
5460
import static org.dependencytrack.resources.v1.FindingResource.MEDIA_TYPE_SARIF_JSON;
55-
import static org.hamcrest.CoreMatchers.equalTo;
5661

5762
class FindingResourceTest extends ResourceTest {
5863

@@ -619,8 +624,9 @@ void getAllFindingsGroupedByVulnerabilityWithAclEnabled() {
619624
Assertions.assertEquals(1, json.getJsonObject(2).getJsonObject("vulnerability").getInt("affectedProjectCount"));
620625
}
621626

622-
@Test
623-
void getSARIFFindingsByProjectTest() {
627+
@ParameterizedTest
628+
@MethodSource("getSARIFFindingsByProjectTestParameters")
629+
void getSARIFFindingsByProjectTest(String query, String expectedResponsePath) throws Exception {
624630
Project project = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false);
625631
Component c1 = createComponent(project, "Component 1", "1.1.4");
626632
Component c2 = createComponent(project, "Component 2", "2.78.123");
@@ -629,192 +635,43 @@ void getSARIFFindingsByProjectTest() {
629635
c1.setPurl("pkg:maven/org.acme/[email protected]?type=jar");
630636
c2.setPurl("pkg:maven/com.xyz/[email protected]?type=jar");
631637

632-
Vulnerability v1 = createVulnerability("Vuln-1", Severity.CRITICAL, "Vuln Title 1", "This is a description", null, 80);
633-
Vulnerability v2 = createVulnerability("Vuln-2", Severity.HIGH, "Vuln Title 2", " Yet another description but with surrounding whitespaces ", "", 46);
634-
Vulnerability v3 = createVulnerability("Vuln-3", Severity.LOW, "Vuln Title 3", "A description-with-hyphens-(and parentheses)", " Recommendation with whitespaces ", 23);
638+
Vulnerability v1 = createVulnerability("Vuln-1", Severity.CRITICAL, "Vuln Title 1", "This is a description", null, 80, Source.INTERNAL);
639+
Vulnerability v2 = createVulnerability("Vuln-2", Severity.HIGH, "Vuln Title 2", " Yet another description but with surrounding whitespaces ", "", 46, Source.INTERNAL);
640+
Vulnerability v3 = createVulnerability("Vuln-3", Severity.LOW, "Vuln Title 3", "A description-with-hyphens-(and parentheses)", " Recommendation with whitespaces ", 23, Source.INTERNAL);
641+
Vulnerability v4 = createVulnerability("Vuln-4", Severity.MEDIUM, "Vuln Title 4", "This is a vulnerability that has GITHUB Advisory as a source", null, 20, Source.GITHUB);
635642

636-
// Note: Same vulnerability added to multiple components to test whether "rules" field doesn't contain duplicates
637643
qm.addVulnerability(v1, c1, AnalyzerIdentity.NONE);
638644
qm.addVulnerability(v2, c1, AnalyzerIdentity.NONE);
639645
qm.addVulnerability(v3, c1, AnalyzerIdentity.NONE);
640646
qm.addVulnerability(v3, c2, AnalyzerIdentity.NONE);
647+
qm.addVulnerability(v4, c2, AnalyzerIdentity.NONE);
641648

642-
Response response = jersey.target(V1_FINDING + "/project/" + project.getUuid().toString()).request()
649+
var target = jersey.target(V1_FINDING + "/project/" + project.getUuid().toString());
650+
if (query != null) {
651+
target = target.queryParam("source", query);
652+
}
653+
Response response = target.request()
643654
.header(HttpHeaders.ACCEPT, MEDIA_TYPE_SARIF_JSON)
644655
.header(X_API_KEY, apiKey)
645656
.get(Response.class);
646657

647658
Assertions.assertEquals(200, response.getStatus(), 0);
648659
Assertions.assertEquals(MEDIA_TYPE_SARIF_JSON, response.getHeaderString(HttpHeaders.CONTENT_TYPE));
649660
final String jsonResponse = getPlainTextBody(response);
661+
final String version = new About().getVersion();
662+
final String fullName = "OWASP Dependency-Track - " + version;
663+
String expectedTemplate = resourceToString(expectedResponsePath, StandardCharsets.UTF_8);
664+
String expected = expectedTemplate
665+
.replace("{{VERSION}}", version)
666+
.replace("{{FULL_NAME}}", fullName);
667+
assertThatJson(jsonResponse).isEqualTo(expected);
668+
}
650669

651-
assertThatJson(jsonResponse)
652-
.withMatcher("version", equalTo(new About().getVersion()))
653-
.withMatcher("fullName", equalTo("OWASP Dependency-Track - " + new About().getVersion()))
654-
.isEqualTo(json("""
655-
{
656-
"version": "2.1.0",
657-
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json",
658-
"runs": [
659-
{
660-
"tool": {
661-
"driver": {
662-
"name": "OWASP Dependency-Track",
663-
"fullName": "${json-unit.matches:fullName}",
664-
"version": "${json-unit.matches:version}",
665-
"informationUri": "https://dependencytrack.org/",
666-
"rules": [
667-
{
668-
"id": "Vuln-1",
669-
"name": "ImproperNeutralizationOfScript-relatedHtmlTagsInAWebPage(basicXss)",
670-
"shortDescription": {
671-
"text": "Vuln-1"
672-
},
673-
"fullDescription": {
674-
"text": "This is a description"
675-
}
676-
},
677-
{
678-
"id": "Vuln-2",
679-
"name": "PathEquivalence:'filename'(trailingSpace)",
680-
"shortDescription": {
681-
"text": "Vuln-2"
682-
},
683-
"fullDescription": {
684-
"text": "Yet another description but with surrounding whitespaces"
685-
}
686-
},
687-
{
688-
"id": "Vuln-3",
689-
"name": "RelativePathTraversal",
690-
"shortDescription": {
691-
"text": "Vuln-3"
692-
},
693-
"fullDescription": {
694-
"text": "A description-with-hyphens-(and parentheses)"
695-
}
696-
}
697-
]
698-
}
699-
},
700-
"results": [
701-
{
702-
"ruleId": "Vuln-1",
703-
"message": {
704-
"text": "This is a description"
705-
},
706-
"locations": [
707-
{
708-
"logicalLocations": [
709-
{
710-
"fullyQualifiedName": "pkg:maven/org.acme/[email protected]?type=jar"
711-
}
712-
]
713-
}
714-
],
715-
"level": "error",
716-
"properties": {
717-
"name": "Component 1",
718-
"group": "org.acme",
719-
"version": "1.1.4",
720-
"source": "INTERNAL",
721-
"cweId": "80",
722-
"cvssV3BaseScore": "",
723-
"epssScore": "",
724-
"epssPercentile": "",
725-
"severityRank": "0",
726-
"recommendation": ""
727-
}
728-
},
729-
{
730-
"ruleId": "Vuln-2",
731-
"message": {
732-
"text": "Yet another description but with surrounding whitespaces"
733-
},
734-
"locations": [
735-
{
736-
"logicalLocations": [
737-
{
738-
"fullyQualifiedName": "pkg:maven/org.acme/[email protected]?type=jar"
739-
}
740-
]
741-
}
742-
],
743-
"level": "error",
744-
"properties": {
745-
"name": "Component 1",
746-
"group": "org.acme",
747-
"version": "1.1.4",
748-
"source": "INTERNAL",
749-
"cweId": "46",
750-
"cvssV3BaseScore": "",
751-
"epssScore": "",
752-
"epssPercentile": "",
753-
"severityRank": "1",
754-
"recommendation": ""
755-
}
756-
},
757-
{
758-
"ruleId": "Vuln-3",
759-
"message": {
760-
"text": "A description-with-hyphens-(and parentheses)"
761-
},
762-
"locations": [
763-
{
764-
"logicalLocations": [
765-
{
766-
"fullyQualifiedName": "pkg:maven/org.acme/[email protected]?type=jar"
767-
}
768-
]
769-
}
770-
],
771-
"level": "note",
772-
"properties": {
773-
"name": "Component 1",
774-
"group": "org.acme",
775-
"version": "1.1.4",
776-
"source": "INTERNAL",
777-
"cweId": "23",
778-
"cvssV3BaseScore": "",
779-
"epssScore": "",
780-
"epssPercentile": "",
781-
"severityRank": "3",
782-
"recommendation": "Recommendation with whitespaces"
783-
}
784-
},
785-
{
786-
"ruleId": "Vuln-3",
787-
"message": {
788-
"text": "A description-with-hyphens-(and parentheses)"
789-
},
790-
"locations": [
791-
{
792-
"logicalLocations": [
793-
{
794-
"fullyQualifiedName": "pkg:maven/com.xyz/[email protected]?type=jar"
795-
}
796-
]
797-
}
798-
],
799-
"level": "note",
800-
"properties": {
801-
"name": "Component 2",
802-
"group": "com.xyz",
803-
"version": "2.78.123",
804-
"source": "INTERNAL",
805-
"cweId": "23",
806-
"cvssV3BaseScore": "",
807-
"epssScore": "",
808-
"epssPercentile": "",
809-
"severityRank": "3",
810-
"recommendation": "Recommendation with whitespaces"
811-
}
812-
}
813-
]
814-
}
815-
]
816-
}
817-
"""));
670+
private static Stream<Arguments> getSARIFFindingsByProjectTestParameters() {
671+
return Stream.of(
672+
Arguments.of("INTERNAL", "/unit/sarif/expected-internal.sarif.json"),
673+
Arguments.of(null, "/unit/sarif/expected-all.sarif.json")
674+
);
818675
}
819676

820677
private Component createComponent(Project project, String name, String version) {
@@ -834,28 +691,15 @@ private Vulnerability createVulnerability(String vulnId, Severity severity) {
834691
return qm.createVulnerability(vulnerability, false);
835692
}
836693

837-
private Vulnerability createVulnerability(String vulnId, Severity severity, String title, String description, String recommendation, Integer cweId) {
694+
private Vulnerability createVulnerability(String vulnId, Severity severity, String title, String description, String recommendation, Integer cweId, Vulnerability.Source source) {
838695
Vulnerability vulnerability = new Vulnerability();
839696
vulnerability.setVulnId(vulnId);
840-
vulnerability.setSource(Vulnerability.Source.INTERNAL);
697+
vulnerability.setSource(source);
841698
vulnerability.setSeverity(severity);
842699
vulnerability.setTitle(title);
843700
vulnerability.setDescription(description);
844701
vulnerability.setRecommendation(recommendation);
845702
vulnerability.setCwes(List.of(cweId));
846703
return qm.createVulnerability(vulnerability, false);
847704
}
848-
849-
private static String getSARIFLevelFromSeverity(Severity severity) {
850-
if (Severity.LOW == severity || Severity.INFO == severity) {
851-
return "note";
852-
}
853-
if (Severity.MEDIUM == severity) {
854-
return "warning";
855-
}
856-
if (Severity.HIGH == severity || Severity.CRITICAL == severity) {
857-
return "error";
858-
}
859-
return "none";
860-
}
861705
}

0 commit comments

Comments
 (0)