diff --git a/src/main/java/org/dependencytrack/event/kafka/processor/VulnerabilityScanResultProcessor.java b/src/main/java/org/dependencytrack/event/kafka/processor/VulnerabilityScanResultProcessor.java index 564257299..6354b529e 100644 --- a/src/main/java/org/dependencytrack/event/kafka/processor/VulnerabilityScanResultProcessor.java +++ b/src/main/java/org/dependencytrack/event/kafka/processor/VulnerabilityScanResultProcessor.java @@ -80,6 +80,8 @@ 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.VulnerabilityUtil.canBeMirrored; import static org.dependencytrack.util.VulnerabilityUtil.isAuthoritativeSource; import static org.dependencytrack.util.VulnerabilityUtil.isMirroringEnabled; @@ -634,6 +636,8 @@ private void maybeSendNotifications(final QueryManager qm, final Component compo .setGroup(GROUP_NEW_VULNERABLE_DEPENDENCY) .setLevel(LEVEL_INFORMATIONAL) .setTimestamp(notificationTimestamp) + .setTitle(generateNotificationTitle(NotificationConstants.Title.NEW_VULNERABLE_DEPENDENCY, subject.getProject())) + .setContent(generateNotificationContent(subject.getComponent(), subject.getVulnerabilitiesList())) .setSubject(Any.pack(subject)) .build()) .ifPresent(notifications::add); @@ -645,6 +649,8 @@ private void maybeSendNotifications(final QueryManager qm, final Component compo .setGroup(GROUP_NEW_VULNERABILITY) .setLevel(LEVEL_INFORMATIONAL) .setTimestamp(notificationTimestamp) + .setTitle(generateNotificationTitle(NotificationConstants.Title.NEW_VULNERABILITY, subject.getProject())) + .setContent(generateNotificationContent(subject.getVulnerability())) .setSubject(Any.pack(subject)) .build()) .forEach(notifications::add); diff --git a/src/main/java/org/dependencytrack/persistence/jdbi/NotificationSubjectDao.java b/src/main/java/org/dependencytrack/persistence/jdbi/NotificationSubjectDao.java index 7816b10f1..f87bb7e6c 100644 --- a/src/main/java/org/dependencytrack/persistence/jdbi/NotificationSubjectDao.java +++ b/src/main/java/org/dependencytrack/persistence/jdbi/NotificationSubjectDao.java @@ -87,7 +87,9 @@ public interface NotificationSubjectDao { ) AS "vulnSeverity", STRING_TO_ARRAY("V"."CWES", ',') AS "vulnCwes", "vulnAliasesJson", - :vulnAnalysisLevel AS "vulnAnalysisLevel" + :vulnAnalysisLevel AS "vulnAnalysisLevel", + '/api/v1/vulnerability/source/' || "V"."SOURCE" || '/vuln/' || "V"."VULNID" || '/projects' AS "affectedProjectsApiUrl", + '/vulnerabilities/' || "V"."SOURCE" || '/' || "V"."VULNID" || '/affectedProjects' AS "affectedProjectsFrontendUrl" FROM "COMPONENT" AS "C" INNER JOIN diff --git a/src/main/java/org/dependencytrack/persistence/jdbi/mapping/NotificationSubjectNewVulnerabilityRowMapper.java b/src/main/java/org/dependencytrack/persistence/jdbi/mapping/NotificationSubjectNewVulnerabilityRowMapper.java index abeffbd82..63b5848f0 100644 --- a/src/main/java/org/dependencytrack/persistence/jdbi/mapping/NotificationSubjectNewVulnerabilityRowMapper.java +++ b/src/main/java/org/dependencytrack/persistence/jdbi/mapping/NotificationSubjectNewVulnerabilityRowMapper.java @@ -1,5 +1,6 @@ package org.dependencytrack.persistence.jdbi.mapping; +import org.dependencytrack.proto.notification.v1.BackReference; import org.dependencytrack.proto.notification.v1.Component; import org.dependencytrack.proto.notification.v1.NewVulnerabilitySubject; import org.dependencytrack.proto.notification.v1.Project; @@ -26,6 +27,12 @@ public NewVulnerabilitySubject map(final ResultSet rs, final StatementContext ct .setVulnerability(vulnRowMapper.map(rs, ctx)); builder.addAffectedProjects(builder.getProject()); maybeSet(rs, "vulnAnalysisLevel", ResultSet::getString, builder::setVulnerabilityAnalysisLevel); + final BackReference.Builder backRefBuilder = BackReference.newBuilder(); + maybeSet(rs, "affectedProjectsApiUrl", ResultSet::getString, backRefBuilder::setApiUri); + maybeSet(rs, "affectedProjectsFrontendUrl", ResultSet::getString, backRefBuilder::setFrontendUri); + if (backRefBuilder.hasApiUri() || backRefBuilder.hasFrontendUri()) { + builder.setAffectedProjectsReference(backRefBuilder); + } return builder.build(); } diff --git a/src/main/java/org/dependencytrack/util/NotificationUtil.java b/src/main/java/org/dependencytrack/util/NotificationUtil.java index 3f5063e00..af2534f6c 100644 --- a/src/main/java/org/dependencytrack/util/NotificationUtil.java +++ b/src/main/java/org/dependencytrack/util/NotificationUtil.java @@ -59,13 +59,13 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -386,12 +386,12 @@ public static void loadDefaultNotificationPublishers(QueryManager qm) throws IOE } } - public static String generateNotificationContent(final Vulnerability vulnerability) { + public static String generateNotificationContent(final org.dependencytrack.proto.notification.v1.Vulnerability vulnerability) { final String content; - if (vulnerability.getDescription() != null) { + if (vulnerability.hasDescription()) { content = vulnerability.getDescription(); } else { - content = (vulnerability.getTitle() != null) ? vulnerability.getVulnId() + ": " + vulnerability.getTitle() : vulnerability.getVulnId(); + content = vulnerability.hasTitle() ? vulnerability.getVulnId() + ": " + vulnerability.getTitle() : vulnerability.getVulnId(); } return content; } @@ -400,7 +400,8 @@ private static String generateNotificationContent(final PolicyViolation policyVi return "A " + policyViolation.getType().name().toLowerCase() + " policy violation occurred"; } - public static String generateNotificationContent(final Component component, final Set vulnerabilities) { + public static String generateNotificationContent(final org.dependencytrack.proto.notification.v1.Component component, + final Collection vulnerabilities) { final String content; if (vulnerabilities.size() == 1) { content = "A dependency was introduced that contains 1 known vulnerability"; @@ -431,6 +432,27 @@ public static String generateNotificationTitle(String messageType, Project proje return messageType; } + public static String generateNotificationTitle(final String messageType, final org.dependencytrack.proto.notification.v1.Project project) { + if (project == null) { + return messageType; + } + + // Emulate Project#toString() + final String projectStr; + if (project.hasPurl()) { + projectStr = project.getPurl(); + } else { + StringBuilder sb = new StringBuilder(); + sb.append(project.getName()); + if (project.hasVersion()) { + sb.append(" : ").append(project.getVersion()); + } + projectStr = sb.toString(); + } + + return messageType + " on Project: [" + projectStr + "]"; + } + private static void sendNotificationToKafka(UUID projectUuid, Notification notification) { new KafkaEventDispatcher().dispatchAsync(projectUuid, notification); } diff --git a/src/test/java/org/dependencytrack/persistence/jdbi/NotificationSubjectDaoTest.java b/src/test/java/org/dependencytrack/persistence/jdbi/NotificationSubjectDaoTest.java index 807eedee4..b7f64ac21 100644 --- a/src/test/java/org/dependencytrack/persistence/jdbi/NotificationSubjectDaoTest.java +++ b/src/test/java/org/dependencytrack/persistence/jdbi/NotificationSubjectDaoTest.java @@ -165,7 +165,11 @@ public void testGetForNewVulnerabilities() { "uuid": "${json-unit.matches:vulnUuid}", "vulnId": "CVE-100" }, - "vulnerabilityAnalysisLevel": "BOM_UPLOAD_ANALYSIS" + "vulnerabilityAnalysisLevel": "BOM_UPLOAD_ANALYSIS", + "affectedProjectsReference": { + "apiUri": "/api/v1/vulnerability/source/NVD/vuln/CVE-100/projects", + "frontendUri": "/vulnerabilities/NVD/CVE-100/affectedProjects" + } } """)); } @@ -242,7 +246,11 @@ public void testGetForNewVulnerabilityWithAnalysisRatingOverwrite() throws Excep "uuid": "${json-unit.matches:projectUuid}", "name": "projectName" } - ] + ], + "affectedProjectsReference": { + "apiUri": "/api/v1/vulnerability/source/NVD/vuln/CVE-100/projects", + "frontendUri": "/vulnerabilities/NVD/CVE-100/affectedProjects" + } } """); }