Skip to content

Commit

Permalink
refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
sahibamittal committed Dec 22, 2023
1 parent 59274ec commit 13f983a
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import java.util.ServiceLoader;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;

Expand All @@ -76,13 +77,13 @@
import static org.dependencytrack.persistence.jdbi.JdbiFactory.jdbi;
import static org.dependencytrack.proto.notification.v1.Group.GROUP_NEW_VULNERABILITY;
import static org.dependencytrack.proto.notification.v1.Group.GROUP_NEW_VULNERABLE_DEPENDENCY;
import static org.dependencytrack.proto.notification.v1.Group.GROUP_PROJECT_AUDIT_CHANGE;
import static org.dependencytrack.proto.notification.v1.Level.LEVEL_INFORMATIONAL;
import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_PORTFOLIO;
import static org.dependencytrack.proto.vulnanalysis.v1.ScanStatus.SCAN_STATUS_FAILED;
import static org.dependencytrack.proto.vulnanalysis.v1.Scanner.SCANNER_INTERNAL;
import static org.dependencytrack.util.NotificationUtil.generateNotificationContent;
import static org.dependencytrack.util.NotificationUtil.generateNotificationTitle;
import static org.dependencytrack.util.NotificationUtil.generateTitle;
import static org.dependencytrack.util.VulnerabilityUtil.canBeMirrored;
import static org.dependencytrack.util.VulnerabilityUtil.isAuthoritativeSource;
import static org.dependencytrack.util.VulnerabilityUtil.isMirroringEnabled;
Expand Down Expand Up @@ -412,6 +413,7 @@ private List<Vulnerability> maybeApplyPolicyAnalyses(QueryManager qm, final Dao
.collect(Collectors.toMap(Analysis::getVulnUuid, Function.identity()));

final var analysesToCreateOrUpdate = new ArrayList<Analysis>();
final var projectAuditChangeNotifications = new ArrayList<org.dependencytrack.proto.notification.v1.Notification>();
final var analysisCommentsByVulnId = new MultivaluedHashMap<Long, AnalysisComment>();

for (final Map.Entry<UUID, VulnerabilityPolicy> vulnUuidAndPolicy : policiesByVulnUuid.entrySet()) {
Expand Down Expand Up @@ -486,6 +488,8 @@ private List<Vulnerability> maybeApplyPolicyAnalyses(QueryManager qm, final Dao
analysisCommentsByVulnId.addAll(policyAnalysis.getVulnId(), commentFactory.getComments());
} else {
boolean shouldUpdate = false;
boolean analysisStateChange = false;
boolean suppressionChange = false;
final var commentFactory = new AnalysisCommentFactory(existingAnalysis.getId(), policy);
if (!Objects.equals(existingAnalysis.getState(), policyAnalysis.getState())) {
commentFactory.createComment("State: %s → %s".formatted(
Expand All @@ -494,6 +498,7 @@ private List<Vulnerability> maybeApplyPolicyAnalyses(QueryManager qm, final Dao

existingAnalysis.setState(policyAnalysis.getState());
shouldUpdate = true;
analysisStateChange = true;
}
if (!Objects.equals(existingAnalysis.getJustification(), policyAnalysis.getJustification())) {
commentFactory.createComment("Justification: %s → %s".formatted(
Expand Down Expand Up @@ -526,6 +531,7 @@ private List<Vulnerability> maybeApplyPolicyAnalyses(QueryManager qm, final Dao

existingAnalysis.setSuppressed(policy.analysis().isSuppress());
shouldUpdate = true;
suppressionChange = true;
}
if (!Objects.equals(existingAnalysis.getSeverity(), policyAnalysis.getSeverity())) {
commentFactory.createComment("Severity: %s → %s".formatted(
Expand Down Expand Up @@ -586,6 +592,7 @@ private List<Vulnerability> maybeApplyPolicyAnalyses(QueryManager qm, final Dao
if (shouldUpdate) {
analysesToCreateOrUpdate.add(existingAnalysis);
analysisCommentsByVulnId.addAll(existingAnalysis.getVulnId(), commentFactory.getComments());
projectAuditChangeNotifications.add(createAndAddProjectAuditChangeNotification(qm, component, vuln, policyAnalysis, analysisStateChange, suppressionChange));
}
}

Expand All @@ -606,17 +613,36 @@ private List<Vulnerability> maybeApplyPolicyAnalyses(QueryManager qm, final Dao
.toList());
}
dao.createAnalysisComments(analysisCommentsByVulnId.values().stream().flatMap(Collection::stream).toList());

// dispatch PROJECT_AUDIT_CHANGE notifications
maybeSendProjectAuditChangeNotification(qm, component, createdAnalyses.stream().map(CreatedAnalysis::vulnId).toList());
}

// dispatch PROJECT_AUDIT_CHANGE notifications
maybeSendProjectAuditChangeNotification(component.projectUuid, projectAuditChangeNotifications);

return vulnById.entrySet().stream()
.filter(entry -> newFindingVulnIds.contains(entry.getKey()))
.map(Map.Entry::getValue)
.toList();
}

private org.dependencytrack.proto.notification.v1.Notification createAndAddProjectAuditChangeNotification(QueryManager qm, Component component, Vulnerability vuln, Analysis policyAnalysis, boolean analysisStateChange, boolean suppressionChange) {
final Timestamp notificationTimestamp = Timestamps.now();
AtomicReference<org.dependencytrack.proto.notification.v1.Notification> notification = null;
jdbi(qm).useExtension(NotificationSubjectDao.class, dao -> dao.getForProjectAuditChange(component.uuid(), vuln.getUuid())
.map(subject -> {
notification.set(org.dependencytrack.proto.notification.v1.Notification.newBuilder()
.setScope(SCOPE_PORTFOLIO)
.setGroup(GROUP_NEW_VULNERABILITY)
.setLevel(LEVEL_INFORMATIONAL)
.setTimestamp(notificationTimestamp)
.setTitle(generateTitle(policyAnalysis.getState(), policyAnalysis.getSuppressed(), analysisStateChange, suppressionChange))
.setContent(generateNotificationContent(policyAnalysis))
.setSubject(Any.pack(subject))
.build());
return null;
}));
return notification.get();
}

/**
* Send {@link Group#GROUP_NEW_VULNERABLE_DEPENDENCY} and {@link Group#GROUP_NEW_VULNERABILITY} notifications
* for a given {@link Component}, <em>if it was found to have at least one non-suppressed vulnerability</em>.
Expand Down Expand Up @@ -669,27 +695,12 @@ private void maybeSendNotifications(final QueryManager qm, final Component compo
}


private void maybeSendProjectAuditChangeNotification(final QueryManager qm, final Component component, List<Long> vulnIds) {
if (vulnIds.isEmpty()) {
private void maybeSendProjectAuditChangeNotification(final UUID projectUuid, final ArrayList<org.dependencytrack.proto.notification.v1.Notification> projectAuditChangeNotifications) {
if (projectAuditChangeNotifications.isEmpty()) {
return;
}
final Timestamp notificationTimestamp = Timestamps.now();
final var notifications = new ArrayList<org.dependencytrack.proto.notification.v1.Notification>();
jdbi(qm).useExtension(NotificationSubjectDao.class, dao -> {
dao.getForProjectAuditChange(component.uuid(), vulnIds).stream()
.map(subject -> org.dependencytrack.proto.notification.v1.Notification.newBuilder()
.setScope(SCOPE_PORTFOLIO)
.setGroup(GROUP_PROJECT_AUDIT_CHANGE)
.setLevel(LEVEL_INFORMATIONAL)
.setTimestamp(notificationTimestamp)
.setTitle(generateNotificationTitle(NotificationConstants.Title.NEW_VULNERABILITY, subject.getProject())) // TODO
.setContent(generateNotificationContent(subject.getVulnerability())) // TODO
.setSubject(Any.pack(subject))
.build())
.forEach(notifications::add);
});
for (final org.dependencytrack.proto.notification.v1.Notification notification : notifications) {
eventDispatcher.dispatchAsync(component.projectUuid().toString(), notification);
for (final var notification : projectAuditChangeNotifications) {
eventDispatcher.dispatchAsync(projectUuid.toString(), notification);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,8 +338,8 @@ LEFT JOIN LATERAL (
OR ("V"."SOURCE" = 'VULNDB' AND "VA"."VULNDB_ID" = "V"."VULNID")
) AS "vulnAliases" ON TRUE
WHERE
"C"."UUID" = (:componentUuid)::TEXT AND "V"."ID" = ANY((:vulnIds)::BIGINT[])
"C"."UUID" = (:componentUuid)::TEXT AND "V"."UUID" = (:vulnUuid)::TEXT
""")
@RegisterRowMapper(NotificationSubjectProjectAuditChangeRowMapper.class)
List<VulnerabilityAnalysisDecisionChangeSubject> getForProjectAuditChange(final UUID componentUuid, final Collection<Long> vulnIds);
Optional<VulnerabilityAnalysisDecisionChangeSubject> getForProjectAuditChange(final UUID componentUuid, final UUID vulnUuid);
}
69 changes: 40 additions & 29 deletions src/main/java/org/dependencytrack/util/NotificationUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.dependencytrack.event.kafka.KafkaEventDispatcher;
import org.dependencytrack.event.kafka.processor.VulnerabilityScanResultProcessor;
import org.dependencytrack.model.Analysis;
import org.dependencytrack.model.AnalysisState;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.Finding;
Expand Down Expand Up @@ -108,35 +110,7 @@ public static void analyzeNotificationCriteria(final QueryManager qm, Analysis a
final NotificationGroup notificationGroup;
notificationGroup = NotificationGroup.PROJECT_AUDIT_CHANGE;

String title = null;
if (analysisStateChange) {
switch (analysis.getAnalysisState()) {
case EXPLOITABLE:
title = NotificationConstants.Title.ANALYSIS_DECISION_EXPLOITABLE;
break;
case IN_TRIAGE:
title = NotificationConstants.Title.ANALYSIS_DECISION_IN_TRIAGE;
break;
case NOT_AFFECTED:
title = NotificationConstants.Title.ANALYSIS_DECISION_NOT_AFFECTED;
break;
case FALSE_POSITIVE:
title = NotificationConstants.Title.ANALYSIS_DECISION_FALSE_POSITIVE;
break;
case NOT_SET:
title = NotificationConstants.Title.ANALYSIS_DECISION_NOT_SET;
break;
case RESOLVED:
title = NotificationConstants.Title.ANALYSIS_DECISION_RESOLVED;
break;
}
} else if (suppressionChange) {
if (analysis.isSuppressed()) {
title = NotificationConstants.Title.ANALYSIS_DECISION_SUPPRESSED;
} else {
title = NotificationConstants.Title.ANALYSIS_DECISION_UNSUPPRESSED;
}
}
String title = generateTitle(analysis.getAnalysisState(), analysis.isSuppressed(), analysisStateChange, suppressionChange);

Project project = analysis.getComponent().getProject();

Expand All @@ -159,6 +133,39 @@ public static void analyzeNotificationCriteria(final QueryManager qm, Analysis a
}
}

public static String generateTitle(AnalysisState analysisState, boolean isSuppressed, boolean analysisStateChange, boolean suppressionChange) {
String title = null;
if (analysisStateChange) {
switch (analysisState) {
case EXPLOITABLE:
title = NotificationConstants.Title.ANALYSIS_DECISION_EXPLOITABLE;
break;
case IN_TRIAGE:
title = NotificationConstants.Title.ANALYSIS_DECISION_IN_TRIAGE;
break;
case NOT_AFFECTED:
title = NotificationConstants.Title.ANALYSIS_DECISION_NOT_AFFECTED;
break;
case FALSE_POSITIVE:
title = NotificationConstants.Title.ANALYSIS_DECISION_FALSE_POSITIVE;
break;
case NOT_SET:
title = NotificationConstants.Title.ANALYSIS_DECISION_NOT_SET;
break;
case RESOLVED:
title = NotificationConstants.Title.ANALYSIS_DECISION_RESOLVED;
break;
}
} else if (suppressionChange) {
if (isSuppressed) {
title = NotificationConstants.Title.ANALYSIS_DECISION_SUPPRESSED;
} else {
title = NotificationConstants.Title.ANALYSIS_DECISION_UNSUPPRESSED;
}
}
return title;
}

public static void analyzeNotificationCriteria(final QueryManager qm, ViolationAnalysis violationAnalysis,
final boolean analysisStateChange, final boolean suppressionChange) {
// TODO: Convert data loading to raw SQL to avoid loading unneeded data and excessive queries.
Expand Down Expand Up @@ -432,6 +439,10 @@ public static String generateNotificationTitle(String messageType, Project proje
return messageType;
}

public static String generateNotificationContent(final VulnerabilityScanResultProcessor.Analysis analysis) {
return "An analysis decision was made to a finding affecting a project";
}

public static String generateNotificationTitle(final String messageType, final org.dependencytrack.proto.notification.v1.Project project) {
if (project == null) {
return messageType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,28 +491,20 @@ public void testGetForProjectAuditChange() {
vulnA.setCwes(List.of(666, 777));
qm.persist(vulnA);

final var vulnB = new Vulnerability();
vulnB.setVulnId("CVE-200");
vulnB.setSource(Vulnerability.Source.NVD);
qm.persist(vulnB);

final var vulnAlias = new VulnerabilityAlias();
vulnAlias.setCveId("CVE-100");
vulnAlias.setGhsaId("GHSA-100");
qm.synchronizeVulnerabilityAlias(vulnAlias);

qm.addVulnerability(vulnA, component, AnalyzerIdentity.INTERNAL_ANALYZER);
qm.addVulnerability(vulnB, component, AnalyzerIdentity.INTERNAL_ANALYZER);

// Suppress vulnB, it should not appear in the query results.
qm.makeAnalysis(component, vulnB, AnalysisState.FALSE_POSITIVE, null, null, null, true);
qm.makeAnalysis(component, vulnA, AnalysisState.NOT_AFFECTED, null, null, null, false);

final List<VulnerabilityAnalysisDecisionChangeSubject> subjects = JdbiFactory.jdbi(qm).withExtension(NotificationSubjectDao.class,
dao -> dao.getForProjectAuditChange(component.getUuid(), List.of(vulnA.getId(), vulnB.getId())));
final Optional<VulnerabilityAnalysisDecisionChangeSubject> optionalSubject = JdbiFactory.jdbi(qm).withExtension(NotificationSubjectDao.class,
dao -> dao.getForProjectAuditChange(component.getUuid(), vulnA.getUuid()));

assertThat(subjects.size()).isEqualTo(2);
assertThat(subjects.get(0)).satisfies(subject ->
assertThat(optionalSubject.get()).satisfies(subject ->
assertThatJson(JsonFormat.printer().print(subject))
.withMatcher("projectUuid", equalTo(project.getUuid().toString()))
.withMatcher("componentUuid", equalTo(component.getUuid().toString()))
Expand Down

0 comments on commit 13f983a

Please sign in to comment.