From ae18536e6d487a8f7faaa595bfb063e1dd9f2664 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 9 Feb 2024 12:39:55 +0000 Subject: [PATCH 1/7] WIP addition of supplier & manufacturer Co-Authored-By: Niklas --- .../org/dependencytrack/model/Classifier.java | 6 +- .../org/dependencytrack/model/Component.java | 15 ++ .../model/OrganizationalContact.java | 14 ++ .../model/OrganizationalEntity.java | 17 +++ .../org/dependencytrack/model/Project.java | 48 ++++++- .../model/ProjectMetadata.java | 101 ++++++++++++++ .../parser/cyclonedx/util/ModelConverter.java | 132 ++++++++++++++---- .../persistence/ComponentQueryManager.java | 2 + .../persistence/ProjectQueryManager.java | 46 ++---- .../persistence/QueryManager.java | 4 - .../converter/AbstractJsonConverter.java | 66 +++++++++ .../OrganizationalContactsJsonConverter.java | 47 +++++++ .../OrganizationalEntityJsonConverter.java | 45 ++++++ .../resources/v1/ProjectResource.java | 2 + .../tasks/BomUploadProcessingTask.java | 22 ++- src/main/resources/META-INF/persistence.xml | 1 + .../resources/migration/changelog-v5.3.0.xml | 20 +++ ...ganizationalContactsJsonConverterTest.java | 79 +++++++++++ ...OrganizationalEntityJsonConverterTest.java | 100 +++++++++++++ .../resources/v1/BomResourceTest.java | 41 ++++++ .../resources/v1/ProjectResourceTest.java | 118 ++++++++++++++-- .../tasks/BomUploadProcessingTaskTest.java | 35 +++++ src/test/resources/unit/bom-1.xml | 48 ++++++- 23 files changed, 924 insertions(+), 85 deletions(-) create mode 100644 src/main/java/org/dependencytrack/model/ProjectMetadata.java create mode 100644 src/main/java/org/dependencytrack/persistence/converter/AbstractJsonConverter.java create mode 100644 src/main/java/org/dependencytrack/persistence/converter/OrganizationalContactsJsonConverter.java create mode 100644 src/main/java/org/dependencytrack/persistence/converter/OrganizationalEntityJsonConverter.java create mode 100644 src/test/java/org/dependencytrack/persistence/converter/OrganizationalContactsJsonConverterTest.java create mode 100644 src/test/java/org/dependencytrack/persistence/converter/OrganizationalEntityJsonConverterTest.java diff --git a/src/main/java/org/dependencytrack/model/Classifier.java b/src/main/java/org/dependencytrack/model/Classifier.java index 3c2085274..321a96eb6 100644 --- a/src/main/java/org/dependencytrack/model/Classifier.java +++ b/src/main/java/org/dependencytrack/model/Classifier.java @@ -32,5 +32,9 @@ public enum Classifier { OPERATING_SYSTEM, DEVICE, FIRMWARE, - FILE + FILE, + PLATFORM, + DEVICE_DRIVER, + MACHINE_LEARNING_MODEL, + DATA } diff --git a/src/main/java/org/dependencytrack/model/Component.java b/src/main/java/org/dependencytrack/model/Component.java index 4011012ef..0c060dbf7 100644 --- a/src/main/java/org/dependencytrack/model/Component.java +++ b/src/main/java/org/dependencytrack/model/Component.java @@ -29,9 +29,11 @@ import com.github.packageurl.PackageURL; import org.apache.commons.lang3.StringUtils; import org.dependencytrack.model.validation.ValidSpdxExpression; +import org.dependencytrack.persistence.converter.OrganizationalEntityJsonConverter; import org.dependencytrack.resources.v1.serializers.CustomPackageURLSerializer; import javax.jdo.annotations.Column; +import javax.jdo.annotations.Convert; import javax.jdo.annotations.Element; import javax.jdo.annotations.Extension; import javax.jdo.annotations.FetchGroup; @@ -120,6 +122,11 @@ public enum FetchGroup { @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The publisher may only contain printable characters") private String publisher; + @Persistent(defaultFetchGroup = "true") + @Convert(OrganizationalEntityJsonConverter.class) + @Column(name = "SUPPLIER", jdbcType = "CLOB", allowsNull = "true") + private OrganizationalEntity supplier; + @Persistent @Column(name = "GROUP", jdbcType = "VARCHAR") @Index(name = "COMPONENT_GROUP_IDX") @@ -393,6 +400,14 @@ public void setPublisher(String publisher) { this.publisher = publisher; } + public OrganizationalEntity getSupplier() { + return supplier; + } + + public void setSupplier(OrganizationalEntity supplier) { + this.supplier = supplier; + } + public String getGroup() { return group; } diff --git a/src/main/java/org/dependencytrack/model/OrganizationalContact.java b/src/main/java/org/dependencytrack/model/OrganizationalContact.java index f03f09f01..347bfd89e 100644 --- a/src/main/java/org/dependencytrack/model/OrganizationalContact.java +++ b/src/main/java/org/dependencytrack/model/OrganizationalContact.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.io.Serializable; +import java.util.Objects; /** * Model class for tracking organizational contacts. @@ -67,4 +68,17 @@ public String getPhone() { public void setPhone(String phone) { this.phone = phone; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final OrganizationalContact that = (OrganizationalContact) o; + return Objects.equals(name, that.name) && Objects.equals(email, that.email) && Objects.equals(phone, that.phone); + } + + @Override + public int hashCode() { + return Objects.hash(name, email, phone); + } } diff --git a/src/main/java/org/dependencytrack/model/OrganizationalEntity.java b/src/main/java/org/dependencytrack/model/OrganizationalEntity.java index 134fc7cbf..e8f682bac 100644 --- a/src/main/java/org/dependencytrack/model/OrganizationalEntity.java +++ b/src/main/java/org/dependencytrack/model/OrganizationalEntity.java @@ -25,7 +25,9 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Objects; /** * Model class for tracking organizational entities (provider, supplier, manufacturer, etc). @@ -76,4 +78,19 @@ public void addContact(OrganizationalContact contact) { public void setContacts(List contacts) { this.contacts = contacts; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final OrganizationalEntity that = (OrganizationalEntity) o; + return Objects.equals(name, that.name) && Arrays.equals(urls, that.urls) && Objects.equals(contacts, that.contacts); + } + + @Override + public int hashCode() { + int result = Objects.hash(name, contacts); + result = 31 * result + Arrays.hashCode(urls); + return result; + } } diff --git a/src/main/java/org/dependencytrack/model/Project.java b/src/main/java/org/dependencytrack/model/Project.java index 184b5492c..8ce18c201 100644 --- a/src/main/java/org/dependencytrack/model/Project.java +++ b/src/main/java/org/dependencytrack/model/Project.java @@ -31,9 +31,12 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.github.packageurl.MalformedPackageURLException; import com.github.packageurl.PackageURL; +import io.swagger.annotations.ApiModelProperty; +import org.dependencytrack.persistence.converter.OrganizationalEntityJsonConverter; import org.dependencytrack.resources.v1.serializers.CustomPackageURLSerializer; import javax.jdo.annotations.Column; +import javax.jdo.annotations.Convert; import javax.jdo.annotations.Element; import javax.jdo.annotations.Extension; import javax.jdo.annotations.FetchGroup; @@ -86,7 +89,11 @@ @Persistent(name = "children"), @Persistent(name = "properties"), @Persistent(name = "tags"), - @Persistent(name = "accessTeams") + @Persistent(name = "accessTeams"), + @Persistent(name = "metadata") + }), + @FetchGroup(name = "METADATA", members = { + @Persistent(name = "metadata") }), @FetchGroup(name = "IDENTIFIERS", members = { @Persistent(name = "id"), @@ -122,6 +129,7 @@ public class Project implements Serializable { */ public enum FetchGroup { ALL, + METADATA, IDENTIFIERS, METRICS_UPDATE, NOTIFICATION, @@ -147,6 +155,16 @@ public enum FetchGroup { @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The publisher may only contain printable characters") private String publisher; + @Persistent(defaultFetchGroup = "true") + @Convert(OrganizationalEntityJsonConverter.class) + @Column(name = "MANUFACTURER", jdbcType = "CLOB", allowsNull = "true") + private OrganizationalEntity manufacturer; + + @Persistent(defaultFetchGroup = "true") + @Convert(OrganizationalEntityJsonConverter.class) + @Column(name = "SUPPLIER", jdbcType = "CLOB", allowsNull = "true") + private OrganizationalEntity supplier; + @Persistent @Column(name = "GROUP", jdbcType = "VARCHAR") @Index(name = "PROJECT_GROUP_IDX") @@ -275,6 +293,10 @@ public enum FetchGroup { @Serialized private List externalReferences; + @Persistent(mappedBy = "project") + @ApiModelProperty(accessMode = ApiModelProperty.AccessMode.READ_ONLY) + private ProjectMetadata metadata; + private transient ProjectMetrics metrics; private transient List versions; @@ -305,6 +327,22 @@ public void setPublisher(String publisher) { this.publisher = publisher; } + public OrganizationalEntity getManufacturer() { + return manufacturer; + } + + public void setManufacturer(final OrganizationalEntity manufacturer) { + this.manufacturer = manufacturer; + } + + public OrganizationalEntity getSupplier() { + return supplier; + } + + public void setSupplier(OrganizationalEntity supplier) { + this.supplier = supplier; + } + public String getGroup() { return group; } @@ -501,6 +539,14 @@ public void addAccessTeam(Team accessTeam) { this.accessTeams.add(accessTeam); } + public ProjectMetadata getMetadata() { + return metadata; + } + + public void setMetadata(final ProjectMetadata metadata) { + this.metadata = metadata; + } + @JsonIgnore public List getDependencyGraph() { return dependencyGraph; diff --git a/src/main/java/org/dependencytrack/model/ProjectMetadata.java b/src/main/java/org/dependencytrack/model/ProjectMetadata.java new file mode 100644 index 000000000..ddc71647e --- /dev/null +++ b/src/main/java/org/dependencytrack/model/ProjectMetadata.java @@ -0,0 +1,101 @@ +/* + * 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) Steve Springett. All Rights Reserved. + */ +package org.dependencytrack.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import org.dependencytrack.persistence.converter.OrganizationalContactsJsonConverter; +import org.dependencytrack.persistence.converter.OrganizationalEntityJsonConverter; + +import javax.jdo.annotations.Column; +import javax.jdo.annotations.Convert; +import javax.jdo.annotations.IdGeneratorStrategy; +import javax.jdo.annotations.PersistenceCapable; +import javax.jdo.annotations.Persistent; +import javax.jdo.annotations.PrimaryKey; +import javax.jdo.annotations.Unique; +import java.util.List; + +/** + * Metadata that relates to, but does not directly describe, a {@link Project}. + *

+ * In CycloneDX terms, {@link ProjectMetadata} represents data from the {@code metadata} node + * of a BOM (except {@code metadata.component}, which represents a {@link Project} in Dependency-Track). + * + * @since 4.10.0 + */ +@PersistenceCapable(table = "PROJECT_METADATA") +@JsonInclude(Include.NON_NULL) +public class ProjectMetadata { + + @PrimaryKey + @Persistent(valueStrategy = IdGeneratorStrategy.NATIVE) + @JsonIgnore + private long id; + + @Persistent + @Unique(name = "PROJECT_METADATA_PROJECT_ID_IDX") + @Column(name = "PROJECT_ID", allowsNull = "false") + @JsonIgnore + private Project project; + + @Persistent(defaultFetchGroup = "true") + @Convert(OrganizationalEntityJsonConverter.class) + @Column(name = "SUPPLIER", jdbcType = "CLOB", allowsNull = "true") + private OrganizationalEntity supplier; + + @Persistent(defaultFetchGroup = "true") + @Convert(OrganizationalContactsJsonConverter.class) + @Column(name = "AUTHORS", jdbcType = "CLOB", allowsNull = "true") + private List authors; + + public long getId() { + return id; + } + + public void setId(final long id) { + this.id = id; + } + + public Project getProject() { + return project; + } + + public void setProject(final Project project) { + this.project = project; + } + + public OrganizationalEntity getSupplier() { + return supplier; + } + + public void setSupplier(final OrganizationalEntity supplier) { + this.supplier = supplier; + } + + public List getAuthors() { + return authors; + } + + public void setAuthors(final List authors) { + this.authors = authors; + } + +} \ No newline at end of file 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 1e1a11a69..d3a41ec6e 100644 --- a/src/main/java/org/dependencytrack/parser/cyclonedx/util/ModelConverter.java +++ b/src/main/java/org/dependencytrack/parser/cyclonedx/util/ModelConverter.java @@ -25,6 +25,7 @@ import org.cyclonedx.model.Dependency; import org.cyclonedx.model.Hash; import org.cyclonedx.model.LicenseChoice; +import org.cyclonedx.model.Metadata; import org.cyclonedx.model.Swid; import org.dependencytrack.model.Analysis; import org.dependencytrack.model.AnalysisJustification; @@ -39,6 +40,7 @@ import org.dependencytrack.model.OrganizationalContact; import org.dependencytrack.model.OrganizationalEntity; import org.dependencytrack.model.Project; +import org.dependencytrack.model.ProjectMetadata; import org.dependencytrack.model.ServiceComponent; import org.dependencytrack.model.Severity; import org.dependencytrack.model.Vulnerability; @@ -58,7 +60,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -81,7 +82,15 @@ public class ModelConverter { private ModelConverter() { } - public static Project convertToProject(final org.cyclonedx.model.Component cdxComponent) { + public static ProjectMetadata convertToProjectMetadata(final Metadata cdxMetadata) { + final var projectMetadata = new ProjectMetadata(); + projectMetadata.setSupplier(ModelConverter.convert(cdxMetadata.getSupplier())); + projectMetadata.setAuthors(ModelConverter.convertCdxContacts(cdxMetadata.getAuthors())); + return projectMetadata; + } + + public static Project convertToProject(final Metadata cdxMetadata, final ProjectMetadata projectMetadata) { + final var cdxComponent = cdxMetadata.getComponent(); final var project = new Project(); project.setAuthor(trimToNull(cdxComponent.getAuthor())); project.setPublisher(trimToNull(cdxComponent.getPublisher())); @@ -91,6 +100,9 @@ public static Project convertToProject(final org.cyclonedx.model.Component cdxCo project.setVersion(trimToNull(cdxComponent.getVersion())); project.setDescription(trimToNull(cdxComponent.getDescription())); project.setExternalReferences(convertExternalReferences(cdxComponent.getExternalReferences())); + project.setManufacturer(ModelConverter.convert(cdxMetadata.getManufacture())); + project.setSupplier(ModelConverter.convert(cdxComponent.getSupplier())); + project.setMetadata(projectMetadata); if (cdxComponent.getPurl() != null) { try { @@ -120,6 +132,7 @@ public static Component convertComponent(final org.cyclonedx.model.Component cdx final var component = new Component(); component.setAuthor(trimToNull(cdxComponent.getAuthor())); component.setPublisher(trimToNull(cdxComponent.getPublisher())); + component.setSupplier(convert(cdxComponent.getSupplier())); component.setBomRef(trimToNull(cdxComponent.getBomRef())); component.setClassifier(convertClassifier(cdxComponent.getType()).orElse(Classifier.LIBRARY)); component.setGroup(trimToNull(cdxComponent.getGroup())); @@ -216,6 +229,80 @@ public static Component convertComponent(final org.cyclonedx.model.Component cdx return component; } + public static OrganizationalEntity convert(final org.cyclonedx.model.OrganizationalEntity cdxEntity) { + if (cdxEntity == null) { + return null; + } + + final var dtEntity = new OrganizationalEntity(); + dtEntity.setName(StringUtils.trimToNull(cdxEntity.getName())); + if (cdxEntity.getContacts() != null && !cdxEntity.getContacts().isEmpty()) { + dtEntity.setContacts(cdxEntity.getContacts().stream().map(ModelConverter::convert).toList()); + } + if (cdxEntity.getUrls() != null && !cdxEntity.getUrls().isEmpty()) { + dtEntity.setUrls(cdxEntity.getUrls().toArray(new String[0])); + } + + return dtEntity; + } + + public static List convertCdxContacts(final List cdxContacts) { + if (cdxContacts == null) { + return null; + } + + return cdxContacts.stream().map(ModelConverter::convert).toList(); + } + + private static OrganizationalContact convert(final org.cyclonedx.model.OrganizationalContact cdxContact) { + if (cdxContact == null) { + return null; + } + + final var dtContact = new OrganizationalContact(); + dtContact.setName(StringUtils.trimToNull(cdxContact.getName())); + dtContact.setEmail(StringUtils.trimToNull(cdxContact.getEmail())); + dtContact.setPhone(StringUtils.trimToNull(cdxContact.getPhone())); + return dtContact; + } + + private static List convertContacts(final List dtContacts) { + if (dtContacts == null) { + return null; + } + + return dtContacts.stream().map(ModelConverter::convert).toList(); + } + + private static org.cyclonedx.model.OrganizationalEntity convert(final OrganizationalEntity dtEntity) { + if (dtEntity == null) { + return null; + } + + final var cdxEntity = new org.cyclonedx.model.OrganizationalEntity(); + cdxEntity.setName(StringUtils.trimToNull(dtEntity.getName())); + if (dtEntity.getContacts() != null && !dtEntity.getContacts().isEmpty()) { + cdxEntity.setContacts(dtEntity.getContacts().stream().map(ModelConverter::convert).toList()); + } + if (dtEntity.getUrls() != null && dtEntity.getUrls().length > 0) { + cdxEntity.setUrls(Arrays.stream(dtEntity.getUrls()).toList()); + } + + return cdxEntity; + } + + private static org.cyclonedx.model.OrganizationalContact convert(final OrganizationalContact dtContact) { + if (dtContact == null) { + return null; + } + + final var cdxContact = new org.cyclonedx.model.OrganizationalContact(); + cdxContact.setName(StringUtils.trimToNull(dtContact.getName())); + cdxContact.setEmail(StringUtils.trimToNull(dtContact.getEmail())); + cdxContact.setPhone(StringUtils.trimToNull(cdxContact.getPhone())); + return cdxContact; + } + public static List convertServices(final List cdxServices) { if (cdxServices == null || cdxServices.isEmpty()) { return Collections.emptyList(); @@ -227,6 +314,7 @@ public static List convertServices(final List List flatten(final Collection items, return result; } - @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(); cycloneComponent.setBomRef(component.getUuid().toString()); @@ -350,6 +437,7 @@ public static org.cyclonedx.model.Component convert(final QueryManager qm, final cycloneComponent.setCopyright(StringUtils.trimToNull(component.getCopyright())); cycloneComponent.setCpe(StringUtils.trimToNull(component.getCpe())); cycloneComponent.setAuthor(StringUtils.trimToNull(component.getAuthor())); + cycloneComponent.setSupplier(convert(component.getSupplier())); if (component.getSwidTagId() != null) { final Swid swid = new Swid(); @@ -410,7 +498,7 @@ public static org.cyclonedx.model.Component convert(final QueryManager qm, final cycloneComponent.setLicenseChoice(licenseChoice); } - if (component.getExternalReferences() != null && component.getExternalReferences().size() > 0) { + if (component.getExternalReferences() != null && !component.getExternalReferences().isEmpty()) { List references = new ArrayList<>(); for (ExternalReference ref : component.getExternalReferences()) { org.cyclonedx.model.ExternalReference cdxRef = new org.cyclonedx.model.ExternalReference(); @@ -451,6 +539,7 @@ public static org.cyclonedx.model.Metadata createMetadata(final Project project) tool.setVersion(alpine.Config.getInstance().getApplicationVersion()); metadata.setTools(Collections.singletonList(tool)); if (project != null) { + metadata.setManufacture(convert(project.getManufacturer())); final org.cyclonedx.model.Component cycloneComponent = new org.cyclonedx.model.Component(); cycloneComponent.setBomRef(project.getUuid().toString()); cycloneComponent.setAuthor(StringUtils.trimToNull(project.getAuthor())); @@ -479,9 +568,9 @@ public static org.cyclonedx.model.Metadata createMetadata(final Project project) } else { cycloneComponent.setType(org.cyclonedx.model.Component.Type.LIBRARY); } - if (project.getExternalReferences() != null && project.getExternalReferences().size() > 0) { + if (project.getExternalReferences() != null && !project.getExternalReferences().isEmpty()) { List references = new ArrayList<>(); - project.getExternalReferences().stream().forEach(externalReference -> { + project.getExternalReferences().forEach(externalReference -> { org.cyclonedx.model.ExternalReference ref = new org.cyclonedx.model.ExternalReference(); ref.setUrl(externalReference.getUrl()); ref.setType(externalReference.getType()); @@ -490,7 +579,13 @@ public static org.cyclonedx.model.Metadata createMetadata(final Project project) }); cycloneComponent.setExternalReferences(references); } + cycloneComponent.setSupplier(convert(project.getSupplier())); metadata.setComponent(cycloneComponent); + + if (project.getMetadata() != null) { + metadata.setAuthors(convertContacts(project.getMetadata().getAuthors())); + metadata.setSupplier(convert(project.getMetadata().getSupplier())); + } } return metadata; } @@ -498,25 +593,8 @@ public static org.cyclonedx.model.Metadata createMetadata(final Project project) public static org.cyclonedx.model.Service convert(final QueryManager qm, final ServiceComponent service) { final org.cyclonedx.model.Service cycloneService = new org.cyclonedx.model.Service(); cycloneService.setBomRef(service.getUuid().toString()); - if (service.getProvider() != null) { - org.cyclonedx.model.OrganizationalEntity cycloneEntity = new org.cyclonedx.model.OrganizationalEntity(); - cycloneEntity.setName(service.getProvider().getName()); - if (service.getProvider().getUrls() != null) { - cycloneEntity.setUrls(Arrays.asList(service.getProvider().getUrls())); - } - if (service.getProvider().getContacts() != null && service.getProvider().getContacts().size() > 0) { - List contacts = new ArrayList<>(); - for (OrganizationalContact contact : service.getProvider().getContacts()) { - org.cyclonedx.model.OrganizationalContact cycloneContact = new org.cyclonedx.model.OrganizationalContact(); - cycloneContact.setName(contact.getName()); - cycloneContact.setEmail(contact.getEmail()); - cycloneContact.setPhone(contact.getPhone()); - contacts.add(cycloneContact); - } - cycloneEntity.setContacts(contacts); - } - cycloneService.setProvider(cycloneEntity); - } + cycloneService.setProvider(convert(service.getProvider())); + cycloneService.setProvider(convert(service.getProvider())); cycloneService.setGroup(StringUtils.trimToNull(service.getGroup())); cycloneService.setName(StringUtils.trimToNull(service.getName())); cycloneService.setVersion(StringUtils.trimToNull(service.getVersion())); @@ -526,13 +604,13 @@ public static org.cyclonedx.model.Service convert(final QueryManager qm, final S } cycloneService.setAuthenticated(service.getAuthenticated()); cycloneService.setxTrustBoundary(service.getCrossesTrustBoundary()); - if (service.getData() != null && service.getData().size() > 0) { + if (service.getData() != null && !service.getData().isEmpty()) { for (DataClassification dc : service.getData()) { org.cyclonedx.model.ServiceData sd = new org.cyclonedx.model.ServiceData(dc.getDirection().name(), dc.getName()); cycloneService.addServiceData(sd); } } - if (service.getExternalReferences() != null && service.getExternalReferences().size() > 0) { + if (service.getExternalReferences() != null && !service.getExternalReferences().isEmpty()) { for (ExternalReference ref : service.getExternalReferences()) { org.cyclonedx.model.ExternalReference cycloneRef = new org.cyclonedx.model.ExternalReference(); cycloneRef.setType(ref.getType()); diff --git a/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java b/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java index 535006b75..2f45e02d0 100644 --- a/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java @@ -548,6 +548,7 @@ public Component cloneComponent(Component sourceComponent, Project destinationPr component.setLicenseUrl(sourceComponent.getLicenseUrl()); component.setResolvedLicense(sourceComponent.getResolvedLicense()); component.setAuthor(sourceComponent.getAuthor()); + component.setSupplier(sourceComponent.getSupplier()); // TODO Add support for parent component and children components component.setProject(destinationProject); return createComponent(component, commitIndex); @@ -584,6 +585,7 @@ public Component updateComponent(Component transientComponent, boolean commitInd component.setPurlCoordinates(component.getPurl()); component.setInternal(transientComponent.isInternal()); component.setAuthor(transientComponent.getAuthor()); + component.setSupplier(transientComponent.getSupplier()); final Component result = persist(component); return result; } diff --git a/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java b/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java index 9a8c1113f..e9cb6fd10 100644 --- a/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java @@ -37,6 +37,7 @@ import org.dependencytrack.model.ConfigPropertyConstants; import org.dependencytrack.model.FindingAttribution; import org.dependencytrack.model.Project; +import org.dependencytrack.model.ProjectMetadata; import org.dependencytrack.model.ProjectProperty; import org.dependencytrack.model.ProjectVersion; import org.dependencytrack.model.ServiceComponent; @@ -529,39 +530,6 @@ public Project createProject(final Project project, List tags, boolean comm return result; } - /** - * Updates an existing Project. - * - * @param uuid the uuid of the project to update - * @param name the name of the project - * @param description a description of the project - * @param version the project version - * @param tags a List of Tags - these will be resolved if necessary - * @param purl an optional Package URL - * @param active specified if the project is active - * @param commitIndex specifies if the search index should be committed (an expensive operation) - * @return the updated Project - */ - @Override - public Project updateProject(UUID uuid, String name, String description, String version, List tags, PackageURL purl, boolean active, boolean commitIndex) { - final Project project = getObjectByUuid(Project.class, uuid); - project.setName(name); - project.setDescription(description); - project.setVersion(version); - project.setPurl(purl); - - if (!active && Boolean.TRUE.equals(project.isActive()) && hasActiveChild(project)) { - throw new IllegalArgumentException("Project cannot be set to inactive, if active children are present."); - } - project.setActive(active); - - final List resolvedTags = resolveTags(tags); - bind(project, resolvedTags); - - final Project result = persist(project); - return result; - } - /** * Updates an existing Project. * @@ -574,6 +542,8 @@ public Project updateProject(Project transientProject, boolean commitIndex) { final Project project = getObjectByUuid(Project.class, transientProject.getUuid()); project.setAuthor(transientProject.getAuthor()); project.setPublisher(transientProject.getPublisher()); + project.setManufacturer(transientProject.getManufacturer()); + project.setSupplier(transientProject.getSupplier()); project.setGroup(transientProject.getGroup()); project.setName(transientProject.getName()); project.setDescription(transientProject.getDescription()); @@ -632,6 +602,8 @@ public Project clone(UUID from, String newVersion, boolean includeTags, boolean } Project project = new Project(); project.setAuthor(source.getAuthor()); + project.setManufacturer(source.getManufacturer()); + project.setSupplier(source.getSupplier()); project.setPublisher(source.getPublisher()); project.setGroup(source.getGroup()); project.setName(source.getName()); @@ -648,6 +620,14 @@ public Project clone(UUID from, String newVersion, boolean includeTags, boolean project.setParent(source.getParent()); project = persist(project); + if (source.getMetadata() != null) { + final var metadata = new ProjectMetadata(); + metadata.setProject(project); + metadata.setAuthors(source.getMetadata().getAuthors()); + metadata.setSupplier(source.getMetadata().getSupplier()); + persist(metadata); + } + if (includeTags) { for (final Tag tag : source.getTags()) { tag.getProjects().add(project); diff --git a/src/main/java/org/dependencytrack/persistence/QueryManager.java b/src/main/java/org/dependencytrack/persistence/QueryManager.java index 2958da116..eca7adc73 100644 --- a/src/main/java/org/dependencytrack/persistence/QueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/QueryManager.java @@ -550,10 +550,6 @@ public Project createProject(final Project project, List tags, boolean comm return getProjectQueryManager().createProject(project, tags, commitIndex); } - public Project updateProject(UUID uuid, String name, String description, String version, List tags, PackageURL purl, boolean active, boolean commitIndex) { - return getProjectQueryManager().updateProject(uuid, name, description, version, tags, purl, active, commitIndex); - } - public Project updateProject(Project transientProject, boolean commitIndex) { return getProjectQueryManager().updateProject(transientProject, commitIndex); } diff --git a/src/main/java/org/dependencytrack/persistence/converter/AbstractJsonConverter.java b/src/main/java/org/dependencytrack/persistence/converter/AbstractJsonConverter.java new file mode 100644 index 000000000..c4f9fe19f --- /dev/null +++ b/src/main/java/org/dependencytrack/persistence/converter/AbstractJsonConverter.java @@ -0,0 +1,66 @@ +/* + * 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) Steve Springett. All Rights Reserved. + */ +package org.dependencytrack.persistence.converter; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import javax.jdo.AttributeConverter; + +/** + * @since 4.10.0 + */ +abstract class AbstractJsonConverter implements AttributeConverter { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private final TypeReference typeReference; + + AbstractJsonConverter(final TypeReference typeReference) { + this.typeReference = typeReference; + } + + @Override + public String convertToDatastore(final T attributeValue) { + if (attributeValue == null) { + return null; + } + + try { + return OBJECT_MAPPER.writeValueAsString(attributeValue); + } catch (JacksonException e) { + throw new RuntimeException(e); + } + } + + @Override + public T convertToAttribute(final String datastoreValue) { + if (datastoreValue == null) { + return null; + } + + try { + return OBJECT_MAPPER.readValue(datastoreValue, typeReference); + } catch (JacksonException e) { + throw new RuntimeException(e); + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/persistence/converter/OrganizationalContactsJsonConverter.java b/src/main/java/org/dependencytrack/persistence/converter/OrganizationalContactsJsonConverter.java new file mode 100644 index 000000000..71d6904e7 --- /dev/null +++ b/src/main/java/org/dependencytrack/persistence/converter/OrganizationalContactsJsonConverter.java @@ -0,0 +1,47 @@ +/* + * 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) Steve Springett. All Rights Reserved. + */ +package org.dependencytrack.persistence.converter; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.dependencytrack.model.OrganizationalContact; + +import java.util.List; + +/** + * @since 4.10.0 + */ +public class OrganizationalContactsJsonConverter extends AbstractJsonConverter> { + + public OrganizationalContactsJsonConverter() { + super(new TypeReference<>() {}); + } + + @Override + public String convertToDatastore(final List attributeValue) { + // Overriding is required for DataNucleus to correctly detect the return type. + return super.convertToDatastore(attributeValue); + } + + @Override + public List convertToAttribute(final String datastoreValue) { + // Overriding is required for DataNucleus to correctly detect the return type. + return super.convertToAttribute(datastoreValue); + } + +} \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/persistence/converter/OrganizationalEntityJsonConverter.java b/src/main/java/org/dependencytrack/persistence/converter/OrganizationalEntityJsonConverter.java new file mode 100644 index 000000000..0a0a1ce47 --- /dev/null +++ b/src/main/java/org/dependencytrack/persistence/converter/OrganizationalEntityJsonConverter.java @@ -0,0 +1,45 @@ +/* + * 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) Steve Springett. All Rights Reserved. + */ +package org.dependencytrack.persistence.converter; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.dependencytrack.model.OrganizationalEntity; + +/** + * @since 4.10.0 + */ +public class OrganizationalEntityJsonConverter extends AbstractJsonConverter { + + public OrganizationalEntityJsonConverter() { + super(new TypeReference<>() {}); + } + + @Override + public String convertToDatastore(final OrganizationalEntity attributeValue) { + // Overriding is required for DataNucleus to correctly detect the return type. + return super.convertToDatastore(attributeValue); + } + + @Override + public OrganizationalEntity convertToAttribute(final String datastoreValue) { + // Overriding is required for DataNucleus to correctly detect the return type. + return super.convertToAttribute(datastoreValue); + } + +} \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java index b1db57900..7f6f668ca 100644 --- a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java @@ -382,6 +382,8 @@ public Response patchProject( modified |= setIfDifferent(jsonProject, project, Project::getPurl, Project::setPurl); modified |= setIfDifferent(jsonProject, project, Project::getSwidTagId, Project::setSwidTagId); modified |= setIfDifferent(jsonProject, project, Project::isActive, Project::setActive); + modified |= setIfDifferent(jsonProject, project, Project::getManufacturer, Project::setManufacturer); + modified |= setIfDifferent(jsonProject, project, Project::getSupplier, Project::setSupplier); if (jsonProject.getParent() != null && jsonProject.getParent().getUuid() != null) { final Project parent = qm.getObjectByUuid(Project.class, jsonProject.getParent().getUuid()); if (parent == null) { diff --git a/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java b/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java index 8b42e42e0..a4b53e6c7 100644 --- a/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java +++ b/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java @@ -50,6 +50,7 @@ import org.dependencytrack.model.IntegrityMetaComponent; import org.dependencytrack.model.License; import org.dependencytrack.model.Project; +import org.dependencytrack.model.ProjectMetadata; import org.dependencytrack.model.ServiceComponent; import org.dependencytrack.model.VulnerabilityAnalysisLevel; import org.dependencytrack.model.VulnerabilityScan.TargetType; @@ -97,6 +98,7 @@ import static org.dependencytrack.parser.cyclonedx.util.ModelConverter.convertComponents; import static org.dependencytrack.parser.cyclonedx.util.ModelConverter.convertServices; import static org.dependencytrack.parser.cyclonedx.util.ModelConverter.convertToProject; +import static org.dependencytrack.parser.cyclonedx.util.ModelConverter.convertToProjectMetadata; import static org.dependencytrack.parser.cyclonedx.util.ModelConverter.flatten; import static org.dependencytrack.util.InternalComponentIdentificationUtil.isInternalComponent; import static org.dependencytrack.util.PersistenceUtil.applyIfChanged; @@ -218,13 +220,16 @@ private void processBom(final Context ctx, final File bomFile) throws BomConsump // Note: One identity can point to multiple BOM refs, due to component and service de-duplication. final var bomRefsByIdentity = new HashSetValuedHashMap(); - final Project metadataComponent; + Project metadataComponent = null; + ProjectMetadata projectMetadata = null; List components = new ArrayList<>(); - if (cdxBom.getMetadata() != null && cdxBom.getMetadata().getComponent() != null) { - metadataComponent = convertToProject(cdxBom.getMetadata().getComponent()); - components.addAll(convertComponents(cdxBom.getMetadata().getComponent().getComponents())); - } else { - metadataComponent = null; + if (cdxBom.getMetadata() != null) { + projectMetadata = convertToProjectMetadata(cdxBom.getMetadata()); + if (cdxBom.getMetadata().getComponent() != null) { + metadataComponent = convertToProject(cdxBom.getMetadata(), projectMetadata); + projectMetadata.setProject(metadataComponent); + components.addAll(convertComponents(cdxBom.getMetadata().getComponent().getComponents())); + } } components.addAll(convertComponents(cdxBom.getComponents())); components = flatten(components, Component::getChildren, Component::setChildren); @@ -307,6 +312,7 @@ private void processBom(final Context ctx, final File bomFile) throws BomConsump try { trx.begin(); + qm.getPersistenceManager().makePersistent(projectMetadata); final Project project = processMetadataComponent(ctx, pm, metadataComponent); final Map persistentComponents = processComponents(qm, project, components, identitiesByBomRef, bomRefsByIdentity); @@ -491,6 +497,8 @@ private static Project processMetadataComponent(final Context ctx, final Persist changed |= applyIfChanged(project, metadataComponent, Project::getAuthor, project::setAuthor); changed |= applyIfChanged(project, metadataComponent, Project::getPublisher, project::setPublisher); changed |= applyIfChanged(project, metadataComponent, Project::getClassifier, project::setClassifier); + changed |= applyIfChanged(project, metadataComponent, Project::getSupplier, project::setSupplier); + changed |= applyIfChanged(project, metadataComponent, Project::getManufacturer, project::setManufacturer); // TODO: Currently these properties are "decoupled" from the BOM and managed directly by DT users. // Perhaps there could be a flag for BOM uploads saying "use BOM properties" or something? // changed |= applyIfChanged(project, metadataComponent, Project::getGroup, project::setGroup); @@ -500,6 +508,7 @@ private static Project processMetadataComponent(final Context ctx, final Persist changed |= applyIfChanged(project, metadataComponent, Project::getExternalReferences, project::setExternalReferences); changed |= applyIfChanged(project, metadataComponent, Project::getPurl, project::setPurl); changed |= applyIfChanged(project, metadataComponent, Project::getSwidTagId, project::setSwidTagId); + changed |= applyIfChanged(project, metadataComponent, Project::getMetadata, project::setMetadata); if (changed) { pm.flush(); } @@ -582,6 +591,7 @@ private static Map processComponents(final QueryMa var changed = false; changed |= applyIfChanged(persistentComponent, component, Component::getAuthor, persistentComponent::setAuthor); changed |= applyIfChanged(persistentComponent, component, Component::getPublisher, persistentComponent::setPublisher); + changed |= applyIfChanged(persistentComponent, component, Component::getSupplier, persistentComponent::setSupplier); changed |= applyIfChanged(persistentComponent, component, Component::getClassifier, persistentComponent::setClassifier); changed |= applyIfChanged(persistentComponent, component, Component::getGroup, persistentComponent::setGroup); changed |= applyIfChanged(persistentComponent, component, Component::getName, persistentComponent::setName); diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml index 94e63b561..c691be1e0 100644 --- a/src/main/resources/META-INF/persistence.xml +++ b/src/main/resources/META-INF/persistence.xml @@ -39,6 +39,7 @@ org.dependencytrack.model.PolicyViolation org.dependencytrack.model.PortfolioMetrics org.dependencytrack.model.Project + org.dependencytrack.model.ProjectMetadata org.dependencytrack.model.ProjectMetrics org.dependencytrack.model.ProjectProperty org.dependencytrack.model.Repository diff --git a/src/main/resources/migration/changelog-v5.3.0.xml b/src/main/resources/migration/changelog-v5.3.0.xml index 322f733a7..3a12b459c 100644 --- a/src/main/resources/migration/changelog-v5.3.0.xml +++ b/src/main/resources/migration/changelog-v5.3.0.xml @@ -2470,4 +2470,24 @@ ON "COMPONENT" USING GIN ("DIRECT_DEPENDENCIES" GIN_TRGM_OPS); + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/java/org/dependencytrack/persistence/converter/OrganizationalContactsJsonConverterTest.java b/src/test/java/org/dependencytrack/persistence/converter/OrganizationalContactsJsonConverterTest.java new file mode 100644 index 000000000..c7db6029e --- /dev/null +++ b/src/test/java/org/dependencytrack/persistence/converter/OrganizationalContactsJsonConverterTest.java @@ -0,0 +1,79 @@ +/* + * 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) Steve Springett. All Rights Reserved. + */ +package org.dependencytrack.persistence.converter; + +import org.dependencytrack.model.OrganizationalContact; +import org.junit.Test; + +import java.util.List; + +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; +import static org.assertj.core.api.Assertions.assertThat; + +public class OrganizationalContactsJsonConverterTest { + + @Test + public void testConvertToDatastore() { + final var contact = new OrganizationalContact(); + contact.setName("Foo"); + contact.setEmail("foo@example.com"); + contact.setPhone("123456789"); + + assertThatJson(new OrganizationalContactsJsonConverter().convertToDatastore(List.of(contact))) + .isEqualTo(""" + [ + { + "name": "Foo", + "email": "foo@example.com", + "phone": "123456789" + } + ] + """); + } + + @Test + public void testConvertToAttribute() { + final List contacts = new OrganizationalContactsJsonConverter().convertToAttribute(""" + [ + { + "name": "Foo", + "email": "foo@example.com", + "phone": "123456789" + } + ] + """); + + assertThat(contacts).satisfiesExactly(contact -> { + assertThat(contact.getName()).isEqualTo("Foo"); + assertThat(contact.getEmail()).isEqualTo("foo@example.com"); + assertThat(contact.getPhone()).isEqualTo("123456789"); + }); + } + + @Test + public void testConvertToDatastoreNull() { + assertThat(new OrganizationalContactsJsonConverter().convertToDatastore(null)).isNull(); + } + + @Test + public void testConvertToAttributeNull() { + assertThat(new OrganizationalContactsJsonConverter().convertToAttribute(null)).isNull(); + } + +} \ No newline at end of file diff --git a/src/test/java/org/dependencytrack/persistence/converter/OrganizationalEntityJsonConverterTest.java b/src/test/java/org/dependencytrack/persistence/converter/OrganizationalEntityJsonConverterTest.java new file mode 100644 index 000000000..e2fb40783 --- /dev/null +++ b/src/test/java/org/dependencytrack/persistence/converter/OrganizationalEntityJsonConverterTest.java @@ -0,0 +1,100 @@ +/* + * 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) Steve Springett. All Rights Reserved. + */ +package org.dependencytrack.persistence.converter; + +import org.dependencytrack.model.OrganizationalContact; +import org.dependencytrack.model.OrganizationalEntity; +import org.junit.Test; + +import java.util.List; + +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; +import static org.assertj.core.api.Assertions.assertThat; + +public class OrganizationalEntityJsonConverterTest { + + @Test + public void testConvertToDatastore() { + final var contact = new OrganizationalContact(); + contact.setName("Foo"); + contact.setEmail("foo@example.com"); + contact.setPhone("123456789"); + + final var entity = new OrganizationalEntity(); + entity.setName("foo"); + entity.setUrls(new String[]{"https://example.com"}); + entity.setContacts(List.of(contact)); + + assertThatJson(new OrganizationalEntityJsonConverter().convertToDatastore(entity)) + .isEqualTo(""" + { + "name": "foo", + "urls": [ + "https://example.com" + ], + "contacts": [ + { + "name": "Foo", + "email": "foo@example.com", + "phone": "123456789" + } + ] + } + """); + } + + @Test + public void testConvertToAttribute() { + final OrganizationalEntity entity = new OrganizationalEntityJsonConverter().convertToAttribute(""" + { + "name": "foo", + "urls": [ + "https://example.com" + ], + "contacts": [ + { + "name": "Foo", + "email": "foo@example.com", + "phone": "123456789" + } + ] + } + """); + + assertThat(entity).isNotNull(); + assertThat(entity.getName()).isEqualTo("foo"); + assertThat(entity.getUrls()).containsOnly("https://example.com"); + assertThat(entity.getContacts()).satisfiesExactly(contact -> { + assertThat(contact.getName()).isEqualTo("Foo"); + assertThat(contact.getEmail()).isEqualTo("foo@example.com"); + assertThat(contact.getPhone()).isEqualTo("123456789"); + }); + } + + @Test + public void testConvertToDatastoreNull() { + assertThat(new OrganizationalEntityJsonConverter().convertToDatastore(null)).isNull(); + } + + @Test + public void testConvertToAttributeNull() { + assertThat(new OrganizationalEntityJsonConverter().convertToAttribute(null)).isNull(); + } + +} \ No newline at end of file diff --git a/src/test/java/org/dependencytrack/resources/v1/BomResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/BomResourceTest.java index a32b632d7..0f30791ef 100644 --- a/src/test/java/org/dependencytrack/resources/v1/BomResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/BomResourceTest.java @@ -33,7 +33,10 @@ import org.dependencytrack.model.AnalyzerIdentity; import org.dependencytrack.model.Classifier; import org.dependencytrack.model.Component; +import org.dependencytrack.model.OrganizationalContact; +import org.dependencytrack.model.OrganizationalEntity; import org.dependencytrack.model.Project; +import org.dependencytrack.model.ProjectMetadata; import org.dependencytrack.model.Severity; import org.dependencytrack.model.Vulnerability; import org.dependencytrack.model.WorkflowState; @@ -65,6 +68,7 @@ import java.util.ArrayList; import java.util.Base64; import java.util.Date; +import java.util.List; import java.util.UUID; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; @@ -126,15 +130,35 @@ public void exportProjectAsCycloneDxInventoryTest() { vulnerability.setSeverity(Severity.HIGH); vulnerability = qm.createVulnerability(vulnerability, false); + final var projectManufacturer = new OrganizationalEntity(); + projectManufacturer.setName("projectManufacturer"); + final var projectSupplier = new OrganizationalEntity(); + projectSupplier.setName("projectSupplier"); + var project = new Project(); project.setName("acme-app"); project.setClassifier(Classifier.APPLICATION); + project.setManufacturer(projectManufacturer); + project.setSupplier(projectSupplier); project = qm.createProject(project, null, false); + final var bomSupplier = new OrganizationalEntity(); + bomSupplier.setName("bomSupplier"); + final var bomAuthor = new OrganizationalContact(); + bomAuthor.setName("bomAuthor"); + final var projectMetadata = new ProjectMetadata(); + projectMetadata.setProject(project); + projectMetadata.setAuthors(List.of(bomAuthor)); + projectMetadata.setSupplier(bomSupplier); + qm.persist(projectMetadata); + + final var componentSupplier = new OrganizationalEntity(); + componentSupplier.setName("componentSupplier"); var componentWithoutVuln = new Component(); componentWithoutVuln.setProject(project); componentWithoutVuln.setName("acme-lib-a"); componentWithoutVuln.setVersion("1.0.0"); + componentWithoutVuln.setSupplier(componentSupplier); componentWithoutVuln.setDirectDependencies("[]"); componentWithoutVuln = qm.createComponent(componentWithoutVuln, false); @@ -197,12 +221,26 @@ public void exportProjectAsCycloneDxInventoryTest() { "version": 1, "metadata": { "timestamp": "${json-unit.any-string}", + "authors": [ + { + "name": "bomAuthor" + } + ], "component": { "type": "application", "bom-ref": "${json-unit.matches:projectUuid}", + "supplier": { + "name": "projectSupplier" + }, "name": "acme-app", "version": "SNAPSHOT" }, + "manufacture": { + "name": "projectManufacturer" + }, + "supplier": { + "name": "bomSupplier" + }, "tools": [ { "vendor": "OWASP", @@ -215,6 +253,9 @@ public void exportProjectAsCycloneDxInventoryTest() { { "type": "library", "bom-ref": "${json-unit.matches:componentWithoutVulnUuid}", + "supplier": { + "name": "componentSupplier" + }, "name": "acme-lib-a", "version": "1.0.0" }, diff --git a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java index db344f2be..524dfe1f2 100644 --- a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java @@ -34,7 +34,10 @@ import org.dependencytrack.model.Component; import org.dependencytrack.model.ConfigPropertyConstants; import org.dependencytrack.model.ExternalReference; +import org.dependencytrack.model.OrganizationalContact; +import org.dependencytrack.model.OrganizationalEntity; import org.dependencytrack.model.Project; +import org.dependencytrack.model.ProjectMetadata; import org.dependencytrack.model.ProjectProperty; import org.dependencytrack.model.ServiceComponent; import org.dependencytrack.model.Tag; @@ -64,6 +67,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.dependencytrack.assertion.Assertions.assertConditionWithTimeout; @@ -71,6 +75,7 @@ import static org.dependencytrack.proto.notification.v1.Level.LEVEL_INFORMATIONAL; import static org.dependencytrack.proto.notification.v1.Scope.SCOPE_PORTFOLIO; import static org.dependencytrack.util.KafkaTestUtil.deserializeValue; +import static org.hamcrest.Matchers.equalTo; public class ProjectResourceTest extends ResourceTest { @@ -664,6 +669,21 @@ public void patchProjectParentNotFoundTest() { public void patchProjectSuccessfullyPatchedTest() { final var tags = Stream.of("tag1", "tag2").map(qm::createTag).collect(Collectors.toUnmodifiableList()); final var p1 = qm.createProject("ABC", "Test project", "1.0", tags, null, null, true, false); + final var projectManufacturerContact = new OrganizationalContact(); + projectManufacturerContact.setName("manufacturerContactName"); + final var projectManufacturer = new OrganizationalEntity(); + projectManufacturer.setName("manufacturerName"); + projectManufacturer.setUrls(new String[]{"https://manufacturer.example.com"}); + projectManufacturer.setContacts(List.of(projectManufacturerContact)); + p1.setManufacturer(projectManufacturer); + final var projectSupplierContact = new OrganizationalContact(); + projectSupplierContact.setName("supplierContactName"); + final var projectSupplier = new OrganizationalEntity(); + projectSupplier.setName("supplierName"); + projectSupplier.setUrls(new String[]{"https://supplier.example.com"}); + projectSupplier.setContacts(List.of(projectSupplierContact)); + p1.setSupplier(projectSupplier); + qm.persist(p1); final var jsonProject = new Project(); jsonProject.setActive(false); jsonProject.setName("new name"); @@ -673,22 +693,66 @@ public void patchProjectSuccessfullyPatchedTest() { t.setName(name); return t; }).collect(Collectors.toUnmodifiableList())); + final var jsonProjectManufacturerContact = new OrganizationalContact(); + jsonProjectManufacturerContact.setName("newManufacturerContactName"); + final var jsonProjectManufacturer = new OrganizationalEntity(); + jsonProjectManufacturer.setName("manufacturerName"); + jsonProjectManufacturer.setUrls(new String[]{"https://manufacturer.example.com"}); + jsonProjectManufacturer.setContacts(List.of(jsonProjectManufacturerContact)); + jsonProject.setManufacturer(jsonProjectManufacturer); + final var jsonProjectSupplierContact = new OrganizationalContact(); + jsonProjectSupplierContact.setName("newSupplierContactName"); + final var jsonProjectSupplier = new OrganizationalEntity(); + jsonProjectSupplier.setName("supplierName"); + jsonProjectSupplier.setUrls(new String[]{"https://supplier.example.com"}); + jsonProjectSupplier.setContacts(List.of(jsonProjectSupplierContact)); + jsonProject.setSupplier(jsonProjectSupplier); final var response = target(V1_PROJECT + "/" + p1.getUuid()) .request() .header(X_API_KEY, apiKey) .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true) .method("PATCH", Entity.json(jsonProject)); Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); - final var json = parseJsonObject(response); - Assert.assertEquals(p1.getUuid().toString(), json.getString("uuid")); - Assert.assertEquals(p1.getDescription(), json.getString("description")); - Assert.assertEquals(p1.getVersion(), json.getString("version")); - Assert.assertEquals(jsonProject.getName(), json.getString("name")); - Assert.assertEquals(jsonProject.getPublisher(), json.getString("publisher")); - Assert.assertEquals(false, json.getBoolean("active")); - final var jsonTags = json.getJsonArray("tags"); - Assert.assertEquals(1, jsonTags.size()); - Assert.assertEquals("tag4", jsonTags.get(0).asJsonObject().getString("name")); + assertThatJson(getPlainTextBody(response)) + .withMatcher("projectUuid", equalTo(p1.getUuid().toString())) + .isEqualTo(""" + { + "publisher": "new publisher", + "manufacturer": { + "name": "manufacturerName", + "urls": [ + "https://manufacturer.example.com" + ], + "contacts": [ + { + "name": "newManufacturerContactName" + } + ] + }, + "supplier": { + "name": "supplierName", + "urls": [ + "https://supplier.example.com" + ], + "contacts": [ + { + "name": "newSupplierContactName" + } + ] + }, + "name": "new name", + "description": "Test project", + "version": "1.0", + "uuid": "${json-unit.matches:projectUuid}", + "properties": [], + "tags": [ + { + "name": "tag4" + } + ], + "active": false + } + """); } @Test @@ -812,9 +876,16 @@ public void getProjectsWithoutDescendantsOfTest() { public void cloneProjectTest() { EventService.getInstance().subscribe(CloneProjectEvent.class, CloneProjectTask.class); + final var projectManufacturer = new OrganizationalEntity(); + projectManufacturer.setName("projectManufacturer"); + final var projectSupplier = new OrganizationalEntity(); + projectSupplier.setName("projectSupplier"); + final var project = new Project(); project.setName("acme-app"); project.setVersion("1.0.0"); + project.setManufacturer(projectManufacturer); + project.setSupplier(projectSupplier); project.setAccessTeams(List.of(team)); qm.persist(project); @@ -825,10 +896,24 @@ public void cloneProjectTest() { qm.createTag("tag-b") )); + final var metadataAuthor = new OrganizationalContact(); + metadataAuthor.setName("metadataAuthor"); + final var metadataSupplier = new OrganizationalEntity(); + metadataSupplier.setName("metadataSupplier"); + final var metadata = new ProjectMetadata(); + metadata.setProject(project); + metadata.setAuthors(List.of(metadataAuthor)); + metadata.setSupplier(metadataSupplier); + qm.persist(metadata); + + final var componentSupplier = new OrganizationalEntity(); + componentSupplier.setName("componentSupplier"); + final var component = new Component(); component.setProject(project); component.setName("acme-lib"); component.setVersion("2.0.0"); + component.setSupplier(componentSupplier); qm.persist(component); final var service = new ServiceComponent(); @@ -872,6 +957,10 @@ public void cloneProjectTest() { final Project clonedProject = qm.getProject("acme-app", "1.1.0"); assertThat(clonedProject).isNotNull(); assertThat(clonedProject.getUuid()).isNotEqualTo(project.getUuid()); + assertThat(clonedProject.getSupplier()).isNotNull(); + assertThat(clonedProject.getSupplier().getName()).isEqualTo("projectSupplier"); + assertThat(clonedProject.getManufacturer()).isNotNull(); + assertThat(clonedProject.getManufacturer().getName()).isEqualTo("projectManufacturer"); assertThat(clonedProject.getAccessTeams()).containsOnly(team); final List clonedProperties = qm.getProjectProperties(clonedProject); @@ -887,10 +976,19 @@ public void cloneProjectTest() { assertThat(clonedProject.getTags()).extracting(Tag::getName) .containsOnly("tag-a", "tag-b"); + final ProjectMetadata clonedMetadata = clonedProject.getMetadata(); + assertThat(clonedMetadata).isNotNull(); + assertThat(clonedMetadata.getAuthors()) + .satisfiesExactly(contact -> assertThat(contact.getName()).isEqualTo("metadataAuthor")); + assertThat(clonedMetadata.getSupplier()) + .satisfies(entity -> assertThat(entity.getName()).isEqualTo("metadataSupplier")); + assertThat(qm.getAllComponents(clonedProject)).satisfiesExactly(clonedComponent -> { assertThat(clonedComponent.getUuid()).isNotEqualTo(component.getUuid()); assertThat(clonedComponent.getName()).isEqualTo("acme-lib"); assertThat(clonedComponent.getVersion()).isEqualTo("2.0.0"); + assertThat(clonedComponent.getSupplier()).isNotNull(); + assertThat(clonedComponent.getSupplier().getName()).isEqualTo("componentSupplier"); assertThat(qm.getAllVulnerabilities(clonedComponent)).containsOnly(vuln); diff --git a/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java b/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java index 2db95d049..47e284ee0 100644 --- a/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java +++ b/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java @@ -112,6 +112,40 @@ public void informTest() throws Exception { assertThat(project.getLastBomImportFormat()).isEqualTo("CycloneDX 1.5"); assertThat(project.getExternalReferences()).isNotNull(); assertThat(project.getExternalReferences()).hasSize(4); + assertThat(project.getSupplier()).satisfies(supplier -> { + assertThat(supplier.getName()).isEqualTo("Foo Incorporated"); + assertThat(supplier.getUrls()).containsOnly("https://foo.bar.com"); + assertThat(supplier.getContacts()).satisfiesExactly(contact -> { + assertThat(contact.getName()).isEqualTo("Foo Jr."); + assertThat(contact.getEmail()).isEqualTo("foojr@bar.com"); + assertThat(contact.getPhone()).isEqualTo("123-456-7890"); + }); + }); + assertThat(project.getManufacturer()).satisfies(manufacturer -> { + assertThat(manufacturer.getName()).isEqualTo("Foo Incorporated"); + assertThat(manufacturer.getUrls()).containsOnly("https://foo.bar.com"); + assertThat(manufacturer.getContacts()).satisfiesExactly(contact -> { + assertThat(contact.getName()).isEqualTo("Foo Sr."); + assertThat(contact.getEmail()).isEqualTo("foo@bar.com"); + assertThat(contact.getPhone()).isEqualTo("800-123-4567"); + }); + }); + + assertThat(project.getMetadata()).isNotNull(); + assertThat(project.getMetadata().getAuthors()).satisfiesExactly(contact -> { + assertThat(contact.getName()).isEqualTo("Author"); + assertThat(contact.getEmail()).isEqualTo("author@example.com"); + assertThat(contact.getPhone()).isEqualTo("123-456-7890"); + }); + assertThat(project.getMetadata().getSupplier()).satisfies(manufacturer -> { + assertThat(manufacturer.getName()).isEqualTo("Foo Incorporated"); + assertThat(manufacturer.getUrls()).containsOnly("https://foo.bar.com"); + assertThat(manufacturer.getContacts()).satisfiesExactly(contact -> { + assertThat(contact.getName()).isEqualTo("Foo Jr."); + assertThat(contact.getEmail()).isEqualTo("foojr@bar.com"); + assertThat(contact.getPhone()).isEqualTo("123-456-7890"); + }); + }); final List components = qm.getAllComponents(project); assertThat(components).hasSize(1); @@ -119,6 +153,7 @@ public void informTest() throws Exception { final Component component = components.get(0); 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.getSupplier().getName()).isEqualTo("Foo Incorporated"); assertThat(component.getGroup()).isEqualTo("com.example"); assertThat(component.getName()).isEqualTo("xmlutil"); assertThat(component.getVersion()).isEqualTo("1.0.0"); diff --git a/src/test/resources/unit/bom-1.xml b/src/test/resources/unit/bom-1.xml index 209ee417c..1b3b0f026 100644 --- a/src/test/resources/unit/bom-1.xml +++ b/src/test/resources/unit/bom-1.xml @@ -1,7 +1,23 @@ + + + Author + author@example.com + 123-456-7890 + + + + Foo Incorporated + https://foo.bar.com + + Foo Jr. + foojr@bar.com + 123-456-7890 + + DependencyTrack Acme example @@ -19,10 +35,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...................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... + 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 @@ -45,5 +88,4 @@ false - - \ No newline at end of file + \ No newline at end of file From f0fdd652bbd6814124f7dc10c47fcaae80040b6f Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 9 Feb 2024 17:36:08 +0000 Subject: [PATCH 2/7] Update BomUploadProcessingTask.java --- .../org/dependencytrack/tasks/BomUploadProcessingTask.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java b/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java index a4b53e6c7..543962d9e 100644 --- a/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java +++ b/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java @@ -227,7 +227,6 @@ private void processBom(final Context ctx, final File bomFile) throws BomConsump projectMetadata = convertToProjectMetadata(cdxBom.getMetadata()); if (cdxBom.getMetadata().getComponent() != null) { metadataComponent = convertToProject(cdxBom.getMetadata(), projectMetadata); - projectMetadata.setProject(metadataComponent); components.addAll(convertComponents(cdxBom.getMetadata().getComponent().getComponents())); } } @@ -311,9 +310,10 @@ private void processBom(final Context ctx, final File bomFile) throws BomConsump final Transaction trx = pm.currentTransaction(); try { trx.begin(); - - qm.getPersistenceManager().makePersistent(projectMetadata); final Project project = processMetadataComponent(ctx, pm, metadataComponent); + if (projectMetadata != null) { + qm.getPersistenceManager().makePersistent(projectMetadata); + } final Map persistentComponents = processComponents(qm, project, components, identitiesByBomRef, bomRefsByIdentity); final Map persistentServices = @@ -419,7 +419,6 @@ private void processBom(final Context ctx, final File bomFile) throws BomConsump } }); } - // TODO: Trigger index updates } } From 16487a69ab207b9668dc8ec4e0a8d08ea651ae42 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Mon, 12 Feb 2024 15:10:22 +0000 Subject: [PATCH 3/7] Update BomUploadProcessingTask.java --- .../org/dependencytrack/tasks/BomUploadProcessingTask.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java b/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java index 543962d9e..b91033d76 100644 --- a/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java +++ b/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java @@ -311,7 +311,8 @@ private void processBom(final Context ctx, final File bomFile) throws BomConsump try { trx.begin(); final Project project = processMetadataComponent(ctx, pm, metadataComponent); - if (projectMetadata != null) { + if (projectMetadata != null && project.getMetadata() == null) { + projectMetadata.setProject(project); qm.getPersistenceManager().makePersistent(projectMetadata); } final Map persistentComponents = @@ -507,12 +508,10 @@ private static Project processMetadataComponent(final Context ctx, final Persist changed |= applyIfChanged(project, metadataComponent, Project::getExternalReferences, project::setExternalReferences); changed |= applyIfChanged(project, metadataComponent, Project::getPurl, project::setPurl); changed |= applyIfChanged(project, metadataComponent, Project::getSwidTagId, project::setSwidTagId); - changed |= applyIfChanged(project, metadataComponent, Project::getMetadata, project::setMetadata); if (changed) { pm.flush(); } } - return project; } From 865daf708b81e57b66f8310a6e512cf05826b9bc Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Mon, 12 Feb 2024 16:05:30 +0000 Subject: [PATCH 4/7] fix cdx order --- .../tasks/BomUploadProcessingTask.java | 11 ++++++++--- src/test/resources/unit/bom-1.xml | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java b/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java index b91033d76..acdd63776 100644 --- a/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java +++ b/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java @@ -311,9 +311,14 @@ private void processBom(final Context ctx, final File bomFile) throws BomConsump try { trx.begin(); final Project project = processMetadataComponent(ctx, pm, metadataComponent); - if (projectMetadata != null && project.getMetadata() == null) { - projectMetadata.setProject(project); - qm.getPersistenceManager().makePersistent(projectMetadata); + if (projectMetadata != null) { + if (project.getMetadata() == null) { + projectMetadata.setProject(project); + qm.getPersistenceManager().makePersistent(projectMetadata); + } else { + project.getMetadata().setSupplier(projectMetadata.getSupplier()); + project.getMetadata().setAuthors(projectMetadata.getAuthors()); + } } final Map persistentComponents = processComponents(qm, project, components, identitiesByBomRef, bomRefsByIdentity); diff --git a/src/test/resources/unit/bom-1.xml b/src/test/resources/unit/bom-1.xml index 1b3b0f026..e21ca0ee4 100644 --- a/src/test/resources/unit/bom-1.xml +++ b/src/test/resources/unit/bom-1.xml @@ -56,7 +56,6 @@ - Sometimes this field is long because it is composed of a list of authors...................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... Foo Incorporated https://foo.bar.com @@ -66,6 +65,7 @@ 123-456-7890 + Sometimes this field is long because it is composed of a list of authors...................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................... Example Incorporated com.example xmlutil From e2ff2aeb625b5b9aba5b7dca104b024005aeb643 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Tue, 13 Feb 2024 10:27:29 +0000 Subject: [PATCH 5/7] Update changelog-v5.3.0.xml --- src/main/resources/migration/changelog-v5.3.0.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/resources/migration/changelog-v5.3.0.xml b/src/main/resources/migration/changelog-v5.3.0.xml index 3a12b459c..25b57ec83 100644 --- a/src/main/resources/migration/changelog-v5.3.0.xml +++ b/src/main/resources/migration/changelog-v5.3.0.xml @@ -2479,15 +2479,15 @@ - - + + - + - - + + From edfbb9361957877b9803ece7ca56fb6dab4ff2e6 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Tue, 13 Feb 2024 14:52:25 +0000 Subject: [PATCH 6/7] Update changelog-v5.3.0.xml --- src/main/resources/migration/changelog-v5.3.0.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/resources/migration/changelog-v5.3.0.xml b/src/main/resources/migration/changelog-v5.3.0.xml index 25b57ec83..3a12b459c 100644 --- a/src/main/resources/migration/changelog-v5.3.0.xml +++ b/src/main/resources/migration/changelog-v5.3.0.xml @@ -2479,15 +2479,15 @@ - - + + - + - - + + From eee4f11751c8775c03f25905ceba046ea002e3a8 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Wed, 14 Feb 2024 11:46:34 +0000 Subject: [PATCH 7/7] Update changelog-v5.3.0.xml --- src/main/resources/migration/changelog-v5.3.0.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/resources/migration/changelog-v5.3.0.xml b/src/main/resources/migration/changelog-v5.3.0.xml index 3a12b459c..7ef18fe21 100644 --- a/src/main/resources/migration/changelog-v5.3.0.xml +++ b/src/main/resources/migration/changelog-v5.3.0.xml @@ -2482,6 +2482,10 @@ +