From 146b9e135fd4c5755ee596ff3bcfdad04ab68f59 Mon Sep 17 00:00:00 2001 From: nscuro Date: Fri, 13 Sep 2024 18:31:32 +0200 Subject: [PATCH] Handle empty component and service names `component.name` and `service.name` are required as per CycloneDX specification, but the schema doesn't sufficiently enforce this requirement (https://github.com/CycloneDX/specification/issues/461). Because DT trims names from the BOM during model conversion, empty or blank names end up becoming `null`. Since the respective database columns have a `NOT NULL` constraint on them, inserting or updating such components will always fail. Usually we would not want to try to "repair" data, but the name being empty appears to be so common that there's no other sensible way for us to deal with it. With this change, empty names will end up being saved as `-` instead, to signal the absence of a proper value. Fixes #2821 Signed-off-by: nscuro --- .../parser/cyclonedx/util/ModelConverter.java | 5 ++- .../tasks/BomUploadProcessingTaskTest.java | 40 ++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/dependencytrack/parser/cyclonedx/util/ModelConverter.java b/src/main/java/org/dependencytrack/parser/cyclonedx/util/ModelConverter.java index 2ea2bb172..fcf75410b 100644 --- a/src/main/java/org/dependencytrack/parser/cyclonedx/util/ModelConverter.java +++ b/src/main/java/org/dependencytrack/parser/cyclonedx/util/ModelConverter.java @@ -75,6 +75,7 @@ import java.util.function.Consumer; import java.util.function.Function; +import static java.util.Objects.requireNonNullElse; import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.trim; import static org.apache.commons.lang3.StringUtils.trimToNull; @@ -167,7 +168,7 @@ public static Component convertComponent(final org.cyclonedx.model.Component cdx component.setSupplier(convert(cdxComponent.getSupplier())); component.setClassifier(convertClassifier(cdxComponent.getType()).orElse(Classifier.LIBRARY)); component.setGroup(trimToNull(cdxComponent.getGroup())); - component.setName(trimToNull(cdxComponent.getName())); + component.setName(requireNonNullElse(trimToNull(cdxComponent.getName()), "-")); component.setVersion(trimToNull(cdxComponent.getVersion())); component.setDescription(trimToNull(cdxComponent.getDescription())); component.setCopyright(trimToNull(cdxComponent.getCopyright())); @@ -325,7 +326,7 @@ public static ServiceComponent convertService(final org.cyclonedx.model.Service final var service = new ServiceComponent(); service.setBomRef(useOrGenerateRandomBomRef(cdxService.getBomRef())); service.setGroup(trimToNull(cdxService.getGroup())); - service.setName(trimToNull(cdxService.getName())); + service.setName(requireNonNullElse(trimToNull(cdxService.getName()), "-")); service.setVersion(trimToNull(cdxService.getVersion())); service.setDescription(trimToNull(cdxService.getDescription())); service.setAuthenticated(cdxService.getAuthenticated()); diff --git a/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java b/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java index 03d128be9..58147107d 100644 --- a/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java +++ b/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java @@ -1065,6 +1065,45 @@ public void informWithLicenseResolutionByIdOrNameTest() { }); } + @Test + public void informWithEmptyComponentAndServiceNameTest() { + final var project = new Project(); + project.setName("acme-license-app"); + qm.persist(project); + + final byte[] bomBytes = """ + { + "bomFormat": "CycloneDX", + "specVersion": "1.4", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b80", + "version": 1, + "components": [ + { + "type": "library", + "name": "" + } + ], + "services": [ + { + "name": "" + } + ] + } + """.getBytes(StandardCharsets.UTF_8); + + final var bomUploadEvent = new BomUploadEvent(qm.detach(Project.class, project.getId()), bomBytes); + new BomUploadProcessingTask().inform(bomUploadEvent); + awaitBomProcessedNotification(bomUploadEvent); + + qm.getPersistenceManager().evictAll(); + assertThat(qm.getAllComponents(project)).satisfiesExactly(component -> { + assertThat(component.getName()).isEqualTo("-"); + }); + assertThat(qm.getAllServiceComponents(project)).satisfiesExactly(service -> { + assertThat(service.getName()).isEqualTo("-"); + }); + } + @Test // https://github.com/DependencyTrack/dependency-track/issues/1905 public void informIssue1905Test() throws Exception { final var project = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); @@ -1331,7 +1370,6 @@ public void informIssue3981Test() { @Test public void informIssue3936Test() throws Exception{ - final Project project = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); List boms = new ArrayList<>(Arrays.asList("/unit/bom-issue3936-authors.json", "/unit/bom-issue3936-author.json", "/unit/bom-issue3936-both.json")); for(String bom : boms){