From 43903e724aecca610a7105f733eba94138ea3da6 Mon Sep 17 00:00:00 2001 From: Aravind Parappil Date: Sat, 1 Jun 2024 20:48:05 -0400 Subject: [PATCH 1/6] Add Notification For BOM_VALIDATION_FAILED If uploaded BOM is invalid, dispatches a notification with InvalidBomProblemDetails before throwing the respective exception Signed-off-by: Aravind Parappil --- docs/_docs/integrations/notifications.md | 1 + .../notification/NotificationConstants.java | 1 + .../notification/NotificationGroup.java | 1 + .../notification/NotificationRouter.java | 4 ++ .../notification/publisher/Publisher.java | 4 ++ .../notification/vo/BomValidationFailed.java | 55 +++++++++++++++++ .../resources/v1/BomResource.java | 30 ++++++++- .../resources/v1/VexResource.java | 4 +- .../util/NotificationUtil.java | 42 +++++++++++++ .../notification/publisher/email.peb | 5 ++ .../notification/publisher/msteams.peb | 31 ++++++++++ .../notification/publisher/slack.peb | 52 ++++++++++++++++ .../notification/NotificationRouterTest.java | 26 ++++++++ .../publisher/AbstractPublisherTest.java | 28 +++++++++ .../publisher/MsTeamsPublisherTest.java | 54 ++++++++++++++++ .../publisher/SendMailPublisherTest.java | 36 +++++++++++ .../publisher/SlackPublisherTest.java | 61 +++++++++++++++++++ 17 files changed, 430 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/dependencytrack/notification/vo/BomValidationFailed.java diff --git a/docs/_docs/integrations/notifications.md b/docs/_docs/integrations/notifications.md index 5595de405a..8cdc82a919 100644 --- a/docs/_docs/integrations/notifications.md +++ b/docs/_docs/integrations/notifications.md @@ -53,6 +53,7 @@ multiple levels, while others can only ever have a single level. | PORTFOLIO | BOM_CONSUMED | INFORMATIONAL | Notifications generated whenever a supported BOM is ingested and identified | | PORTFOLIO | BOM_PROCESSED | INFORMATIONAL | Notifications generated after a supported BOM is ingested, identified, and successfully processed | | PORTFOLIO | BOM_PROCESSING_FAILED | ERROR | Notifications generated whenever a BOM upload process fails | +| PORTFOLIO | BOM_VALIDATION_FAILED | ERROR | Notifications generated whenever an invalid BOM is uploaded | | PORTFOLIO | POLICY_VIOLATION | INFORMATIONAL | Notifications generated whenever a policy violation is identified | ## Configuring Publishers diff --git a/src/main/java/org/dependencytrack/notification/NotificationConstants.java b/src/main/java/org/dependencytrack/notification/NotificationConstants.java index 6897136f7c..83e78c5339 100644 --- a/src/main/java/org/dependencytrack/notification/NotificationConstants.java +++ b/src/main/java/org/dependencytrack/notification/NotificationConstants.java @@ -58,6 +58,7 @@ public static class Title { public static final String BOM_CONSUMED = "Bill of Materials Consumed"; public static final String BOM_PROCESSED = "Bill of Materials Processed"; public static final String BOM_PROCESSING_FAILED = "Bill of Materials Processing Failed"; + public static final String BOM_VALIDATION_FAILED = "Bill of Materials Validation Failed"; public static final String VEX_CONSUMED = "Vulnerability Exploitability Exchange (VEX) Consumed"; public static final String VEX_PROCESSED = "Vulnerability Exploitability Exchange (VEX) Processed"; public static final String PROJECT_CREATED = "Project Added"; diff --git a/src/main/java/org/dependencytrack/notification/NotificationGroup.java b/src/main/java/org/dependencytrack/notification/NotificationGroup.java index 67db8803fb..64596886c5 100644 --- a/src/main/java/org/dependencytrack/notification/NotificationGroup.java +++ b/src/main/java/org/dependencytrack/notification/NotificationGroup.java @@ -40,6 +40,7 @@ public enum NotificationGroup { BOM_CONSUMED, BOM_PROCESSED, BOM_PROCESSING_FAILED, + BOM_VALIDATION_FAILED, VEX_CONSUMED, VEX_PROCESSED, POLICY_VIOLATION, diff --git a/src/main/java/org/dependencytrack/notification/NotificationRouter.java b/src/main/java/org/dependencytrack/notification/NotificationRouter.java index 6fddbad41a..a5b6cf7aa5 100644 --- a/src/main/java/org/dependencytrack/notification/NotificationRouter.java +++ b/src/main/java/org/dependencytrack/notification/NotificationRouter.java @@ -32,6 +32,7 @@ import org.dependencytrack.notification.vo.AnalysisDecisionChange; import org.dependencytrack.notification.vo.BomConsumedOrProcessed; import org.dependencytrack.notification.vo.BomProcessingFailed; +import org.dependencytrack.notification.vo.BomValidationFailed; import org.dependencytrack.notification.vo.NewVulnerabilityIdentified; import org.dependencytrack.notification.vo.NewVulnerableDependency; import org.dependencytrack.notification.vo.PolicyViolationIdentified; @@ -186,6 +187,9 @@ List resolveRules(final PublishContext ctx, final Notification } else if (NotificationScope.PORTFOLIO.name().equals(notification.getScope()) && notification.getSubject() instanceof final BomProcessingFailed subject) { limitToProject(ctx, rules, result, notification, subject.getProject()); + } else if (NotificationScope.PORTFOLIO.name().equals(notification.getScope()) + && notification.getSubject() instanceof final BomValidationFailed subject) { + limitToProject(ctx, rules, result, notification, subject.getProject()); } else if (NotificationScope.PORTFOLIO.name().equals(notification.getScope()) && notification.getSubject() instanceof final VexConsumedOrProcessed subject) { limitToProject(ctx, rules, result, notification, subject.getProject()); diff --git a/src/main/java/org/dependencytrack/notification/publisher/Publisher.java b/src/main/java/org/dependencytrack/notification/publisher/Publisher.java index 3793a3830b..50a319d92f 100644 --- a/src/main/java/org/dependencytrack/notification/publisher/Publisher.java +++ b/src/main/java/org/dependencytrack/notification/publisher/Publisher.java @@ -30,6 +30,7 @@ import org.dependencytrack.notification.vo.AnalysisDecisionChange; import org.dependencytrack.notification.vo.BomConsumedOrProcessed; import org.dependencytrack.notification.vo.BomProcessingFailed; +import org.dependencytrack.notification.vo.BomValidationFailed; import org.dependencytrack.notification.vo.NewVulnerabilityIdentified; import org.dependencytrack.notification.vo.NewVulnerableDependency; import org.dependencytrack.notification.vo.PolicyViolationIdentified; @@ -120,6 +121,9 @@ default String prepareTemplate(final Notification notification, final PebbleTemp } else if (notification.getSubject() instanceof final BomProcessingFailed subject) { context.put("subject", subject); context.put("subjectJson", NotificationUtil.toJson(subject)); + } else if (notification.getSubject() instanceof final BomValidationFailed subject) { + context.put("subject", subject); + context.put("subjectJson", NotificationUtil.toJson(subject)); } else if (notification.getSubject() instanceof final VexConsumedOrProcessed subject) { context.put("subject", subject); context.put("subjectJson", NotificationUtil.toJson(subject)); diff --git a/src/main/java/org/dependencytrack/notification/vo/BomValidationFailed.java b/src/main/java/org/dependencytrack/notification/vo/BomValidationFailed.java new file mode 100644 index 0000000000..0fb8cf20ac --- /dev/null +++ b/src/main/java/org/dependencytrack/notification/vo/BomValidationFailed.java @@ -0,0 +1,55 @@ +/* + * 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.notification.vo; + +import org.dependencytrack.model.Bom; +import org.dependencytrack.model.Project; +import org.dependencytrack.resources.v1.problems.InvalidBomProblemDetails; + +public class BomValidationFailed { + + private Project project; + private String bom; + private InvalidBomProblemDetails problemDetails; + private Bom.Format format; + + public BomValidationFailed(final Project project, final String bom, final InvalidBomProblemDetails problemDetails, final Bom.Format format) { + this.project = project; + this.bom = bom; + this.problemDetails = problemDetails; + this.format = format; + } + + public Project getProject() { + return project; + } + + public String getBom() { + return bom; + } + + public InvalidBomProblemDetails getProblemDetails() { + return problemDetails; + } + + public Bom.Format getFormat() { + return format; + } + +} diff --git a/src/main/java/org/dependencytrack/resources/v1/BomResource.java b/src/main/java/org/dependencytrack/resources/v1/BomResource.java index f429cf6e12..34df79d7ca 100644 --- a/src/main/java/org/dependencytrack/resources/v1/BomResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/BomResource.java @@ -20,6 +20,8 @@ import alpine.common.logging.Logger; import alpine.event.framework.Event; +import alpine.notification.Notification; +import alpine.notification.NotificationLevel; import alpine.server.auth.PermissionRequired; import alpine.server.resources.AlpineResource; import io.swagger.v3.oas.annotations.Operation; @@ -38,10 +40,16 @@ import org.cyclonedx.exception.GeneratorException; import org.dependencytrack.auth.Permissions; import org.dependencytrack.event.BomUploadEvent; +import org.dependencytrack.model.Bom; +import org.dependencytrack.model.Bom.Format; import org.dependencytrack.model.Component; import org.dependencytrack.model.ConfigPropertyConstants; import org.dependencytrack.model.Project; import org.dependencytrack.model.validation.ValidUuid; +import org.dependencytrack.notification.NotificationConstants.Title; +import org.dependencytrack.notification.NotificationGroup; +import org.dependencytrack.notification.NotificationScope; +import org.dependencytrack.notification.vo.BomValidationFailed; import org.dependencytrack.parser.cyclonedx.CycloneDXExporter; import org.dependencytrack.parser.cyclonedx.CycloneDxValidator; import org.dependencytrack.parser.cyclonedx.InvalidBomException; @@ -461,7 +469,7 @@ private Response process(QueryManager qm, Project project, String encodedBomData final byte[] decoded = Base64.getDecoder().decode(encodedBomData); try (final ByteArrayInputStream bain = new ByteArrayInputStream(decoded)) { final byte[] content = IOUtils.toByteArray(BOMInputStream.builder().setInputStream(bain).get()); - validate(content); + validate(content, project); final BomUploadEvent bomUploadEvent = new BomUploadEvent(qm.getPersistenceManager().detachCopy(project), content); Event.dispatch(bomUploadEvent); return Response.ok(Collections.singletonMap("token", bomUploadEvent.getChainIdentifier())).build(); @@ -485,7 +493,7 @@ private Response process(QueryManager qm, Project project, List assertThat(resolvedRule.getName()).isEqualTo("Test Rule")); } + @Test + public void testBomValidationFailedLimitedToProject() { + final Project projectA = qm.createProject("Project A", null, "1.0", null, null, null, true, false); + final Project projectB = qm.createProject("Project B", null, "1.0", null, null, null, true, false); + + final NotificationPublisher publisher = createSlackPublisher(); + + final NotificationRule rule = qm.createNotificationRule("Test Rule", NotificationScope.PORTFOLIO, NotificationLevel.INFORMATIONAL, publisher); + rule.setNotifyOn(Set.of(NotificationGroup.BOM_VALIDATION_FAILED)); + rule.setProjects(List.of(projectA)); + + final var notification = new Notification(); + notification.setScope(NotificationScope.PORTFOLIO.name()); + notification.setGroup(NotificationGroup.BOM_VALIDATION_FAILED.name()); + notification.setLevel(NotificationLevel.ERROR); + notification.setSubject(new BomValidationFailed(projectB, "", null, Bom.Format.CYCLONEDX)); + + final var router = new NotificationRouter(); + assertThat(router.resolveRules(PublishContext.from(notification), notification)).isEmpty(); + + notification.setSubject(new BomValidationFailed(projectA, "", null, Bom.Format.CYCLONEDX)); + assertThat(router.resolveRules(PublishContext.from(notification), notification)) + .satisfiesExactly(resolvedRule -> assertThat(resolvedRule.getName()).isEqualTo("Test Rule")); + } + @Test public void testVexConsumedOrProcessedLimitedToProject() { final Project projectA = qm.createProject("Project A", null, "1.0", null, null, null, true, false); diff --git a/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java index 5f638f5383..0efd766e26 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java @@ -37,7 +37,9 @@ import org.dependencytrack.notification.vo.AnalysisDecisionChange; import org.dependencytrack.notification.vo.BomConsumedOrProcessed; import org.dependencytrack.notification.vo.BomProcessingFailed; +import org.dependencytrack.notification.vo.BomValidationFailed; import org.dependencytrack.notification.vo.NewVulnerabilityIdentified; +import org.dependencytrack.resources.v1.problems.InvalidBomProblemDetails; import org.dependencytrack.notification.vo.NewVulnerableDependency; import org.junit.Test; @@ -98,6 +100,23 @@ public void testInformWithBomProcessingFailedNotification() { .isThrownBy(() -> publisherInstance.inform(PublishContext.from(notification), notification, createConfig())); } + @Test + public void testInformWithBomValidationFailedNotification() { + final var subject = new BomValidationFailed(createProject(), "bomContent", createInvalidBomProblemDetails(), Bom.Format.CYCLONEDX); + + final var notification = new Notification() + .scope(NotificationScope.PORTFOLIO) + .group(NotificationGroup.BOM_VALIDATION_FAILED) + .title(NotificationConstants.Title.BOM_VALIDATION_FAILED) + .content("An error occurred during BOM Validation") + .level(NotificationLevel.ERROR) + .timestamp(LocalDateTime.ofEpochSecond(1234, 888, ZoneOffset.UTC)) + .subject(subject); + + assertThatNoException() + .isThrownBy(() -> publisherInstance.inform(PublishContext.from(notification), notification, createConfig())); + } + @Test // https://github.com/DependencyTrack/dependency-track/issues/3197 public void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() { final var subject = new BomProcessingFailed(createProject(), "bomContent", "cause", Bom.Format.CYCLONEDX, null); @@ -219,6 +238,15 @@ private static Project createProject() { return project; } + private static InvalidBomProblemDetails createInvalidBomProblemDetails() { + final var invalidBomProblemDetails = new InvalidBomProblemDetails(); + invalidBomProblemDetails.setTitle("The uploaded BOM is invalid"); + invalidBomProblemDetails.setDetail("Schema validation failed"); + invalidBomProblemDetails.setStatus(400); + invalidBomProblemDetails.setErrors(List.of("$.components[928].externalReferences[1].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference")); + return invalidBomProblemDetails; + } + private static Vulnerability createVulnerability() { final var alias = new org.dependencytrack.model.VulnerabilityAlias(); alias.setInternalId("INT-001"); diff --git a/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java index 7aaf2290d9..7f71322bc6 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java @@ -114,6 +114,60 @@ public void testInformWithBomProcessingFailedNotification() { """))); } + @Override + public void testInformWithBomValidationFailedNotification() { + super.testInformWithBomValidationFailedNotification(); + + verify(postRequestedFor(anyUrl()) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(""" + { + "@type": "MessageCard", + "@context": "http://schema.org/extensions", + "summary": "Bill of Materials Validation Failed", + "title": "Bill of Materials Validation Failed", + "sections": [ + { + "activityTitle": "Dependency-Track", + "activitySubtitle": "1970-01-01T00:20:34.000000888", + "activityImage": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", + "facts": [ + { + "name": "Level", + "value": "ERROR" + }, + { + "name": "Scope", + "value": "PORTFOLIO" + }, + { + "name": "Group", + "value": "BOM_VALIDATION_FAILED" + }, + { + "name": "Project", + "value": "pkg:maven/org.acme/projectName@projectVersion" + }, + { + "name": "Project URL", + "value": "https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95" + }, + { + "name": "Summary", + "value": "The uploaded BOM is invalid - Schema validation failed" + }, + { + "name": "Errors", + "value": "[$.components[928].externalReferences[1].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference]" + } + ], + "text": "An error occurred during BOM Validation" + } + ] + } + """))); + } + @Override public void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() { super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); diff --git a/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java index eac32c4ef6..5808ee8734 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java @@ -177,6 +177,42 @@ public void testInformWithBomProcessingFailedNotification() { }); } + @Override + public void testInformWithBomValidationFailedNotification() { + super.testInformWithBomValidationFailedNotification(); + + assertThat(greenMail.getReceivedMessages()).satisfiesExactly(message -> { + assertThat(message.getSubject()).isEqualTo("[Dependency-Track] Bill of Materials Validation Failed"); + assertThat(message.getContent()).isInstanceOf(MimeMultipart.class); + final MimeMultipart content = (MimeMultipart) message.getContent(); + assertThat(content.getCount()).isEqualTo(1); + assertThat(content.getBodyPart(0)).isInstanceOf(MimeBodyPart.class); + assertThat((String) content.getBodyPart(0).getContent()).isEqualToIgnoringNewLines(""" + Bill of Materials Validation Failed + + -------------------------------------------------------------------------------- + + Project: projectName + Version: projectVersion + Description: projectDescription + Project URL: /projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95 + + -------------------------------------------------------------------------------- + + Cause: + + + -------------------------------------------------------------------------------- + + An error occurred during BOM Validation + + -------------------------------------------------------------------------------- + + 1970-01-01T00:20:34.000000888 + """); + }); + } + @Override public void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() { super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); diff --git a/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java index 1e47eba589..003ee835e4 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java @@ -128,6 +128,67 @@ public void testInformWithBomProcessingFailedNotification() { """))); } + @Override + public void testInformWithBomValidationFailedNotification() { + super.testInformWithBomValidationFailedNotification(); + + verify(postRequestedFor(anyUrl()) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(""" + { + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": "BOM_VALIDATION_FAILED | pkg:maven/org.acme/projectName@projectVersion" + } + }, + { + "type": "context", + "elements": [ + { + "text": "*ERROR* | *PORTFOLIO*", + "type": "mrkdwn" + } + ] + }, + { + "type": "divider" + }, + { + "type": "section", + "text": { + "text": "Bill of Materials Validation Failed", + "type": "plain_text" + } + }, + { + "type": "section", + "text": { + "text": "An error occurred during BOM Validation | The uploaded BOM is invalid", + "type": "plain_text" + } + }, + { + "type" : "section", + "text" : { + "text" : "Schema validation failed", + "type" : "plain_text" + } + }, + { + "type" : "section", + "text" : { + "text" : "[$.components[928].externalReferences[1].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference]", + "type" : "plain_text" + } + } + ] + } + """))); + } + @Override public void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() { super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); From e4f5fee413b4dc913c09aab5c6a6f0f7459c88b2 Mon Sep 17 00:00:00 2001 From: Aravind Parappil Date: Sat, 1 Jun 2024 21:18:36 -0400 Subject: [PATCH 2/6] Add Tests For Jira, MatterMost and Webhook Signed-off-by: Aravind Parappil --- .../publisher/JiraPublisherTest.java | 23 +++++++++++ .../publisher/MattermostPublisherTest.java | 15 +++++++ .../publisher/WebhookPublisherTest.java | 40 +++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java index 4c4b4a302d..57e71e253d 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/JiraPublisherTest.java @@ -113,6 +113,29 @@ public void testInformWithBomProcessingFailedNotification() { """))); } + @Override + public void testInformWithBomValidationFailedNotification() { + super.testInformWithBomValidationFailedNotification(); + + verify(postRequestedFor(urlPathEqualTo("/rest/api/2/issue")) + .withHeader("Authorization", equalTo("Basic amlyYVVzZXI6amlyYVBhc3N3b3Jk")) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(""" + { + "fields" : { + "project" : { + "key" : "PROJECT" + }, + "issuetype" : { + "name" : "Task" + }, + "summary" : "[Dependency-Track] [BOM_VALIDATION_FAILED] Bill of Materials Validation Failed", + "description" : "An error occurred during BOM Validation\\n\\\\\\\\\\n\\\\\\\\\\n*Level*\\nERROR\\n\\n" + } + } + """))); + } + @Override public void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() { super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); diff --git a/src/test/java/org/dependencytrack/notification/publisher/MattermostPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/MattermostPublisherTest.java index 927d406b02..9b7249318f 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/MattermostPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/MattermostPublisherTest.java @@ -60,6 +60,21 @@ public void testInformWithBomProcessingFailedNotification() { """))); } + @Override + public void testInformWithBomValidationFailedNotification() { + super.testInformWithBomValidationFailedNotification(); + + verify(postRequestedFor(anyUrl()) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(""" + { + "username": "Dependency Track", + "icon_url": "https://raw.githubusercontent.com/DependencyTrack/branding/master/dt-logo-symbol-blue-background.png", + "text": "#### Bill of Materials Validation Failed\\nAn error occurred during BOM Validation\\n" + } + """))); + } + @Override public void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() { super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); diff --git a/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java index 7dc26f53bc..66168eeb5f 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java @@ -101,6 +101,46 @@ public void testInformWithBomProcessingFailedNotification() { """))); } + @Override + public void testInformWithBomValidationFailedNotification() { + super.testInformWithBomValidationFailedNotification(); + + verify(postRequestedFor(anyUrl()) + .withHeader("Content-Type", equalTo("application/json")) + .withRequestBody(equalToJson(""" + { + "notification" : { + "level" : "ERROR", + "scope" : "PORTFOLIO", + "group" : "BOM_VALIDATION_FAILED", + "timestamp" : "1970-01-01T00:20:34.000000888", + "title" : "Bill of Materials Validation Failed", + "content" : "An error occurred during BOM Validation", + "subject" : { + "project" : { + "uuid" : "c9c9539a-e381-4b36-ac52-6a7ab83b2c95", + "name" : "projectName", + "version" : "projectVersion", + "description" : "projectDescription", + "purl" : "pkg:maven/org.acme/projectName@projectVersion", + "tags" : "tag1,tag2" + }, + "bom" : { + "content" : "bomContent", + "format" : "CycloneDX" + }, + "problemDetails" : { + "status" : "400", + "title" : "The uploaded BOM is invalid", + "detail" : "Schema validation failed", + "errors" : "$.components[928].externalReferences[1].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference" + } + } + } + } + """))); + } + @Override public void testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject() { super.testInformWithBomProcessingFailedNotificationAndNoSpecVersionInSubject(); From 12bc76d080b17149c84bef0b6d07907daf107921 Mon Sep 17 00:00:00 2001 From: Aravind Parappil Date: Sat, 1 Jun 2024 21:36:40 -0400 Subject: [PATCH 3/6] Fix Mail Publisher Test Signed-off-by: Aravind Parappil --- .../resources/templates/notification/publisher/email.peb | 6 +++--- .../notification/publisher/SendMailPublisherTest.java | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/resources/templates/notification/publisher/email.peb b/src/main/resources/templates/notification/publisher/email.peb index 53d956e5ae..a0f3b80046 100644 --- a/src/main/resources/templates/notification/publisher/email.peb +++ b/src/main/resources/templates/notification/publisher/email.peb @@ -70,17 +70,17 @@ Project: {{ subject.project.name }} Version: {{ subject.project.version }} Description: {{ subject.project.description }} Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} -{% elseif notification.group == "BOM_PROCESSED" %} +{% elseif notification.group == "BOM_VALIDATION_FAILED" %} Project: {{ subject.project.name }} Version: {{ subject.project.version }} Description: {{ subject.project.description }} Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} -{% elseif notification.group == "BOM_PROCESSING_FAILED" %} +{% elseif notification.group == "BOM_PROCESSED" %} Project: {{ subject.project.name }} Version: {{ subject.project.version }} Description: {{ subject.project.description }} Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} -{% elseif notification.group == "BOM_VALIDATION_FAILED" %} +{% elseif notification.group == "BOM_PROCESSING_FAILED" %} Project: {{ subject.project.name }} Version: {{ subject.project.version }} Description: {{ subject.project.description }} diff --git a/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java index 5808ee8734..39538a5b94 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java @@ -197,11 +197,6 @@ public void testInformWithBomValidationFailedNotification() { Description: projectDescription Project URL: /projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95 - -------------------------------------------------------------------------------- - - Cause: - - -------------------------------------------------------------------------------- An error occurred during BOM Validation From 543cd8c483160698803f007d952a7f6629bd24e0 Mon Sep 17 00:00:00 2001 From: Aravind Parappil Date: Sun, 2 Jun 2024 11:02:43 -0400 Subject: [PATCH 4/6] Replace InvalidBomProblemDetails with Errors Instead of adding the whole problemDetails object, just storing the errors now Signed-off-by: Aravind Parappil --- .../notification/vo/BomValidationFailed.java | 12 ++++---- .../resources/v1/BomResource.java | 7 ++--- .../util/NotificationUtil.java | 30 ++++--------------- .../notification/publisher/email.peb | 1 + .../notification/publisher/msteams.peb | 6 +--- .../notification/publisher/slack.peb | 11 ++----- .../publisher/AbstractPublisherTest.java | 13 ++------ .../publisher/MsTeamsPublisherTest.java | 4 --- .../publisher/SendMailPublisherTest.java | 1 + .../publisher/SlackPublisherTest.java | 9 +----- 10 files changed, 23 insertions(+), 71 deletions(-) diff --git a/src/main/java/org/dependencytrack/notification/vo/BomValidationFailed.java b/src/main/java/org/dependencytrack/notification/vo/BomValidationFailed.java index 0fb8cf20ac..e076e5db57 100644 --- a/src/main/java/org/dependencytrack/notification/vo/BomValidationFailed.java +++ b/src/main/java/org/dependencytrack/notification/vo/BomValidationFailed.java @@ -18,21 +18,21 @@ */ package org.dependencytrack.notification.vo; +import java.util.List; import org.dependencytrack.model.Bom; import org.dependencytrack.model.Project; -import org.dependencytrack.resources.v1.problems.InvalidBomProblemDetails; public class BomValidationFailed { private Project project; private String bom; - private InvalidBomProblemDetails problemDetails; + private List errors; private Bom.Format format; - public BomValidationFailed(final Project project, final String bom, final InvalidBomProblemDetails problemDetails, final Bom.Format format) { + public BomValidationFailed(final Project project, final String bom, final List errors, final Bom.Format format) { this.project = project; this.bom = bom; - this.problemDetails = problemDetails; + this.errors = errors; this.format = format; } @@ -44,8 +44,8 @@ public String getBom() { return bom; } - public InvalidBomProblemDetails getProblemDetails() { - return problemDetails; + public List getErrors() { + return errors; } public Bom.Format getFormat() { diff --git a/src/main/java/org/dependencytrack/resources/v1/BomResource.java b/src/main/java/org/dependencytrack/resources/v1/BomResource.java index 34df79d7ca..76910dd738 100644 --- a/src/main/java/org/dependencytrack/resources/v1/BomResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/BomResource.java @@ -539,7 +539,7 @@ static void validate(final byte[] bomBytes, final Project project) { final var bomEncoded = Base64.getEncoder() .encodeToString(bomBytes); - dispatchBomValidationFailedNotification(project, bomEncoded, problemDetails, Format.CYCLONEDX); + dispatchBomValidationFailedNotification(project, bomEncoded, problemDetails.getErrors(), Format.CYCLONEDX); throw new WebApplicationException(response); } catch (RuntimeException e) { @@ -550,15 +550,14 @@ static void validate(final byte[] bomBytes, final Project project) { } - private static void dispatchBomValidationFailedNotification(final Project project, final String bom, - final InvalidBomProblemDetails problemDetails, final Bom.Format bomFormat) { + private static void dispatchBomValidationFailedNotification(final Project project, final String bom, final List errors, final Bom.Format bomFormat) { Notification.dispatch(new Notification() .scope(NotificationScope.PORTFOLIO) .group(NotificationGroup.BOM_VALIDATION_FAILED) .level(NotificationLevel.ERROR) .title(Title.BOM_VALIDATION_FAILED) .content("An error occurred during BOM Validation") - .subject(new BomValidationFailed(project, bom, problemDetails, bomFormat))); + .subject(new BomValidationFailed(project, bom, errors, bomFormat))); } } diff --git a/src/main/java/org/dependencytrack/util/NotificationUtil.java b/src/main/java/org/dependencytrack/util/NotificationUtil.java index ebcf58a092..cb42034657 100644 --- a/src/main/java/org/dependencytrack/util/NotificationUtil.java +++ b/src/main/java/org/dependencytrack/util/NotificationUtil.java @@ -307,28 +307,6 @@ public static JsonObject toJson(final Component component) { return componentBuilder.build(); } - public static JsonObject toJson(final InvalidBomProblemDetails problemDetails) { - final JsonObjectBuilder builder = Json.createObjectBuilder(); - final var errors = problemDetails.getErrors(); - - if (problemDetails.getType() != null) { - builder.add("type", problemDetails.getType().toString()); - } - JsonUtil.add(builder, "status", problemDetails.getStatus().toString()); - JsonUtil.add(builder, "title", problemDetails.getTitle()); - JsonUtil.add(builder, "detail", problemDetails.getDetail()); - - if (errors != null && !errors.isEmpty()) { - final var commaSeparatedErrors = String.join(",", errors); - JsonUtil.add(builder, "errors", commaSeparatedErrors); - } - - if (problemDetails.getInstance() != null) { - JsonUtil.add(builder, "instance", problemDetails.getInstance().toString()); - } - return builder.build(); - } - public static JsonObject toJson(final Vulnerability vulnerability) { final JsonObjectBuilder vulnerabilityBuilder = Json.createObjectBuilder(); vulnerabilityBuilder.add("uuid", vulnerability.getUuid().toString()); @@ -505,7 +483,7 @@ public static JsonObject toJson(final BomProcessingFailed vo) { } public static JsonObject toJson(final BomValidationFailed vo) { - final JsonObjectBuilder builder = Json.createObjectBuilder(); + final var builder = Json.createObjectBuilder(); if (vo.getProject() != null) { builder.add("project", toJson(vo.getProject())); } @@ -516,8 +494,10 @@ public static JsonObject toJson(final BomValidationFailed vo) { .build() ); } - if (vo.getProblemDetails() != null) { - builder.add("problemDetails", toJson(vo.getProblemDetails())); + final var errors = vo.getErrors(); + if (errors != null && !errors.isEmpty()) { + final var commaSeparatedErrors = String.join(",", errors); + JsonUtil.add(builder, "errors", commaSeparatedErrors); } return builder.build(); } diff --git a/src/main/resources/templates/notification/publisher/email.peb b/src/main/resources/templates/notification/publisher/email.peb index a0f3b80046..3cdbb7320c 100644 --- a/src/main/resources/templates/notification/publisher/email.peb +++ b/src/main/resources/templates/notification/publisher/email.peb @@ -75,6 +75,7 @@ Project: {{ subject.project.name }} Version: {{ subject.project.version }} Description: {{ subject.project.description }} Project URL: {{ baseUrl }}/projects/{{ subject.project.uuid }} +Errors: {{ subject.errors }} {% elseif notification.group == "BOM_PROCESSED" %} Project: {{ subject.project.name }} Version: {{ subject.project.version }} diff --git a/src/main/resources/templates/notification/publisher/msteams.peb b/src/main/resources/templates/notification/publisher/msteams.peb index d0ade59412..c91c8f691b 100644 --- a/src/main/resources/templates/notification/publisher/msteams.peb +++ b/src/main/resources/templates/notification/publisher/msteams.peb @@ -141,13 +141,9 @@ "name": "Project URL", "value": "{{ baseUrl }}/projects/{{ subject.project.uuid | escape(strategy='json') }}" }, - { - "name": "Summary", - "value": "{{ subject.problemDetails.title.toString | escape(strategy='json') }} - {{ subject.problemDetails.detail.toString | escape(strategy='json') }}" - }, { "name": "Errors", - "value": "{{ subject.problemDetails.errors.toString | escape(strategy='json') }}" + "value": "{{ subject.errors.toString | escape(strategy='json') }}" } ], {% else %} diff --git a/src/main/resources/templates/notification/publisher/slack.peb b/src/main/resources/templates/notification/publisher/slack.peb index e09cf65a93..beb0de3eb9 100644 --- a/src/main/resources/templates/notification/publisher/slack.peb +++ b/src/main/resources/templates/notification/publisher/slack.peb @@ -419,21 +419,14 @@ { "type": "section", "text": { - "text": "{{ notification.content | escape(strategy="json") }} | {{ subject.problemDetails.title | escape(strategy="json") }}", - "type": "plain_text" - } - }, - { - "type": "section", - "text": { - "text": "{{ subject.problemDetails.detail.toString | escape(strategy="json") }}", + "text": "{{ notification.content | escape(strategy="json") }}", "type": "plain_text" } }, { "type": "section", "text": { - "text": "{{ subject.problemDetails.errors.toString | escape(strategy="json") }}", + "text": "{{ subject.errors.toString | escape(strategy="json") }}", "type": "plain_text" } } diff --git a/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java index 0efd766e26..1f46b1b45f 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/AbstractPublisherTest.java @@ -102,7 +102,9 @@ public void testInformWithBomProcessingFailedNotification() { @Test public void testInformWithBomValidationFailedNotification() { - final var subject = new BomValidationFailed(createProject(), "bomContent", createInvalidBomProblemDetails(), Bom.Format.CYCLONEDX); + final var errorsSample = List.of( + "$.components[928].externalReferences[1].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference"); + final var subject = new BomValidationFailed(createProject(), "bomContent", errorsSample, Bom.Format.CYCLONEDX); final var notification = new Notification() .scope(NotificationScope.PORTFOLIO) @@ -238,15 +240,6 @@ private static Project createProject() { return project; } - private static InvalidBomProblemDetails createInvalidBomProblemDetails() { - final var invalidBomProblemDetails = new InvalidBomProblemDetails(); - invalidBomProblemDetails.setTitle("The uploaded BOM is invalid"); - invalidBomProblemDetails.setDetail("Schema validation failed"); - invalidBomProblemDetails.setStatus(400); - invalidBomProblemDetails.setErrors(List.of("$.components[928].externalReferences[1].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference")); - return invalidBomProblemDetails; - } - private static Vulnerability createVulnerability() { final var alias = new org.dependencytrack.model.VulnerabilityAlias(); alias.setInternalId("INT-001"); diff --git a/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java index 7f71322bc6..7dd578279b 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java @@ -152,10 +152,6 @@ public void testInformWithBomValidationFailedNotification() { "name": "Project URL", "value": "https://example.com/projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95" }, - { - "name": "Summary", - "value": "The uploaded BOM is invalid - Schema validation failed" - }, { "name": "Errors", "value": "[$.components[928].externalReferences[1].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference]" diff --git a/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java index 39538a5b94..bee3a3be2a 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java @@ -196,6 +196,7 @@ public void testInformWithBomValidationFailedNotification() { Version: projectVersion Description: projectDescription Project URL: /projects/c9c9539a-e381-4b36-ac52-6a7ab83b2c95 + Errors: [$.components[928].externalReferences[1].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference] -------------------------------------------------------------------------------- diff --git a/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java index 003ee835e4..b0644e124b 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java @@ -166,17 +166,10 @@ public void testInformWithBomValidationFailedNotification() { { "type": "section", "text": { - "text": "An error occurred during BOM Validation | The uploaded BOM is invalid", + "text": "An error occurred during BOM Validation", "type": "plain_text" } }, - { - "type" : "section", - "text" : { - "text" : "Schema validation failed", - "type" : "plain_text" - } - }, { "type" : "section", "text" : { From 396d26df0d87406c3947f0ff04bbefcfe61b14a3 Mon Sep 17 00:00:00 2001 From: Aravind Parappil Date: Sun, 2 Jun 2024 11:04:51 -0400 Subject: [PATCH 5/6] Remove unused import Signed-off-by: Aravind Parappil --- src/main/java/org/dependencytrack/util/NotificationUtil.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/dependencytrack/util/NotificationUtil.java b/src/main/java/org/dependencytrack/util/NotificationUtil.java index cb42034657..4eb50a6320 100644 --- a/src/main/java/org/dependencytrack/util/NotificationUtil.java +++ b/src/main/java/org/dependencytrack/util/NotificationUtil.java @@ -70,7 +70,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import org.dependencytrack.resources.v1.problems.InvalidBomProblemDetails; import static java.nio.charset.StandardCharsets.UTF_8; From 451aaa6564007705daee0ff710099b66fa78bc10 Mon Sep 17 00:00:00 2001 From: Aravind Parappil Date: Sun, 2 Jun 2024 11:36:33 -0400 Subject: [PATCH 6/6] Fix WebhookPublisherTest Signed-off-by: Aravind Parappil --- .../notification/publisher/WebhookPublisherTest.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java index 66168eeb5f..93dbb330c2 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java @@ -129,12 +129,7 @@ public void testInformWithBomValidationFailedNotification() { "content" : "bomContent", "format" : "CycloneDX" }, - "problemDetails" : { - "status" : "400", - "title" : "The uploaded BOM is invalid", - "detail" : "Schema validation failed", - "errors" : "$.components[928].externalReferences[1].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference" - } + "errors" : "$.components[928].externalReferences[1].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference" } } }