diff --git a/src/main/java/org/dependencytrack/model/Component.java b/src/main/java/org/dependencytrack/model/Component.java index 2c3495ac67..90379169a3 100644 --- a/src/main/java/org/dependencytrack/model/Component.java +++ b/src/main/java/org/dependencytrack/model/Component.java @@ -30,7 +30,6 @@ import org.apache.commons.lang3.StringUtils; import org.dependencytrack.model.validation.ValidSpdxExpression; import org.dependencytrack.resources.v1.serializers.CustomPackageURLSerializer; - import javax.jdo.annotations.Column; import javax.jdo.annotations.Element; import javax.jdo.annotations.Extension; @@ -116,6 +115,20 @@ public enum FetchGroup { @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The publisher may only contain printable characters") private String publisher; + @Persistent /**Issue #2373, #2737 */ + @Column(name = "MANUFACTURE", allowsNull = "true") + @Serialized + @Size(max = 255) + @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The manufacture may only contain printable characters") + private OrganizationalEntity manufacture; + + @Persistent /**Issue #2373, #2737 */ + @Column(name = "SUPPLIER", allowsNull = "true") + @Serialized + @Size(max = 255) + @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The supplier may only contain printable characters") + private OrganizationalEntity supplier; + @Persistent @Column(name = "GROUP", jdbcType = "VARCHAR") @Index(name = "COMPONENT_GROUP_IDX") @@ -385,6 +398,22 @@ public void setPublisher(String publisher) { this.publisher = publisher; } + public OrganizationalEntity getSupplier() { /**Issue #2373, #2737 */ + return supplier; + } + + public void setSupplier(OrganizationalEntity supplier) {/**Issue #2373, #2737 */ + this.supplier = supplier; + } + + public OrganizationalEntity getManufacturer() { /**Issue #2373, #2737 */ + return manufacture; + } + + public void setManufacturer(OrganizationalEntity manufacture) {/**Issue #2373, #2737 */ + this.manufacture = manufacture; + } + public String getGroup() { return group; } diff --git a/src/main/java/org/dependencytrack/model/Project.java b/src/main/java/org/dependencytrack/model/Project.java index f52eb7301a..286c1fcf95 100644 --- a/src/main/java/org/dependencytrack/model/Project.java +++ b/src/main/java/org/dependencytrack/model/Project.java @@ -72,6 +72,7 @@ @Persistent(name = "name"), @Persistent(name = "author"), @Persistent(name = "publisher"), + @Persistent(name = "supplier"), @Persistent(name = "group"), @Persistent(name = "name"), @Persistent(name = "description"), @@ -129,6 +130,18 @@ public enum FetchGroup { @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The publisher may only contain printable characters") private String publisher; + @Persistent /**Issue #2373, #2737 */ + @Column(name = "SUPPLIER", allowsNull = "true") + @Size(max = 255) + @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The supplier may only contain printable characters") + private OrganizationalEntity supplier; + + @Persistent /**Issue #2373, #2737 */ + @Column(name = "MANUFACTURE", allowsNull = "true") + @Size(max = 255) + @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The manufacturer may only contain printable characters") + private OrganizationalEntity manufacture; + @Persistent @Column(name = "GROUP", jdbcType = "VARCHAR") @Index(name = "PROJECT_GROUP_IDX") @@ -285,6 +298,23 @@ public void setPublisher(String publisher) { this.publisher = publisher; } + public OrganizationalEntity getSupplier() { + return supplier; + } + + public void setSupplier(OrganizationalEntity supplier) { + this.supplier = supplier; + } + + public OrganizationalEntity getManufacturer() { /**Issue #2373, #2737 */ + return manufacture; + } + + public void setManufacturer(OrganizationalEntity manufacture) {/**Issue #2373, #2737 */ + this.manufacture = manufacture; + } + + public String getGroup() { return group; } 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 7375273718..7958832102 100644 --- a/src/main/java/org/dependencytrack/parser/cyclonedx/util/ModelConverter.java +++ b/src/main/java/org/dependencytrack/parser/cyclonedx/util/ModelConverter.java @@ -98,6 +98,7 @@ public static List convertComponents(final QueryManager qm, final Bom return components; } + /**Convert from CycloneDX to DT */ @SuppressWarnings("deprecation") public static Component convert(final QueryManager qm, final org.cyclonedx.model.Component cycloneDxComponent, final Project project) { Component component = qm.matchSingleIdentity(project, new ComponentIdentity(cycloneDxComponent)); @@ -108,6 +109,32 @@ public static Component convert(final QueryManager qm, final org.cyclonedx.model component.setAuthor(StringUtils.trimToNull(cycloneDxComponent.getAuthor())); component.setBomRef(StringUtils.trimToNull(cycloneDxComponent.getBomRef())); component.setPublisher(StringUtils.trimToNull(cycloneDxComponent.getPublisher())); + + /**Issue #2373, #2737 */ + if (cycloneDxComponent.getSupplier() != null) { + OrganizationalEntity deptrackOrgEntity = new OrganizationalEntity(); + deptrackOrgEntity.setName(cycloneDxComponent.getSupplier().getName()); + deptrackOrgEntity.setUrls(cycloneDxComponent.getSupplier().getUrls().toArray(new String[0])); + // to do convert contacts + // deptrackOrgEntity.setContacts(cycloneDxComponent.getSupplier().getContacts()); + + if (cycloneDxComponent.getSupplier().getContacts() != null) { + List contacts = new ArrayList<>(); + for (org.cyclonedx.model.OrganizationalContact organizationalContact: cycloneDxComponent.getSupplier().getContacts()) { + OrganizationalContact contact = new OrganizationalContact(); + contact.setName(organizationalContact.getName()); + contact.setEmail(organizationalContact.getEmail()); + contact.setPhone(organizationalContact.getPhone()); + contacts.add(contact); + } + deptrackOrgEntity.setContacts(contacts); + } else { + deptrackOrgEntity.setContacts(null); + } + component.setSupplier(deptrackOrgEntity); + } /**Issue #2373, #2737 */ + + component.setGroup(StringUtils.trimToNull(cycloneDxComponent.getGroup())); component.setName(StringUtils.trimToNull(cycloneDxComponent.getName())); component.setVersion(StringUtils.trimToNull(cycloneDxComponent.getVersion())); @@ -242,7 +269,8 @@ else if (StringUtils.isNotBlank(cycloneLicense.getName())) } return component; } - + + /**Convert from DT to CycloneDX */ @SuppressWarnings("deprecation") public static org.cyclonedx.model.Component convert(final QueryManager qm, final Component component) { final org.cyclonedx.model.Component cycloneComponent = new org.cyclonedx.model.Component(); @@ -395,6 +423,31 @@ public static org.cyclonedx.model.Metadata createMetadata(final Project project) }); cycloneComponent.setExternalReferences(references); } + /*Issue #2737: Adding Supplier contact functionality */ + if (project.getSupplier() != null) { + org.cyclonedx.model.OrganizationalEntity supplier = new org.cyclonedx.model.OrganizationalEntity(); + supplier.setName(project.getSupplier().getName()); + + if (project.getSupplier().getUrls() != null) { + supplier.setUrls(Arrays.asList(project.getSupplier().getUrls())); + } else { + supplier.setUrls(null); + } + if (project.getSupplier().getContacts() != null) { + List contacts = new ArrayList<>(); + for (OrganizationalContact organizationalContact: project.getSupplier().getContacts()) { + org.cyclonedx.model.OrganizationalContact contact = new org.cyclonedx.model.OrganizationalContact(); + contact.setName(organizationalContact.getName()); + contact.setEmail(organizationalContact.getEmail()); + contact.setPhone(organizationalContact.getPhone()); + contacts.add(contact); + } + supplier.setContacts(contacts); + } + cycloneComponent.setSupplier(supplier); + } else { + cycloneComponent.setSupplier(null); + } metadata.setComponent(cycloneComponent); } return metadata; diff --git a/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java b/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java index 553dfb9b60..38ef7d7fe5 100644 --- a/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java +++ b/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java @@ -34,6 +34,8 @@ import org.dependencytrack.model.Classifier; import org.dependencytrack.model.Component; import org.dependencytrack.model.ConfigPropertyConstants; +import org.dependencytrack.model.OrganizationalEntity; +import org.dependencytrack.model.OrganizationalContact; import org.dependencytrack.model.Project; import org.dependencytrack.model.ServiceComponent; import org.dependencytrack.notification.NotificationConstants; @@ -120,6 +122,29 @@ public void inform(final Event e) { serialNumnber = (cycloneDxBom.getSerialNumber() != null) ? cycloneDxBom.getSerialNumber().replaceFirst("urn:uuid:", "") : null; components = ModelConverter.convertComponents(qm, cycloneDxBom, project); services = ModelConverter.convertServices(qm, cycloneDxBom, project); + /**Issue #2373, #2737 */ + if (cycloneDxBom.getMetadata() != null) { + if (cycloneDxBom.getMetadata().getManufacture() != null) { + OrganizationalEntity manufacturer = new OrganizationalEntity(); + manufacturer.setName(cycloneDxBom.getMetadata().getManufacture().getName()); + manufacturer.setUrls(cycloneDxBom.getMetadata().getManufacture().getUrls().toArray(new String[0])); + if (cycloneDxBom.getMetadata().getManufacture().getContacts() != null){ + List contacts = new ArrayList<>(); + for (org.cyclonedx.model.OrganizationalContact organizationalContact: cycloneDxBom.getMetadata().getManufacture().getContacts()) { + OrganizationalContact contact = new OrganizationalContact(); + contact.setName(organizationalContact.getName()); + contact.setEmail(organizationalContact.getEmail()); + contact.setPhone(organizationalContact.getPhone()); + contacts.add(contact); + } + manufacturer.setContacts(contacts); + } else { + manufacturer.setContacts(null); + } + project.setManufacturer(manufacturer); + } + } /**Issue #2373, #2737 */ + } else { LOGGER.warn("A CycloneDX BOM was uploaded but accepting CycloneDX BOMs is disabled. Aborting"); return; diff --git a/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java b/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java index 5fabfc1f46..f5d11683f0 100644 --- a/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java +++ b/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java @@ -141,6 +141,19 @@ public void informTest() throws Exception { assertThat(components).hasSize(1); final Component component = components.get(0); + + assertThat(component.getSupplier().getName()).isEqualTo("Foo Incorporated"); /*Issue #2373, #2737 - Adding support for Supplier*/ + assertThat(component.getSupplier().getUrls()[0]).isEqualTo("https://foo.bar.com"); + assertThat(component.getSupplier().getContacts().get(0).getEmail()).isEqualTo("foojr@bar.com"); + assertThat(component.getSupplier().getContacts().get(0).getPhone()).isEqualTo("123-456-7890"); + + assertThat(project.getManufacturer().getName()).isEqualTo("Foo Incorporated"); + assertThat(project.getManufacturer().getUrls()[0]).isEqualTo("https://foo.bar.com"); + assertThat(project.getManufacturer().getContacts().get(0).getName()).isEqualTo("Foo Sr."); + assertThat(project.getManufacturer().getContacts().get(0).getEmail()).isEqualTo("foo@bar.com"); + assertThat(project.getManufacturer().getContacts().get(0).getPhone()).isEqualTo("800-123-4567"); + + assertThat(component.getAuthor()).isEqualTo("Sometimes this field is long because it is composed of a list of authors......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................"); assertThat(component.getPublisher()).isEqualTo("Example Incorporated"); assertThat(component.getGroup()).isEqualTo("com.example"); diff --git a/src/test/resources/bom-1.xml b/src/test/resources/bom-1.xml index 632bb199d1..a5a8f0ca1d 100644 --- a/src/test/resources/bom-1.xml +++ b/src/test/resources/bom-1.xml @@ -2,6 +2,15 @@ + + Foo Incorporated + https://foo.bar.com + + Foo Jr. + foojr@bar.com + 123-456-7890 + + DependencyTrack Acme example @@ -19,10 +28,37 @@ + + Foo Incorporated + https://foo.bar.com + + Foo Sr. + foo@bar.com + 800-123-4567 + + + + Foo Incorporated + https://foo.bar.com + + Foo Jr. + foojr@bar.com + 123-456-7890 + + Sometimes this field is long because it is composed of a list of authors...................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... + + Foo Incorporated + https://foo.bar.com + + Foo Jr. + foojr@bar.com + 123-456-7890 + + Example Incorporated com.example xmlutil