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..e076e5db57 --- /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 java.util.List; +import org.dependencytrack.model.Bom; +import org.dependencytrack.model.Project; + +public class BomValidationFailed { + + private Project project; + private String bom; + private List errors; + private 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.errors = errors; + this.format = format; + } + + public Project getProject() { + return project; + } + + public String getBom() { + return bom; + } + + public List getErrors() { + return errors; + } + + 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..76910dd738 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 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, errors, bomFormat))); + } + } diff --git a/src/main/java/org/dependencytrack/resources/v1/VexResource.java b/src/main/java/org/dependencytrack/resources/v1/VexResource.java index 7f1830706e..6d24c0f501 100644 --- a/src/main/java/org/dependencytrack/resources/v1/VexResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/VexResource.java @@ -261,7 +261,7 @@ private Response process(QueryManager qm, Project project, String encodedVexData return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build(); } final byte[] decoded = Base64.getDecoder().decode(encodedVexData); - BomResource.validate(decoded); + BomResource.validate(decoded, project); final VexUploadEvent vexUploadEvent = new VexUploadEvent(project.getUuid(), decoded); Event.dispatch(vexUploadEvent); return Response.ok(Collections.singletonMap("token", vexUploadEvent.getChainIdentifier())).build(); @@ -282,7 +282,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..1f46b1b45f 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,25 @@ public void testInformWithBomProcessingFailedNotification() { .isThrownBy(() -> publisherInstance.inform(PublishContext.from(notification), notification, createConfig())); } + @Test + public void testInformWithBomValidationFailedNotification() { + 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) + .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); 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/MsTeamsPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java index 7aaf2290d9..7dd578279b 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/MsTeamsPublisherTest.java @@ -114,6 +114,56 @@ 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": "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..bee3a3be2a 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/SendMailPublisherTest.java @@ -177,6 +177,38 @@ 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 + Errors: [$.components[928].externalReferences[1].url: does not match the iri-reference pattern must be a valid RFC 3987 IRI-reference] + + -------------------------------------------------------------------------------- + + 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..b0644e124b 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/SlackPublisherTest.java @@ -128,6 +128,60 @@ 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", + "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(); diff --git a/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java b/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java index 7dc26f53bc..93dbb330c2 100644 --- a/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java +++ b/src/test/java/org/dependencytrack/notification/publisher/WebhookPublisherTest.java @@ -101,6 +101,41 @@ 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" + }, + "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();