Skip to content

Commit

Permalink
Add Support for Identification of Aliases
Browse files Browse the repository at this point in the history
... by ignoring them

closes #168
  • Loading branch information
sephiroth-j committed Jan 9, 2024
1 parent 571662b commit 549596b
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.jenkinsci.plugins.DependencyTrack;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
Expand All @@ -38,7 +39,13 @@ List<Finding> parse(final String jsonResponse) {
return jsonArray.stream()
.map(JSONObject.class::cast)
.map(FindingParser::parseFinding)
.collect(Collectors.toList());
.collect(ArrayList<Finding>::new, (findings, finding) -> {
// filter duplicates based on aliases
// add if is not already included and if it is not an alias of an already present finding/vulnerability
if (!findings.contains(finding) && findings.stream().noneMatch(finding::isAliasOf)) {
findings.add(finding);
}
}, List::addAll);
}

private Finding parseFinding(JSONObject json) {
Expand Down Expand Up @@ -71,7 +78,8 @@ private Vulnerability parseVulnerability(JSONObject json) {
final var cwe = Optional.ofNullable(json.optJSONArray("cwes")).map(a -> a.optJSONObject(0)).filter(Predicate.not(JSONNull.class::isInstance));
final Integer cweId = cwe.map(o -> o.optInt("cweId")).orElse(null);
final String cweName = cwe.map(o -> getKeyOrNull(o, "name")).orElse(null);
return new Vulnerability(uuid, source, vulnId, title, subtitle, description, recommendation, severity, severityRank, cweId, cweName);
final var aliases = parseAliases(json, vulnId);
return new Vulnerability(uuid, source, vulnId, title, subtitle, description, recommendation, severity, severityRank, cweId, cweName, aliases);
}

private Analysis parseAnalysis(JSONObject json) {
Expand All @@ -80,13 +88,26 @@ private Analysis parseAnalysis(JSONObject json) {
return new Analysis(state, isSuppressed);
}

private List<String> parseAliases(JSONObject json, String vulnId) {
final var aliases = json.optJSONArray("aliases");
return aliases != null ? aliases.stream()
.map(JSONObject.class::cast)
.flatMap(alias -> alias.names().stream()
.map(String.class::cast)
.map(alias::getString)
.filter(Predicate.not(vulnId::equalsIgnoreCase)))
.distinct()
.collect(Collectors.toList())
: null;
}

private String getKeyOrNull(JSONObject json, String key) {
// key can be null. but it may also be JSONNull!
// optString and getString do not check if v is JSONNull. instead they return just v.toString() which will be "null"!
Object v = json.opt(key);
if (v instanceof JSONNull) {
v = null;
}
return v == null ? null : StringUtils.trimToNull(v.toString());
return Optional.ofNullable(json.opt(key))
.filter(Predicate.not(JSONNull.class::isInstance))
.map(Object::toString)
.map(StringUtils::trimToNull)
.orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@
package org.jenkinsci.plugins.DependencyTrack.model;

import java.io.Serializable;
import lombok.EqualsAndHashCode;
import lombok.Value;

@Value
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Component implements Serializable {

private static final long serialVersionUID = -4825926766668357091L;

@EqualsAndHashCode.Include
private final String uuid;
private final String name;
private final String group;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,47 @@
*/
package org.jenkinsci.plugins.DependencyTrack.model;

import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.Serializable;
import lombok.EqualsAndHashCode;
import lombok.Value;

@Value
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Finding implements Serializable {

private static final long serialVersionUID = 5309487290800777874L;

private final Component component;
private final Vulnerability vulnerability;
private final Analysis analysis;

// includes uuid of project, component and vulnerability delimited by colon
@EqualsAndHashCode.Include
private final String matrix;

/**
* checks whether this finding is an alias of the given other finding
*
* @param other the other finding to check against
* @return {@code true} if the finding {@code other} is for the same
* {@link #component} as this one and this
* {@link #vulnerability} {@link Vulnerability#isAliasOf(org.jenkinsci.plugins.DependencyTrack.model.Vulnerability) is an alias of the other one}
*/
public boolean isAliasOf(@NonNull final Finding other) {
return vulnerability != null && component.equals(other.component) && other.getVulnerability() != null && vulnerability.isAliasOf(other.getVulnerability());
}

/**
* checks whether the given other finding is an alias of this finding
*
* @param alias the possible alias to check
* @return {@code true} if the finding {@code alias} is for the same
* {@link #component} as this one and this
* {@link #vulnerability} {@link Vulnerability#hasAlias(org.jenkinsci.plugins.DependencyTrack.model.Vulnerability) has the others one's vulnerability as an alias}
*/
public boolean hasAlias(@NonNull final Finding alias) {
return vulnerability != null && component.equals(alias.component) && alias.getVulnerability() != null && vulnerability.hasAlias(alias.getVulnerability());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,21 @@
*/
package org.jenkinsci.plugins.DependencyTrack.model;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.Serializable;
import java.util.List;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.experimental.NonFinal;

@Value
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Vulnerability implements Serializable {

private static final long serialVersionUID = 2921629806625084133L;

@EqualsAndHashCode.Include
private final String uuid;
private final String source;
private final String vulnId;
Expand All @@ -35,4 +42,30 @@ public class Vulnerability implements Serializable {
private final Integer cweId;
private final String cweName;

@NonFinal
@Nullable
private transient List<String> aliases = null;

/**
* checks whether this vulnerability is an alias of the given other
* vulnerability
*
* @param other the other vulnerability to check against
* @return {@code true} if the {@code other} vulnerability contains this
* vulnerability as an alias
*/
public boolean isAliasOf(@NonNull final Vulnerability other) {
return other.aliases != null && other.aliases.contains(vulnId);
}

/**
* checks whether the given other vulnerability is an alias of this
* vulnerability
*
* @param alias the possible alias to check
* @return {@code true} if this vulnerability contains the other as an alias
*/
public boolean hasAlias(@NonNull final Vulnerability alias) {
return aliases != null && aliases.contains(alias.vulnId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.assertj.core.util.Files;
import org.jenkinsci.plugins.DependencyTrack.model.Analysis;
import org.jenkinsci.plugins.DependencyTrack.model.Component;
Expand All @@ -38,10 +39,21 @@ void parseTest() {
assertThat(FindingParser.parse("[]")).isEmpty();

File findings = new File("src/test/resources/findings.json");

Component c1 = new Component("uuid-1", "name-1", "group-1", "version-1", "purl-1");
Vulnerability v1 = new Vulnerability("uuid-1", "source-1", "vulnId-1", "title-1", "subtitle-1", "description-1", "recommendation-1", Severity.CRITICAL, 1, 2, "cweName-1");
var aliases1 = List.of("GHSA-abcd-abcd-abcd", "FOO-12345");
var v1 = new Vulnerability("uuid-1", "NVD", "vulnId-1", "title-1", "subtitle-1", "description-1", "recommendation-1", Severity.CRITICAL, 1, 2, "cweName-1", aliases1);
Analysis a1 = new Analysis("state-1", false);
Finding f1 = new Finding(c1, v1, a1, "matrix-1");
assertThat(FindingParser.parse(Files.contentOf(findings, StandardCharsets.UTF_8))).containsExactly(f1);

var c2 = new Component("uuid-2", "name-2", "group-2", "version-2", "purl-2");
var v2 = new Vulnerability("uuid-2", "GITHUB", "GHSA-abcd-abcd-abcd", "title-1", "subtitle-1", "description-1", "recommendation-1", Severity.CRITICAL, 1, 2, "cweName-1", null);
var f2 = new Finding(c2, v2, a1, "matrix-3");

var c3 = new Component("uuid-3", "name-3", "group-3", "version-3", "purl-3");
var v3 = new Vulnerability("uuid-3", "FOO", "FOO-78945", "title-3", "subtitle-3", "description-3", "recommendation-3", Severity.CRITICAL, 1, 2, "cweName-1", null);
var f3 = new Finding(c3, v3, a1, "matrix-4");

assertThat(FindingParser.parse(Files.contentOf(findings, StandardCharsets.UTF_8))).containsExactly(f1, f2, f3);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2024 OWASP.
*
* 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
*
* https://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 org.jenkinsci.plugins.DependencyTrack.model;

import java.util.List;
import org.junit.jupiter.api.Test;

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

class FindingTest {

@Test
void testAliases() {
var a1 = new Analysis("state-1", false);
var c1 = new Component("uuid-1", "name-1", "group-1", "version-1", "purl-1");

var aliases1 = List.of("GHSA-abcd-abcd-abcd", "FOO-12345");
var v1 = new Vulnerability("uuid-1", "NVD", "vulnId-1", "title-1", "subtitle-1", "description-1", "recommendation-1", Severity.CRITICAL, 1, 2, "cweName-1", aliases1);
var f1 = new Finding(c1, v1, a1, "matrix-1");

var aliases2 = List.of("vulnId-1", "FOO-12345");
var v2 = new Vulnerability("uuid-2", "GITHUB", "GHSA-abcd-abcd-abcd", "title-1", "subtitle-1", "description-1", "recommendation-1", Severity.CRITICAL, 1, 2, "cweName-1", aliases2);
var f2 = new Finding(c1, v2, a1, "matrix-2");

assertThat(f2.isAliasOf(f1)).isTrue();
assertThat(f1.isAliasOf(f2)).isTrue();
assertThat(f1.hasAlias(f2)).isTrue();
assertThat(f2.hasAlias(f1)).isTrue();

var v3 = new Vulnerability("uuid-2", "GITHUB", "GHSA-abcd-abcd-abcd", "title-1", "subtitle-1", "description-1", "recommendation-1", Severity.CRITICAL, 1, 2, "cweName-1", null);
var f3 = new Finding(c1, v3, a1, "matrix-2");

var c2 = new Component("uuid-2", "name-1", "group-1", "version-1", "purl-1");
var f4 = new Finding(c2, v1, a1, "matrix-2");
assertThat(f2.isAliasOf(f3)).isFalse();
assertThat(f3.isAliasOf(f2)).isFalse();
assertThat(f1.hasAlias(f4)).isFalse();
assertThat(f3.hasAlias(f1)).isFalse();
}
}
Loading

0 comments on commit 549596b

Please sign in to comment.