From 40c84fe717b405863beaefa3cc1b2fd24113c648 Mon Sep 17 00:00:00 2001 From: nscuro Date: Mon, 26 Feb 2024 16:45:45 +0100 Subject: [PATCH 01/34] Build against Java 21 in CI Docker base image is already bumped to 21. Relates to https://github.com/DependencyTrack/hyades/issues/1070 Signed-off-by: nscuro --- .github/workflows/_meta-build.yaml | 2 +- .github/workflows/ci-release.yaml | 2 +- .github/workflows/ci-test.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/_meta-build.yaml b/.github/workflows/_meta-build.yaml index 63bdb74a5..7361e09c5 100644 --- a/.github/workflows/_meta-build.yaml +++ b/.github/workflows/_meta-build.yaml @@ -30,7 +30,7 @@ jobs: uses: actions/setup-java@v4.0.0 with: distribution: 'temurin' - java-version: '17' + java-version: '21' cache: 'maven' - name: Setup CycloneDX CLI diff --git a/.github/workflows/ci-release.yaml b/.github/workflows/ci-release.yaml index e2280f44c..9064ae573 100644 --- a/.github/workflows/ci-release.yaml +++ b/.github/workflows/ci-release.yaml @@ -57,7 +57,7 @@ jobs: uses: actions/setup-java@v4.0.0 with: distribution: 'temurin' - java-version: '17' + java-version: '21' cache: 'maven' - name: Set Version diff --git a/.github/workflows/ci-test.yaml b/.github/workflows/ci-test.yaml index 31624e79b..1488b1739 100644 --- a/.github/workflows/ci-test.yaml +++ b/.github/workflows/ci-test.yaml @@ -37,7 +37,7 @@ jobs: uses: actions/setup-java@v4.0.0 with: distribution: 'temurin' - java-version: '17' + java-version: '21' cache: 'maven' - name: Execute unit tests From e571dddf8f26fe941a5b495e8323e5c3ddcf15c3 Mon Sep 17 00:00:00 2001 From: nscuro Date: Tue, 27 Feb 2024 13:36:04 +0100 Subject: [PATCH 02/34] Fix missing URL encoding of `\` Looks like Java 21's URL validation got a bit stricter :) Signed-off-by: nscuro --- .../dependencytrack/common/ManagedHttpClientFactoryTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/dependencytrack/common/ManagedHttpClientFactoryTest.java b/src/test/java/org/dependencytrack/common/ManagedHttpClientFactoryTest.java index 80c9de6ad..c3bcef0cb 100644 --- a/src/test/java/org/dependencytrack/common/ManagedHttpClientFactoryTest.java +++ b/src/test/java/org/dependencytrack/common/ManagedHttpClientFactoryTest.java @@ -36,7 +36,7 @@ public class ManagedHttpClientFactoryTest { @Before public void before() { - environmentVariables.set("http_proxy", "http://acme\\username:password@127.0.0.1:1080"); + environmentVariables.set("http_proxy", "http://acme%5Cusername:password@127.0.0.1:1080"); environmentVariables.set("no_proxy", "localhost:443,127.0.0.1:8080,example.com,www.example.net"); } From d1eda3dfa1fe38ce3ec7dbe01cf2664c32bbcd00 Mon Sep 17 00:00:00 2001 From: Niklas Date: Wed, 28 Feb 2024 11:11:44 +0100 Subject: [PATCH 03/34] Revert "Remove unnecessary length constraints from VARCHAR(N) columns" Signed-off-by: nscuro --- .../org/dependencytrack/model/Analysis.java | 14 +- .../model/AnalysisComment.java | 2 +- .../java/org/dependencytrack/model/Bom.java | 6 +- .../org/dependencytrack/model/Component.java | 65 ++-- .../java/org/dependencytrack/model/Cwe.java | 4 +- .../model/FindingAttribution.java | 4 +- .../model/IntegrityMetaComponent.java | 8 +- .../org/dependencytrack/model/License.java | 7 +- .../dependencytrack/model/LicenseGroup.java | 4 +- .../model/NotificationPublisher.java | 13 +- .../model/NotificationRule.java | 13 +- .../org/dependencytrack/model/Policy.java | 10 +- .../model/PolicyCondition.java | 11 +- .../model/PolicyViolation.java | 4 +- .../org/dependencytrack/model/Project.java | 30 +- .../model/ProjectProperty.java | 15 +- .../org/dependencytrack/model/Repository.java | 10 +- .../model/RepositoryMetaComponent.java | 8 +- .../model/ServiceComponent.java | 13 +- .../java/org/dependencytrack/model/Tag.java | 4 +- .../java/org/dependencytrack/model/Vex.java | 6 +- .../model/ViolationAnalysis.java | 2 +- .../model/ViolationAnalysisComment.java | 2 +- .../dependencytrack/model/Vulnerability.java | 26 +- .../model/VulnerabilityAlias.java | 16 +- .../model/VulnerabilityPolicy.java | 9 +- .../model/VulnerabilityPolicyBundle.java | 3 +- .../model/VulnerabilityScan.java | 6 +- .../model/VulnerableSoftware.java | 47 ++- .../dependencytrack/model/WorkflowState.java | 4 +- .../ModelConverterCdxToVuln.java | 3 +- .../resources/migration/changelog-main.xml | 1 - .../resources/migration/changelog-v5.4.0.xml | 297 ------------------ .../tasks/BomUploadProcessingTaskTest.java | 2 + 34 files changed, 222 insertions(+), 447 deletions(-) delete mode 100644 src/main/resources/migration/changelog-v5.4.0.xml diff --git a/src/main/java/org/dependencytrack/model/Analysis.java b/src/main/java/org/dependencytrack/model/Analysis.java index fc1256165..df5b0d773 100644 --- a/src/main/java/org/dependencytrack/model/Analysis.java +++ b/src/main/java/org/dependencytrack/model/Analysis.java @@ -69,17 +69,17 @@ public class Analysis implements Serializable { private Vulnerability vulnerability; @Persistent(defaultFetchGroup = "true") - @Column(name = "STATE", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "STATE", jdbcType = "VARCHAR", allowsNull = "false") @NotNull private AnalysisState analysisState; @Persistent(defaultFetchGroup = "true") - @Column(name = "JUSTIFICATION", jdbcType = "CLOB", allowsNull = "true") + @Column(name = "JUSTIFICATION", jdbcType = "VARCHAR", allowsNull = "true") @NotNull private AnalysisJustification analysisJustification; @Persistent(defaultFetchGroup = "true") - @Column(name = "RESPONSE", jdbcType = "CLOB", allowsNull = "true") + @Column(name = "RESPONSE", jdbcType = "VARCHAR", allowsNull = "true") @NotNull private AnalysisResponse analysisResponse; @@ -98,12 +98,12 @@ public class Analysis implements Serializable { private boolean suppressed; @Persistent(defaultFetchGroup = "true") - @Column(name = "SEVERITY", jdbcType = "CLOB") + @Column(name = "SEVERITY") @JsonProperty(value = "severity") private Severity severity; @Persistent - @Column(name = "CVSSV2VECTOR", jdbcType = "CLOB") + @Column(name = "CVSSV2VECTOR") @JsonProperty(value = "cvssV2Vector") private String cvssV2Vector; @@ -113,7 +113,7 @@ public class Analysis implements Serializable { private BigDecimal cvssV2Score; @Persistent - @Column(name = "CVSSV3VECTOR", jdbcType = "CLOB") + @Column(name = "CVSSV3VECTOR") @JsonProperty(value = "cvssV3Vector") private String cvssV3Vector; @@ -123,7 +123,7 @@ public class Analysis implements Serializable { private BigDecimal cvssV3Score; @Persistent - @Column(name = "OWASPVECTOR", jdbcType = "CLOB") + @Column(name = "OWASPVECTOR") @JsonProperty(value = "owaspVector") private String owaspVector; diff --git a/src/main/java/org/dependencytrack/model/AnalysisComment.java b/src/main/java/org/dependencytrack/model/AnalysisComment.java index b66926d9c..3aa3232aa 100644 --- a/src/main/java/org/dependencytrack/model/AnalysisComment.java +++ b/src/main/java/org/dependencytrack/model/AnalysisComment.java @@ -66,7 +66,7 @@ public class AnalysisComment implements Serializable { private String comment; @Persistent(defaultFetchGroup = "true") - @Column(name = "COMMENTER", jdbcType = "CLOB") + @Column(name = "COMMENTER") @JsonDeserialize(using = TrimmedStringDeserializer.class) private String commenter; diff --git a/src/main/java/org/dependencytrack/model/Bom.java b/src/main/java/org/dependencytrack/model/Bom.java index cf2568a53..5dba40876 100644 --- a/src/main/java/org/dependencytrack/model/Bom.java +++ b/src/main/java/org/dependencytrack/model/Bom.java @@ -76,11 +76,11 @@ public String getFormatLongName() { private Date imported; @Persistent - @Column(name = "BOM_FORMAT", jdbcType = "CLOB") + @Column(name = "BOM_FORMAT") private String bomFormat; @Persistent - @Column(name = "SPEC_VERSION", jdbcType = "CLOB") + @Column(name = "SPEC_VERSION") private String specVersion; @Persistent @@ -88,7 +88,7 @@ public String getFormatLongName() { private Integer bomVersion; @Persistent - @Column(name = "SERIAL_NUMBER", jdbcType = "CLOB") + @Column(name = "SERIAL_NUMBER") private String serialNumber; @Persistent(defaultFetchGroup = "true") diff --git a/src/main/java/org/dependencytrack/model/Component.java b/src/main/java/org/dependencytrack/model/Component.java index ec85dbb75..f0e0b65dc 100644 --- a/src/main/java/org/dependencytrack/model/Component.java +++ b/src/main/java/org/dependencytrack/model/Component.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.github.packageurl.MalformedPackageURLException; 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; @@ -49,6 +50,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; @@ -115,7 +117,8 @@ public enum FetchGroup { private String author; @Persistent - @Column(name = "PUBLISHER", jdbcType = "CLOB") + @Column(name = "PUBLISHER", jdbcType = "VARCHAR") + @Size(max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The publisher may only contain printable characters") private String publisher; @@ -125,39 +128,44 @@ public enum FetchGroup { private OrganizationalEntity supplier; @Persistent - @Column(name = "GROUP", jdbcType = "CLOB") + @Column(name = "GROUP", jdbcType = "VARCHAR") @Index(name = "COMPONENT_GROUP_IDX") + @Size(max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The group may only contain printable characters") private String group; @Persistent - @Column(name = "NAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "NAME", jdbcType = "VARCHAR", allowsNull = "false") @Index(name = "COMPONENT_NAME_IDX") @NotBlank + @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") private String name; @Persistent - @Column(name = "VERSION", jdbcType = "CLOB") + @Column(name = "VERSION", jdbcType = "VARCHAR") + @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The version may only contain printable characters") private String version; @Persistent - @Column(name = "CLASSIFIER", jdbcType = "CLOB") + @Column(name = "CLASSIFIER", jdbcType = "VARCHAR") @Index(name = "COMPONENT_CLASSIFIER_IDX") @Extension(vendorName = "datanucleus", key = "enum-check-constraint", value = "true") private Classifier classifier; @Persistent - @Column(name = "FILENAME", jdbcType = "CLOB") + @Column(name = "FILENAME", jdbcType = "VARCHAR") + @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.FS_DIRECTORY_NAME, message = "The specified filename is not valid and cannot be used as a filename") private String filename; @Persistent - @Column(name = "EXTENSION", jdbcType = "CLOB") + @Column(name = "EXTENSION", jdbcType = "VARCHAR") + @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.FS_FILE_NAME, message = "The specified filename extension is not valid and cannot be used as a extension") private String extension; @@ -236,28 +244,31 @@ public enum FetchGroup { @Persistent @Index(name = "COMPONENT_CPE_IDX") - @Column(name = "CPE", jdbcType = "CLOB") + @Column(name = "CPE") + @Size(max = 255) //Patterns obtained from https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd @Pattern(regexp = "(cpe:2\\.3:[aho\\*\\-](:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!\"#$$%&'\\(\\)\\+,/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\\*\\-]))(:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!\"#$$%&'\\(\\)\\+,/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){4})|([c][pP][eE]:/[AHOaho]?(:[A-Za-z0-9\\._\\-~%]*){0,6})", message = "The CPE must conform to the CPE v2.2 or v2.3 specification defined by NIST") private String cpe; @Persistent(defaultFetchGroup = "true") @Index(name = "COMPONENT_PURL_IDX") - @Column(name = "PURL", jdbcType = "CLOB") + @Column(name = "PURL", jdbcType = "VARCHAR", length = 1024) + @Size(max = 1024) @com.github.packageurl.validator.PackageURL @JsonDeserialize(using = TrimmedStringDeserializer.class) private String purl; @Persistent(defaultFetchGroup = "true") @Index(name = "COMPONENT_PURL_COORDINATES_IDX") - @Column(name = "PURLCOORDINATES", jdbcType = "CLOB") + @Size(max = 255) @com.github.packageurl.validator.PackageURL @JsonDeserialize(using = TrimmedStringDeserializer.class) private String purlCoordinates; // Field should contain only type, namespace, name, and version. Everything up to the qualifiers @Persistent - @Column(name = "SWIDTAGID", jdbcType = "CLOB") + @Column(name = "SWIDTAGID") @Index(name = "COMPONENT_SWID_TAGID_IDX") + @Size(max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The SWID tagId may only contain printable characters") private String swidTagId; @@ -267,19 +278,22 @@ public enum FetchGroup { private Boolean internal; @Persistent - @Column(name = "DESCRIPTION", jdbcType = "CLOB") + @Column(name = "DESCRIPTION", jdbcType = "VARCHAR", length = 1024) + @Size(max = 1024) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The description may only contain printable characters") private String description; @Persistent - @Column(name = "COPYRIGHT", jdbcType = "CLOB") + @Column(name = "COPYRIGHT", jdbcType = "VARCHAR", length = 1024) + @Size(max = 1024) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The copyright may only contain printable characters") private String copyright; @Persistent - @Column(name = "LICENSE", jdbcType = "CLOB") + @Column(name = "LICENSE", jdbcType = "VARCHAR") + @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The license may only contain printable characters") private String license; @@ -291,7 +305,8 @@ public enum FetchGroup { private String licenseExpression; @Persistent - @Column(name = "LICENSE_URL", jdbcType = "CLOB") + @Column(name = "LICENSE_URL", jdbcType = "VARCHAR") + @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.URL, message = "The license URL must be a valid URL") private String licenseUrl; @@ -400,7 +415,7 @@ public String getGroup() { } public void setGroup(String group) { - this.group = group; + this.group = StringUtils.abbreviate(group, 255); } public String getName() { @@ -408,7 +423,7 @@ public String getName() { } public void setName(String name) { - this.name = name; + this.name = StringUtils.abbreviate(name, 255); } public String getVersion() { @@ -416,7 +431,7 @@ public String getVersion() { } public void setVersion(String version) { - this.version = version; + this.version = StringUtils.abbreviate(version, 255); } public Classifier getClassifier() { @@ -432,7 +447,7 @@ public String getFilename() { } public void setFilename(String filename) { - this.filename = filename; + this.filename = StringUtils.abbreviate(filename, 255); } public String getExtension() { @@ -440,7 +455,7 @@ public String getExtension() { } public void setExtension(String extension) { - this.extension = extension; + this.extension = StringUtils.abbreviate(extension, 255); } public String getMd5() { @@ -544,7 +559,7 @@ public String getCpe() { } public void setCpe(String cpe) { - this.cpe = cpe; + this.cpe = StringUtils.abbreviate(cpe, 255); } @JsonSerialize(using = CustomPackageURLSerializer.class) @@ -619,7 +634,7 @@ public String getDescription() { } public void setDescription(String description) { - this.description = description; + this.description = StringUtils.abbreviate(description, 1024); } public String getCopyright() { @@ -627,7 +642,7 @@ public String getCopyright() { } public void setCopyright(String copyright) { - this.copyright = copyright; + this.copyright = StringUtils.abbreviate(copyright, 1024); } public String getLicense() { @@ -635,7 +650,7 @@ public String getLicense() { } public void setLicense(String license) { - this.license = license; + this.license = StringUtils.abbreviate(license, 255); } public String getLicenseExpression() { @@ -651,7 +666,7 @@ public String getLicenseUrl() { } public void setLicenseUrl(String licenseUrl) { - this.licenseUrl = licenseUrl; + this.licenseUrl = StringUtils.abbreviate(licenseUrl, 255); } public License getResolvedLicense() { diff --git a/src/main/java/org/dependencytrack/model/Cwe.java b/src/main/java/org/dependencytrack/model/Cwe.java index e2e8c6b3d..9c40774f4 100644 --- a/src/main/java/org/dependencytrack/model/Cwe.java +++ b/src/main/java/org/dependencytrack/model/Cwe.java @@ -32,6 +32,7 @@ import javax.jdo.annotations.Unique; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; /** @@ -57,7 +58,8 @@ public class Cwe implements Serializable { private int cweId; @Persistent - @Column(name = "NAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "NAME", jdbcType = "VARCHAR", allowsNull = "false") + @Size(max = 255) @NotNull @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") diff --git a/src/main/java/org/dependencytrack/model/FindingAttribution.java b/src/main/java/org/dependencytrack/model/FindingAttribution.java index de072dd3b..35aebe70d 100644 --- a/src/main/java/org/dependencytrack/model/FindingAttribution.java +++ b/src/main/java/org/dependencytrack/model/FindingAttribution.java @@ -76,11 +76,11 @@ public class FindingAttribution implements Serializable { private Vulnerability vulnerability; @Persistent - @Column(name = "ALT_ID", allowsNull = "true", jdbcType = "CLOB") + @Column(name = "ALT_ID", allowsNull = "true") private String alternateIdentifier; @Persistent - @Column(name = "REFERENCE_URL", allowsNull = "true", jdbcType = "CLOB") + @Column(name = "REFERENCE_URL", allowsNull = "true") private String referenceUrl; @Persistent(customValueStrategy = "uuid") diff --git a/src/main/java/org/dependencytrack/model/IntegrityMetaComponent.java b/src/main/java/org/dependencytrack/model/IntegrityMetaComponent.java index a3ed6ce93..d46f74a01 100644 --- a/src/main/java/org/dependencytrack/model/IntegrityMetaComponent.java +++ b/src/main/java/org/dependencytrack/model/IntegrityMetaComponent.java @@ -33,6 +33,7 @@ import javax.jdo.annotations.Unique; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.Date; @@ -74,8 +75,9 @@ public void setSha512(String sha512) { private String sha512; @Persistent - @Column(name = "PURL", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "PURL", allowsNull = "false", jdbcType = "VARCHAR", length = 1024) @Index(name = "PURL_IDX") + @Size(max = 1024) @com.github.packageurl.validator.PackageURL @JsonDeserialize(using = TrimmedStringDeserializer.class) @Unique @@ -95,12 +97,12 @@ public void setSha512(String sha512) { private Date lastFetch; @Persistent - @Column(name = "STATUS", jdbcType = "CLOB") + @Column(name = "STATUS", jdbcType = "VARCHAR", length = 64) @Extension(vendorName = "datanucleus", key = "enum-check-constraint", value = "true") private FetchStatus status; @Persistent - @Column(name = "REPOSITORY_URL", jdbcType = "CLOB") + @Column(name = "REPOSITORY_URL", jdbcType = "VARCHAR", length = 1024) private String repositoryUrl; diff --git a/src/main/java/org/dependencytrack/model/License.java b/src/main/java/org/dependencytrack/model/License.java index 3e2866f3e..3920f088a 100644 --- a/src/main/java/org/dependencytrack/model/License.java +++ b/src/main/java/org/dependencytrack/model/License.java @@ -43,6 +43,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.List; import java.util.UUID; @@ -101,10 +102,11 @@ public enum FetchGroup { * The String representation of the license name (i.e. Apache License 2.0). */ @Persistent(defaultFetchGroup = "true") - @Column(name = "NAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "NAME", allowsNull = "false") @Index(name = "LICENSE_NAME_IDX") @JsonProperty(value = "name") @NotBlank + @Size(min = 1, max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") private String name; @@ -149,11 +151,12 @@ public enum FetchGroup { * The SPDX defined licenseId (i.e. Apache-2.0). */ @Persistent(defaultFetchGroup = "true") - @Column(name = "LICENSEID", jdbcType = "CLOB") + @Column(name = "LICENSEID") @Index(name = "LICENSE_LICENSEID_IDX", unique = "true") @JsonProperty(value = "licenseId") @JsonAlias(value = "licenseExceptionId") @JsonDeserialize(using = TrimmedStringDeserializer.class) + @Size(min = 1, max = 255) @NotBlank @Pattern(regexp = RegexSequence.Definition.STRING_IDENTIFIER, message = "The licenseId may only contain alpha, numeric, and specific symbols _-.+") private String licenseId; diff --git a/src/main/java/org/dependencytrack/model/LicenseGroup.java b/src/main/java/org/dependencytrack/model/LicenseGroup.java index f0ea824be..f930434dd 100644 --- a/src/main/java/org/dependencytrack/model/LicenseGroup.java +++ b/src/main/java/org/dependencytrack/model/LicenseGroup.java @@ -37,6 +37,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.List; import java.util.UUID; @@ -61,9 +62,10 @@ public class LicenseGroup implements Serializable { * The String representation of the license group name (i.e. Copyleft). */ @Persistent - @Column(name = "NAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "NAME", allowsNull = "false") @Index(name = "LICENSEGROUP_NAME_IDX") @NotBlank + @Size(min = 1, max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") private String name; diff --git a/src/main/java/org/dependencytrack/model/NotificationPublisher.java b/src/main/java/org/dependencytrack/model/NotificationPublisher.java index 6abf51ccd..f82967a9c 100644 --- a/src/main/java/org/dependencytrack/model/NotificationPublisher.java +++ b/src/main/java/org/dependencytrack/model/NotificationPublisher.java @@ -33,6 +33,7 @@ import javax.jdo.annotations.Unique; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.UUID; @@ -72,19 +73,22 @@ public enum FetchGroup { private long id; @Persistent(defaultFetchGroup = "true") - @Column(name = "NAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "NAME", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) private String name; @Persistent(defaultFetchGroup = "true") - @Column(name = "DESCRIPTION", jdbcType = "CLOB") + @Column(name = "DESCRIPTION") + @Size(min = 0, max = 1024) @JsonDeserialize(using = TrimmedStringDeserializer.class) private String description; @Persistent(defaultFetchGroup = "true") - @Column(name = "PUBLISHER_CLASS", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "PUBLISHER_CLASS", length = 1024, allowsNull = "false") @NotBlank + @Size(min = 1, max = 1024) @JsonDeserialize(using = TrimmedStringDeserializer.class) private String publisherClass; @@ -94,8 +98,9 @@ public enum FetchGroup { private String template; @Persistent(defaultFetchGroup = "true") - @Column(name = "TEMPLATE_MIME_TYPE", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "TEMPLATE_MIME_TYPE", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) private String templateMimeType; diff --git a/src/main/java/org/dependencytrack/model/NotificationRule.java b/src/main/java/org/dependencytrack/model/NotificationRule.java index bdceb9eba..d0fc9d53c 100644 --- a/src/main/java/org/dependencytrack/model/NotificationRule.java +++ b/src/main/java/org/dependencytrack/model/NotificationRule.java @@ -43,6 +43,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -73,8 +74,9 @@ public class NotificationRule implements Serializable { * The String representation of the name of the notification. */ @Persistent - @Column(name = "NAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "NAME", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") private String name; @@ -100,12 +102,12 @@ public class NotificationRule implements Serializable { private boolean logSuccessfulPublish; @Persistent(defaultFetchGroup = "true") - @Column(name = "SCOPE", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "SCOPE", jdbcType = "VARCHAR", allowsNull = "false") @NotNull private NotificationScope scope; @Persistent(defaultFetchGroup = "true") - @Column(name = "NOTIFICATION_LEVEL", jdbcType = "CLOB") + @Column(name = "NOTIFICATION_LEVEL", jdbcType = "VARCHAR") private NotificationLevel notificationLevel; @Persistent(table = "NOTIFICATIONRULE_PROJECTS", defaultFetchGroup = "true") @@ -121,11 +123,12 @@ public class NotificationRule implements Serializable { private List teams; @Persistent - @Column(name = "NOTIFY_ON", jdbcType = "CLOB") + @Column(name = "NOTIFY_ON", length = 1024) private String notifyOn; @Persistent - @Column(name = "MESSAGE", jdbcType = "CLOB") + @Column(name = "MESSAGE", length = 1024) + @Size(max = 1024) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The message may only contain printable characters") private String message; diff --git a/src/main/java/org/dependencytrack/model/Policy.java b/src/main/java/org/dependencytrack/model/Policy.java index 1cca49c7b..9d3dad806 100644 --- a/src/main/java/org/dependencytrack/model/Policy.java +++ b/src/main/java/org/dependencytrack/model/Policy.java @@ -37,6 +37,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -73,9 +74,10 @@ public enum ViolationState { * The String representation of the policy name. */ @Persistent - @Column(name = "NAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "NAME", allowsNull = "false") @Index(name = "POLICY_NAME_IDX") @NotBlank + @Size(min = 1, max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") private String name; @@ -83,8 +85,9 @@ public enum ViolationState { * The operator to use when evaluating conditions. */ @Persistent - @Column(name = "OPERATOR", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "OPERATOR", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The operator may only contain printable characters") private Operator operator; @@ -92,8 +95,9 @@ public enum ViolationState { * The state the policy should trigger upon violation. */ @Persistent - @Column(name = "VIOLATIONSTATE", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "VIOLATIONSTATE", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The violation state may only contain printable characters") private ViolationState violationState; diff --git a/src/main/java/org/dependencytrack/model/PolicyCondition.java b/src/main/java/org/dependencytrack/model/PolicyCondition.java index 131b50df1..c2dfcb3b3 100644 --- a/src/main/java/org/dependencytrack/model/PolicyCondition.java +++ b/src/main/java/org/dependencytrack/model/PolicyCondition.java @@ -32,6 +32,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.UUID; @@ -97,25 +98,29 @@ public enum Subject { private Policy policy; @Persistent - @Column(name = "OPERATOR", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "OPERATOR", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The operator may only contain printable characters") private Operator operator; @Persistent - @Column(name = "SUBJECT", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "SUBJECT", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The subject may only contain printable characters") private Subject subject; @Persistent @Column(name = "VALUE", allowsNull = "false", jdbcType = "CLOB") @NotBlank + @Size(min = 1) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The value may only contain printable characters") private String value; @Persistent - @Column(name = "VIOLATIONTYPE", jdbcType = "CLOB", allowsNull = "true") + @Column(name = "VIOLATIONTYPE", allowsNull = "true") + @Size(min = 1, max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The violation type may only contain printable characters") private PolicyViolation.Type violationType; diff --git a/src/main/java/org/dependencytrack/model/PolicyViolation.java b/src/main/java/org/dependencytrack/model/PolicyViolation.java index 3ae3a65da..2820da971 100644 --- a/src/main/java/org/dependencytrack/model/PolicyViolation.java +++ b/src/main/java/org/dependencytrack/model/PolicyViolation.java @@ -32,6 +32,7 @@ import javax.jdo.annotations.Unique; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.Date; import java.util.UUID; @@ -84,7 +85,8 @@ public enum Type { private Date timestamp; @Persistent - @Column(name = "TEXT", jdbcType = "CLOB") + @Column(name = "TEXT") + @Size(min = 1, max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The text may only contain printable characters") private String text; diff --git a/src/main/java/org/dependencytrack/model/Project.java b/src/main/java/org/dependencytrack/model/Project.java index 540b3092c..bafdd4de5 100644 --- a/src/main/java/org/dependencytrack/model/Project.java +++ b/src/main/java/org/dependencytrack/model/Project.java @@ -53,6 +53,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; @@ -141,13 +142,15 @@ public enum FetchGroup { private long id; @Persistent - @Column(name = "AUTHOR", jdbcType = "CLOB") + @Column(name = "AUTHOR", jdbcType = "VARCHAR") + @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The author may only contain printable characters") private String author; @Persistent - @Column(name = "PUBLISHER", jdbcType = "CLOB") + @Column(name = "PUBLISHER", jdbcType = "VARCHAR") + @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The publisher may only contain printable characters") private String publisher; @@ -163,42 +166,45 @@ public enum FetchGroup { private OrganizationalEntity supplier; @Persistent - @Column(name = "GROUP", jdbcType = "CLOB") + @Column(name = "GROUP", jdbcType = "VARCHAR") @Index(name = "PROJECT_GROUP_IDX") + @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The group may only contain printable characters") private String group; @Persistent @Index(name = "PROJECT_NAME_IDX") - @Column(name = "NAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "NAME", jdbcType = "VARCHAR", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") private String name; @Persistent - @Column(name = "DESCRIPTION", jdbcType = "CLOB") + @Column(name = "DESCRIPTION", jdbcType = "VARCHAR") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The description may only contain printable characters") private String description; @Persistent @Index(name = "PROJECT_VERSION_IDX") - @Column(name = "VERSION", jdbcType = "CLOB") + @Column(name = "VERSION", jdbcType = "VARCHAR") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The version may only contain printable characters") private String version; @Persistent - @Column(name = "CLASSIFIER", jdbcType = "CLOB") + @Column(name = "CLASSIFIER", jdbcType = "VARCHAR") @Index(name = "PROJECT_CLASSIFIER_IDX") @Extension(vendorName = "datanucleus", key = "enum-check-constraint", value = "true") private Classifier classifier; @Persistent @Index(name = "PROJECT_CPE_IDX") - @Column(name = "CPE", jdbcType = "CLOB") + @Column(name = "CPE") + @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) //Patterns obtained from https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd @Pattern(regexp = "(cpe:2\\.3:[aho\\*\\-](:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!\"#$$%&'\\(\\)\\+,/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\\*\\-]))(:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!\"#$$%&'\\(\\)\\+,/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){4})|([c][pP][eE]:/[AHOaho]?(:[A-Za-z0-9\\._\\-~%]*){0,6})", message = "The CPE must conform to the CPE v2.2 or v2.3 specification defined by NIST") @@ -206,14 +212,16 @@ public enum FetchGroup { @Persistent @Index(name = "PROJECT_PURL_IDX") - @Column(name = "PURL", jdbcType = "CLOB") + @Column(name = "PURL") + @Size(max = 255) @com.github.packageurl.validator.PackageURL @JsonDeserialize(using = TrimmedStringDeserializer.class) private String purl; @Persistent @Index(name = "PROJECT_SWID_TAGID_IDX") - @Column(name = "SWIDTAGID", jdbcType = "CLOB") + @Column(name = "SWIDTAGID") + @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The SWID tagId may only contain printable characters") private String swidTagId; @@ -260,7 +268,7 @@ public enum FetchGroup { */ @Persistent @Index(name = "PROJECT_LASTBOMIMPORT_FORMAT_IDX") - @Column(name = "LAST_BOM_IMPORTED_FORMAT", jdbcType = "CLOB") + @Column(name = "LAST_BOM_IMPORTED_FORMAT") private String lastBomImportFormat; /** diff --git a/src/main/java/org/dependencytrack/model/ProjectProperty.java b/src/main/java/org/dependencytrack/model/ProjectProperty.java index 03ad4fb49..3bf3842da 100644 --- a/src/main/java/org/dependencytrack/model/ProjectProperty.java +++ b/src/main/java/org/dependencytrack/model/ProjectProperty.java @@ -33,6 +33,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; /** @@ -58,32 +59,36 @@ public class ProjectProperty implements IConfigProperty, Serializable { private Project project; @Persistent - @Column(name = "GROUPNAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "GROUPNAME", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = "[\\P{Cc}]+", message = "The groupName must not contain control characters") private String groupName; @Persistent - @Column(name = "PROPERTYNAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "PROPERTYNAME", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = "[\\P{Cc}]+", message = "The propertyName must not contain control characters") private String propertyName; @Persistent - @Column(name = "PROPERTYVALUE", jdbcType = "CLOB") + @Column(name = "PROPERTYVALUE", length = 1024) + @Size(min = 0, max = 1024) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = "[\\P{Cc}]+", message = "The propertyValue must not contain control characters") private String propertyValue; @Persistent - @Column(name = "PROPERTYTYPE", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "PROPERTYTYPE", jdbcType = "VARCHAR", allowsNull = "false") @NotNull private PropertyType propertyType; @Persistent - @Column(name = "DESCRIPTION", jdbcType = "CLOB") + @Column(name = "DESCRIPTION") + @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = "[\\P{Cc}]+", message = "The description must not contain control characters") private String description; diff --git a/src/main/java/org/dependencytrack/model/Repository.java b/src/main/java/org/dependencytrack/model/Repository.java index ed6575121..2d62f1b91 100644 --- a/src/main/java/org/dependencytrack/model/Repository.java +++ b/src/main/java/org/dependencytrack/model/Repository.java @@ -55,18 +55,18 @@ public class Repository implements Serializable { private long id; @Persistent(defaultFetchGroup = "true") - @Column(name = "TYPE", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "TYPE", jdbcType = "VARCHAR", allowsNull = "false") @NotNull private RepositoryType type; @Persistent - @Column(name = "IDENTIFIER", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "IDENTIFIER", allowsNull = "false") @NotBlank @JsonDeserialize(using = TrimmedStringDeserializer.class) private String identifier; @Persistent - @Column(name = "URL", jdbcType = "CLOB") + @Column(name = "URL") @NotBlank @JsonDeserialize(using = TrimmedStringDeserializer.class) private String url; @@ -93,12 +93,12 @@ public class Repository implements Serializable { private Boolean authenticationRequired; @Persistent - @Column(name = "USERNAME", jdbcType = "CLOB") + @Column(name = "USERNAME") @JsonDeserialize(using = TrimmedStringDeserializer.class) private String username; @Persistent - @Column(name = "PASSWORD", jdbcType = "CLOB") + @Column(name = "PASSWORD") private String password; @Persistent(customValueStrategy = "uuid") diff --git a/src/main/java/org/dependencytrack/model/RepositoryMetaComponent.java b/src/main/java/org/dependencytrack/model/RepositoryMetaComponent.java index d7f098707..1fde0c3a1 100644 --- a/src/main/java/org/dependencytrack/model/RepositoryMetaComponent.java +++ b/src/main/java/org/dependencytrack/model/RepositoryMetaComponent.java @@ -53,7 +53,7 @@ public class RepositoryMetaComponent implements Serializable { * This is an indirect representation of a the Package URL "type" field. */ @Persistent(defaultFetchGroup = "true") - @Column(name = "REPOSITORY_TYPE", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "REPOSITORY_TYPE", jdbcType = "VARCHAR", allowsNull = "false") @NotNull private RepositoryType repositoryType; @@ -61,14 +61,14 @@ public class RepositoryMetaComponent implements Serializable { * This is a representation of the Package URL "namespace" field. */ @Persistent - @Column(name = "NAMESPACE", jdbcType = "CLOB") + @Column(name = "NAMESPACE") private String namespace; /** * This is a representation of the Package URL "name" field. */ @Persistent - @Column(name = "NAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "NAME", allowsNull = "false") @NotNull private String name; @@ -76,7 +76,7 @@ public class RepositoryMetaComponent implements Serializable { * The latest version of the component. */ @Persistent - @Column(name = "LATEST_VERSION", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "LATEST_VERSION", allowsNull = "false") @NotNull private String latestVersion; diff --git a/src/main/java/org/dependencytrack/model/ServiceComponent.java b/src/main/java/org/dependencytrack/model/ServiceComponent.java index 69f0cc067..eeae2042b 100644 --- a/src/main/java/org/dependencytrack/model/ServiceComponent.java +++ b/src/main/java/org/dependencytrack/model/ServiceComponent.java @@ -42,6 +42,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; @@ -88,25 +89,29 @@ public enum FetchGroup { private OrganizationalEntity provider; @Persistent - @Column(name = "GROUP", jdbcType = "CLOB") + @Column(name = "GROUP", jdbcType = "VARCHAR") + @Size(max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The group may only contain printable characters") private String group; @Persistent - @Column(name = "NAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "NAME", jdbcType = "VARCHAR", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") private String name; @Persistent - @Column(name = "VERSION", jdbcType = "CLOB") + @Column(name = "VERSION", jdbcType = "VARCHAR") + @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The version may only contain printable characters") private String version; @Persistent - @Column(name = "DESCRIPTION", jdbcType = "CLOB") + @Column(name = "DESCRIPTION", jdbcType = "VARCHAR", length = 1024) + @Size(max = 1024) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The description may only contain printable characters") private String description; diff --git a/src/main/java/org/dependencytrack/model/Tag.java b/src/main/java/org/dependencytrack/model/Tag.java index 0c4dad227..c35de98c1 100644 --- a/src/main/java/org/dependencytrack/model/Tag.java +++ b/src/main/java/org/dependencytrack/model/Tag.java @@ -33,6 +33,7 @@ import javax.jdo.annotations.PrimaryKey; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.List; import java.util.Objects; @@ -55,8 +56,9 @@ public class Tag implements Serializable { private long id; @Persistent - @Column(name = "NAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "NAME", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") private String name; diff --git a/src/main/java/org/dependencytrack/model/Vex.java b/src/main/java/org/dependencytrack/model/Vex.java index 0b6b98bcc..490109a2b 100644 --- a/src/main/java/org/dependencytrack/model/Vex.java +++ b/src/main/java/org/dependencytrack/model/Vex.java @@ -74,11 +74,11 @@ public String getFormatLongName() { private Date imported; @Persistent - @Column(name = "VEX_FORMAT", jdbcType = "CLOB") + @Column(name = "VEX_FORMAT") private String vexFormat; @Persistent - @Column(name = "SPEC_VERSION", jdbcType = "CLOB") + @Column(name = "SPEC_VERSION") private String specVersion; @Persistent @@ -86,7 +86,7 @@ public String getFormatLongName() { private Integer vexVersion; @Persistent - @Column(name = "SERIAL_NUMBER", jdbcType = "CLOB") + @Column(name = "SERIAL_NUMBER") private String serialNumber; @Persistent(defaultFetchGroup = "true") diff --git a/src/main/java/org/dependencytrack/model/ViolationAnalysis.java b/src/main/java/org/dependencytrack/model/ViolationAnalysis.java index 8847b2c03..41101fdab 100644 --- a/src/main/java/org/dependencytrack/model/ViolationAnalysis.java +++ b/src/main/java/org/dependencytrack/model/ViolationAnalysis.java @@ -67,7 +67,7 @@ public class ViolationAnalysis implements Serializable { private PolicyViolation policyViolation; @Persistent(defaultFetchGroup = "true") - @Column(name = "STATE", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "STATE", jdbcType = "VARCHAR", allowsNull = "false") @NotNull private ViolationAnalysisState analysisState; diff --git a/src/main/java/org/dependencytrack/model/ViolationAnalysisComment.java b/src/main/java/org/dependencytrack/model/ViolationAnalysisComment.java index d2d70f12a..ebe8206d6 100644 --- a/src/main/java/org/dependencytrack/model/ViolationAnalysisComment.java +++ b/src/main/java/org/dependencytrack/model/ViolationAnalysisComment.java @@ -66,7 +66,7 @@ public class ViolationAnalysisComment implements Serializable { private String comment; @Persistent(defaultFetchGroup = "true") - @Column(name = "COMMENTER", jdbcType = "CLOB") + @Column(name = "COMMENTER") @JsonDeserialize(using = TrimmedStringDeserializer.class) private String commenter; diff --git a/src/main/java/org/dependencytrack/model/Vulnerability.java b/src/main/java/org/dependencytrack/model/Vulnerability.java index 837e60437..4fedf1bc5 100644 --- a/src/main/java/org/dependencytrack/model/Vulnerability.java +++ b/src/main/java/org/dependencytrack/model/Vulnerability.java @@ -49,6 +49,7 @@ import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; import java.math.BigDecimal; import java.util.ArrayList; @@ -136,35 +137,38 @@ public static boolean isKnownSource(String source) { private long id; @Persistent - @Column(name = "VULNID", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "VULNID", allowsNull = "false") @Index(name = "VULNERABILITY_VULNID_IDX") @NotBlank + @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The vulnerability ID may only contain printable characters") private String vulnId; @Persistent - @Column(name = "SOURCE", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "SOURCE", allowsNull = "false") @NotBlank + @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The source may only contain printable characters") private String source; @Persistent - @Column(name = "FRIENDLYVULNID", jdbcType = "CLOB") + @Column(name = "FRIENDLYVULNID") @NotBlank + @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The friendly vulnerability ID may only contain printable characters") private String friendlyVulnId; @Persistent - @Column(name = "TITLE", jdbcType = "CLOB") + @Column(name = "TITLE") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The title may only contain printable characters") private String title; @Persistent - @Column(name = "SUBTITLE", jdbcType = "CLOB") + @Column(name = "SUBTITLE") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The subtitle may only contain printable characters") private String subTitle; @@ -218,7 +222,7 @@ public static boolean isKnownSource(String source) { private Date updated; @Persistent(defaultFetchGroup = "true") - @Column(name = "CWES", jdbcType = "CLOB") + @Column(name = "CWES") @Convert(CollectionIntegerConverter.class) @JsonSerialize(using = CweSerializer.class) @JsonDeserialize(using = CweDeserializer.class) @@ -237,7 +241,7 @@ public static boolean isKnownSource(String source) { private BigDecimal cvssV2ExploitabilitySubScore; @Persistent - @Column(name = "CVSSV2VECTOR", jdbcType = "CLOB") + @Column(name = "CVSSV2VECTOR") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The CVSSv2 Vector may only contain printable characters") private String cvssV2Vector; @@ -255,7 +259,7 @@ public static boolean isKnownSource(String source) { private BigDecimal cvssV3ExploitabilitySubScore; @Persistent - @Column(name = "CVSSV3VECTOR", jdbcType = "CLOB") + @Column(name = "CVSSV3VECTOR") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The CVSSv3 Vector may only contain printable characters") private String cvssV3Vector; @@ -273,7 +277,7 @@ public static boolean isKnownSource(String source) { private BigDecimal owaspRRBusinessImpactScore; @Persistent - @Column(name = "OWASPRRVECTOR", jdbcType = "CLOB") + @Column(name = "OWASPRRVECTOR") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The OWASP RR Vector may only contain printable characters") private String owaspRRVector; @@ -284,13 +288,13 @@ public static boolean isKnownSource(String source) { private Severity severity; @Persistent - @Column(name = "VULNERABLEVERSIONS", jdbcType = "CLOB") + @Column(name = "VULNERABLEVERSIONS") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The vulnerable versions may only contain printable characters") private String vulnerableVersions; @Persistent - @Column(name = "PATCHEDVERSIONS", jdbcType = "CLOB") + @Column(name = "PATCHEDVERSIONS") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The patched versions may only contain printable characters") private String patchedVersions; diff --git a/src/main/java/org/dependencytrack/model/VulnerabilityAlias.java b/src/main/java/org/dependencytrack/model/VulnerabilityAlias.java index 3122c4a4a..487eb7629 100644 --- a/src/main/java/org/dependencytrack/model/VulnerabilityAlias.java +++ b/src/main/java/org/dependencytrack/model/VulnerabilityAlias.java @@ -56,56 +56,56 @@ public class VulnerabilityAlias implements Serializable { private long id; @Persistent - @Column(name = "INTERNAL_ID", jdbcType = "CLOB") + @Column(name = "INTERNAL_ID") @Index(name = "VULNERABILITYALIAS_INTERNAL_ID_IDX") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The internalId field may only contain printable characters") private String internalId; @Persistent - @Column(name = "CVE_ID", jdbcType = "CLOB") + @Column(name = "CVE_ID") @Index(name = "VULNERABILITYALIAS_CVE_ID_IDX") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The cveId field may only contain printable characters") private String cveId; @Persistent - @Column(name = "GHSA_ID", jdbcType = "CLOB") + @Column(name = "GHSA_ID") @Index(name = "VULNERABILITYALIAS_GHSA_ID_IDX") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The ghsaId field may only contain printable characters") private String ghsaId; @Persistent - @Column(name = "SONATYPE_ID", jdbcType = "CLOB") + @Column(name = "SONATYPE_ID") @Index(name = "VULNERABILITYALIAS_SONATYPE_ID_IDX") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The sonatypeId field may only contain printable characters") private String sonatypeId; @Persistent - @Column(name = "OSV_ID", jdbcType = "CLOB") + @Column(name = "OSV_ID") @Index(name = "VULNERABILITYALIAS_OSV_ID_IDX") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The osvId field may only contain printable characters") private String osvId; @Persistent - @Column(name = "SNYK_ID", jdbcType = "CLOB") + @Column(name = "SNYK_ID") @Index(name = "VULNERABILITYALIAS_SNYK_ID_IDX") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The snykId field may only contain printable characters") private String snykId; @Persistent - @Column(name = "GSD_ID", jdbcType = "CLOB") + @Column(name = "GSD_ID") @Index(name = "VULNERABILITYALIAS_GSD_ID_IDX") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The gsdId field may only contain printable characters") private String gsdId; @Persistent - @Column(name = "VULNDB_ID", jdbcType = "CLOB") + @Column(name = "VULNDB_ID") @Index(name = "VULNERABILITYALIAS_VULNDB_ID_IDX") @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS_PLUS, message = "The vulnDbId field may only contain printable characters") diff --git a/src/main/java/org/dependencytrack/model/VulnerabilityPolicy.java b/src/main/java/org/dependencytrack/model/VulnerabilityPolicy.java index b5993a509..205ae1ee9 100644 --- a/src/main/java/org/dependencytrack/model/VulnerabilityPolicy.java +++ b/src/main/java/org/dependencytrack/model/VulnerabilityPolicy.java @@ -15,6 +15,7 @@ import javax.jdo.annotations.PrimaryKey; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; import java.io.Serializable; import java.util.Date; import java.util.List; @@ -30,19 +31,21 @@ public class VulnerabilityPolicy implements Serializable { private long id; @Persistent - @Column(name = "NAME", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "NAME", allowsNull = "false") @Index(name = "VULNERABILITY_POLICY_NAME_IDX", unique = "true") @NotBlank + @Size(min = 1, max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") private String name; @Persistent - @Column(name = "DESCRIPTION", allowsNull = "true", jdbcType = "CLOB") + @Column(name = "DESCRIPTION", allowsNull = "true") + @Size(max = 4096) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The description may only contain printable characters") private String description; @Persistent - @Column(name = "AUTHOR", allowsNull = "true", jdbcType = "CLOB") + @Column(name = "AUTHOR", allowsNull = "true", jdbcType = "VARCHAR") @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The author may only contain printable characters") private String author; diff --git a/src/main/java/org/dependencytrack/model/VulnerabilityPolicyBundle.java b/src/main/java/org/dependencytrack/model/VulnerabilityPolicyBundle.java index 7164836b4..b79612f84 100644 --- a/src/main/java/org/dependencytrack/model/VulnerabilityPolicyBundle.java +++ b/src/main/java/org/dependencytrack/model/VulnerabilityPolicyBundle.java @@ -25,8 +25,9 @@ public class VulnerabilityPolicyBundle implements Serializable { private long id; @Persistent - @Column(name = "URL", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "URL", allowsNull = "false") @NotBlank + @Size(min = 1, max = 2048) private String url; @Persistent diff --git a/src/main/java/org/dependencytrack/model/VulnerabilityScan.java b/src/main/java/org/dependencytrack/model/VulnerabilityScan.java index 49660f265..40c827d6e 100644 --- a/src/main/java/org/dependencytrack/model/VulnerabilityScan.java +++ b/src/main/java/org/dependencytrack/model/VulnerabilityScan.java @@ -36,14 +36,14 @@ public enum TargetType { */ @Persistent @Unique(name = "VULNERABILITY_SCAN_TOKEN_IDX") - @Column(name = "TOKEN", allowsNull = "false", jdbcType = "CLOB") + @Column(name = "TOKEN", allowsNull = "false") private String token; /** * The type of the entity targeted by this scan. */ @Persistent - @Column(name = "TARGET_TYPE", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "TARGET_TYPE", jdbcType = "VARCHAR", allowsNull = "false") private TargetType targetType; /** @@ -57,7 +57,7 @@ public enum TargetType { * The overall status of this scan. */ @Persistent - @Column(name = "STATUS", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "STATUS", jdbcType = "VARCHAR", allowsNull = "false") private Status status; /** diff --git a/src/main/java/org/dependencytrack/model/VulnerableSoftware.java b/src/main/java/org/dependencytrack/model/VulnerableSoftware.java index d9fc3450e..129b65106 100644 --- a/src/main/java/org/dependencytrack/model/VulnerableSoftware.java +++ b/src/main/java/org/dependencytrack/model/VulnerableSoftware.java @@ -63,99 +63,98 @@ public class VulnerableSoftware implements ICpe, Serializable { private long id; @Persistent - @Column(name = "PURL", jdbcType = "CLOB") + @Column(name = "PURL", jdbcType = "VARCHAR") private String purl; @Persistent - @Column(name = "PURL_TYPE", jdbcType = "CLOB") + @Column(name = "PURL_TYPE", jdbcType = "VARCHAR") private String purlType; @Persistent - @Column(name = "PURL_NAMESPACE", jdbcType = "CLOB") + @Column(name = "PURL_NAMESPACE", jdbcType = "VARCHAR") private String purlNamespace; @Persistent - @Column(name = "PURL_NAME", jdbcType = "CLOB") + @Column(name = "PURL_NAME", jdbcType = "VARCHAR") private String purlName; @Persistent - @Column(name = "PURL_VERSION", jdbcType = "CLOB") + @Column(name = "PURL_VERSION", jdbcType = "VARCHAR") private String purlVersion; @Persistent - @Column(name = "PURL_QUALIFIERS", jdbcType = "CLOB") + @Column(name = "PURL_QUALIFIERS", jdbcType = "VARCHAR") private String purlQualifiers; @Persistent - @Column(name = "PURL_SUBPATH", jdbcType = "CLOB") + @Column(name = "PURL_SUBPATH", jdbcType = "VARCHAR") private String purlSubpath; @Persistent - @Column(name = "CPE22", jdbcType = "CLOB") + @Column(name = "CPE22", jdbcType = "VARCHAR") private String cpe22; @Persistent - @Column(name = "CPE23", jdbcType = "CLOB") + @Column(name = "CPE23", jdbcType = "VARCHAR") private String cpe23; @Persistent - @Column(name = "PART", jdbcType = "CLOB") + @Column(name = "PART", jdbcType = "VARCHAR") private String part; @Persistent - @Column(name = "VENDOR", jdbcType = "CLOB") + @Column(name = "VENDOR", jdbcType = "VARCHAR") private String vendor; @Persistent - @Column(name = "PRODUCT", jdbcType = "CLOB") + @Column(name = "PRODUCT", jdbcType = "VARCHAR") private String product; @Persistent - @Column(name = "VERSION", jdbcType = "CLOB") private String version; @Persistent - @Column(name = "UPDATE", jdbcType = "CLOB") + @Column(name = "UPDATE", jdbcType = "VARCHAR") private String update; @Persistent - @Column(name = "EDITION", jdbcType = "CLOB") + @Column(name = "EDITION", jdbcType = "VARCHAR") private String edition; @Persistent - @Column(name = "LANGUAGE", jdbcType = "CLOB") + @Column(name = "LANGUAGE", jdbcType = "VARCHAR") private String language; @Persistent - @Column(name = "SWEDITION", jdbcType = "CLOB") + @Column(name = "SWEDITION", jdbcType = "VARCHAR") private String swEdition; @Persistent - @Column(name = "TARGETSW", jdbcType = "CLOB") + @Column(name = "TARGETSW", jdbcType = "VARCHAR") private String targetSw; @Persistent - @Column(name = "TARGETHW", jdbcType = "CLOB") + @Column(name = "TARGETHW", jdbcType = "VARCHAR") private String targetHw; @Persistent - @Column(name = "OTHER", jdbcType = "CLOB") + @Column(name = "OTHER", jdbcType = "VARCHAR") private String other; @Persistent - @Column(name = "VERSIONENDEXCLUDING", jdbcType = "CLOB") + @Column(name = "VERSIONENDEXCLUDING") private String versionEndExcluding; @Persistent - @Column(name = "VERSIONENDINCLUDING", jdbcType = "CLOB") + @Column(name = "VERSIONENDINCLUDING") private String versionEndIncluding; @Persistent - @Column(name = "VERSIONSTARTEXCLUDING", jdbcType = "CLOB") + @Column(name = "VERSIONSTARTEXCLUDING") private String versionStartExcluding; @Persistent - @Column(name = "VERSIONSTARTINCLUDING", jdbcType = "CLOB") + @Column(name = "VERSIONSTARTINCLUDING") private String versionStartIncluding; @Persistent diff --git a/src/main/java/org/dependencytrack/model/WorkflowState.java b/src/main/java/org/dependencytrack/model/WorkflowState.java index 5f25afe8b..01e48eb2f 100644 --- a/src/main/java/org/dependencytrack/model/WorkflowState.java +++ b/src/main/java/org/dependencytrack/model/WorkflowState.java @@ -44,13 +44,13 @@ public class WorkflowState implements Serializable { private Date updatedAt; @Persistent - @Column(name = "STEP", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "STEP", jdbcType = "VARCHAR", length = 64, allowsNull = "false") @NotNull @Extension(vendorName = "datanucleus", key = "enum-check-constraint", value = "true") private WorkflowStep step; @Persistent - @Column(name = "STATUS", jdbcType = "CLOB", allowsNull = "false") + @Column(name = "STATUS", jdbcType = "VARCHAR", length = 64, allowsNull = "false") @NotNull @Extension(vendorName = "datanucleus", key = "enum-check-constraint", value = "true") private WorkflowStatus status; diff --git a/src/main/java/org/dependencytrack/parser/dependencytrack/ModelConverterCdxToVuln.java b/src/main/java/org/dependencytrack/parser/dependencytrack/ModelConverterCdxToVuln.java index b980dfabe..dae4eece0 100644 --- a/src/main/java/org/dependencytrack/parser/dependencytrack/ModelConverterCdxToVuln.java +++ b/src/main/java/org/dependencytrack/parser/dependencytrack/ModelConverterCdxToVuln.java @@ -1,5 +1,6 @@ package org.dependencytrack.parser.dependencytrack; +import org.apache.commons.lang3.StringUtils; import org.cyclonedx.proto.v1_4.Bom; import org.cyclonedx.proto.v1_4.ScoreMethod; import org.cyclonedx.proto.v1_4.Source; @@ -47,7 +48,7 @@ public static Vulnerability convert(final QueryManager qm, final Bom bom, if (cycloneVuln.getPropertiesCount() != 0) { var titleProperty = cycloneVuln.getProperties(0); if (titleProperty.getName().equals(TITLE_PROPERTY_NAME) && titleProperty.hasValue()) { - vuln.setTitle(titleProperty.getValue()); + vuln.setTitle(StringUtils.abbreviate(titleProperty.getValue(), 255)); } } if (cycloneVuln.hasDescription()) { diff --git a/src/main/resources/migration/changelog-main.xml b/src/main/resources/migration/changelog-main.xml index 5ab794a8e..58a4a9b67 100644 --- a/src/main/resources/migration/changelog-main.xml +++ b/src/main/resources/migration/changelog-main.xml @@ -10,6 +10,5 @@ http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd"> - \ No newline at end of file diff --git a/src/main/resources/migration/changelog-v5.4.0.xml b/src/main/resources/migration/changelog-v5.4.0.xml deleted file mode 100644 index 6f92a7e18..000000000 --- a/src/main/resources/migration/changelog-v5.4.0.xml +++ /dev/null @@ -1,297 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ALTER TABLE "AFFECTEDVERSIONATTRIBUTION" DROP CONSTRAINT IF EXISTS "AFFECTEDVERSIONATTRIBUTION_SOURCE_check"; - ALTER TABLE "AFFECTEDVERSIONATTRIBUTION" ADD CONSTRAINT "AFFECTEDVERSIONATTRIBUTION_SOURCE_check" - CHECK ("SOURCE"::TEXT = ANY(ARRAY['NVD', 'NPM', 'GITHUB', 'VULNDB', 'OSSINDEX', 'RETIREJS', 'INTERNAL', 'OSV', 'SNYK'])); - - ALTER TABLE "ANALYSIS" DROP CONSTRAINT IF EXISTS "ANALYSIS_STATE_check"; - ALTER TABLE "ANALYSIS" ADD CONSTRAINT "ANALYSIS_STATE_check" - CHECK ("STATE"::TEXT = ANY(ARRAY['EXPLOITABLE', 'IN_TRIAGE', 'FALSE_POSITIVE', 'NOT_AFFECTED', 'RESOLVED', 'NOT_SET'])); - - ALTER TABLE "ANALYSIS" DROP CONSTRAINT IF EXISTS "ANALYSIS_JUSTIFICATION_check"; - ALTER TABLE "ANALYSIS" ADD CONSTRAINT "ANALYSIS_JUSTIFICATION_check" - CHECK ("JUSTIFICATION" IS NULL OR "JUSTIFICATION"::TEXT = ANY(ARRAY['CODE_NOT_PRESENT', 'CODE_NOT_REACHABLE', 'REQUIRES_CONFIGURATION', 'REQUIRES_DEPENDENCY', 'REQUIRES_ENVIRONMENT', 'PROTECTED_BY_COMPILER', 'PROTECTED_AT_RUNTIME', 'PROTECTED_AT_PERIMETER', 'PROTECTED_BY_MITIGATING_CONTROL', 'NOT_SET'])); - - ALTER TABLE "ANALYSIS" DROP CONSTRAINT IF EXISTS "ANALYSIS_RESPONSE_check"; - ALTER TABLE "ANALYSIS" ADD CONSTRAINT "ANALYSIS_RESPONSE_check" - CHECK ("RESPONSE" IS NULL OR "RESPONSE"::TEXT = ANY(ARRAY['CAN_NOT_FIX', 'WILL_NOT_FIX', 'UPDATE', 'ROLLBACK', 'WORKAROUND_AVAILABLE', 'NOT_SET'])); - - ALTER TABLE "ANALYSIS" DROP CONSTRAINT IF EXISTS "ANALYSIS_SEVERITY_check"; - ALTER TABLE "ANALYSIS" ADD CONSTRAINT "ANALYSIS_SEVERITY_check" - CHECK ("SEVERITY" IS NULL OR "SEVERITY"::TEXT = ANY(ARRAY['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO', 'UNASSIGNED'])); - - ALTER TABLE "FINDINGATTRIBUTION" DROP CONSTRAINT IF EXISTS "FINDINGATTRIBUTION_ANALYZERIDENTITY_check"; - ALTER TABLE "FINDINGATTRIBUTION" ADD CONSTRAINT "FINDINGATTRIBUTION_ANALYZERIDENTITY_check" - CHECK ("ANALYZERIDENTITY"::TEXT = ANY(ARRAY['INTERNAL_ANALYZER', 'OSSINDEX_ANALYZER', 'NPM_AUDIT_ANALYZER', 'VULNDB_ANALYZER', 'SNYK_ANALYZER', 'NONE'])); - - ALTER TABLE "INTEGRITY_ANALYSIS" DROP CONSTRAINT IF EXISTS "INTEGRITY_ANALYSIS_STATUS_check"; - ALTER TABLE "INTEGRITY_ANALYSIS" ADD CONSTRAINT "INTEGRITY_ANALYSIS_STATUS_check" - CHECK ("INTEGRITY_CHECK_STATUS"::TEXT = ANY(ARRAY['HASH_MATCH_PASSED', 'HASH_MATCH_FAILED', 'HASH_MATCH_UNKNOWN', 'COMPONENT_MISSING_HASH', 'COMPONENT_MISSING_HASH_AND_MATCH_UNKNOWN'])); - - ALTER TABLE "INTEGRITY_ANALYSIS" DROP CONSTRAINT IF EXISTS "INTEGRITY_ANALYSIS_MD5_STATUS_check"; - ALTER TABLE "INTEGRITY_ANALYSIS" ADD CONSTRAINT "INTEGRITY_ANALYSIS_MD5_STATUS_check" - CHECK ("MD5_HASH_MATCH_STATUS"::TEXT = ANY(ARRAY['HASH_MATCH_PASSED', 'HASH_MATCH_FAILED', 'HASH_MATCH_UNKNOWN', 'COMPONENT_MISSING_HASH', 'COMPONENT_MISSING_HASH_AND_MATCH_UNKNOWN'])); - - ALTER TABLE "INTEGRITY_ANALYSIS" DROP CONSTRAINT IF EXISTS "INTEGRITY_ANALYSIS_SHA1_STATUS_check"; - ALTER TABLE "INTEGRITY_ANALYSIS" ADD CONSTRAINT "INTEGRITY_ANALYSIS_SHA1_STATUS_check" - CHECK ("SHA1_HASH_MATCH_STATUS"::TEXT = ANY(ARRAY['HASH_MATCH_PASSED', 'HASH_MATCH_FAILED', 'HASH_MATCH_UNKNOWN', 'COMPONENT_MISSING_HASH', 'COMPONENT_MISSING_HASH_AND_MATCH_UNKNOWN'])); - - ALTER TABLE "INTEGRITY_ANALYSIS" DROP CONSTRAINT IF EXISTS "INTEGRITY_ANALYSIS_SHA256_STATUS_check"; - ALTER TABLE "INTEGRITY_ANALYSIS" ADD CONSTRAINT "INTEGRITY_ANALYSIS_SHA256_STATUS_check" - CHECK ("SHA256_HASH_MATCH_STATUS"::TEXT = ANY(ARRAY['HASH_MATCH_PASSED', 'HASH_MATCH_FAILED', 'HASH_MATCH_UNKNOWN', 'COMPONENT_MISSING_HASH', 'COMPONENT_MISSING_HASH_AND_MATCH_UNKNOWN'])); - - ALTER TABLE "INTEGRITY_ANALYSIS" DROP CONSTRAINT IF EXISTS "INTEGRITY_ANALYSIS_SHA512_STATUS_check"; - ALTER TABLE "INTEGRITY_ANALYSIS" ADD CONSTRAINT "INTEGRITY_ANALYSIS_SHA512_STATUS_check" - CHECK ("SHA512_HASH_MATCH_STATUS"::TEXT = ANY(ARRAY['HASH_MATCH_PASSED', 'HASH_MATCH_FAILED', 'HASH_MATCH_UNKNOWN', 'COMPONENT_MISSING_HASH', 'COMPONENT_MISSING_HASH_AND_MATCH_UNKNOWN'])); - - ALTER TABLE "NOTIFICATIONRULE" DROP CONSTRAINT IF EXISTS "NOTIFICATION_LEVEL_check"; - ALTER TABLE "NOTIFICATIONRULE" ADD CONSTRAINT "NOTIFICATION_LEVEL_check" - CHECK ("NOTIFICATION_LEVEL"::TEXT = ANY(ARRAY['INFORMATIONAL', 'WARNING', 'ERROR'])); - - ALTER TABLE "NOTIFICATIONRULE" DROP CONSTRAINT IF EXISTS "NOTIFICATION_SCOPE_check"; - ALTER TABLE "NOTIFICATIONRULE" ADD CONSTRAINT "NOTIFICATION_SCOPE_check" - CHECK ("SCOPE"::TEXT = ANY(ARRAY['SYSTEM', 'PORTFOLIO'])); - - ALTER TABLE "POLICY" DROP CONSTRAINT IF EXISTS "POLICY_OPERATOR_check"; - ALTER TABLE "POLICY" ADD CONSTRAINT "POLICY_OPERATOR_check" - CHECK ("OPERATOR"::TEXT = ANY(ARRAY['ALL', 'ANY'])); - - ALTER TABLE "POLICY" DROP CONSTRAINT IF EXISTS "POLICY_VIOLATIONSTATE_check"; - ALTER TABLE "POLICY" ADD CONSTRAINT "POLICY_VIOLATIONSTATE_check" - CHECK ("VIOLATIONSTATE"::TEXT = ANY(ARRAY['INFO', 'WARN', 'FAIL'])); - - ALTER TABLE "POLICYCONDITION" DROP CONSTRAINT IF EXISTS "POLICYCONDITION_OPERATOR_check"; - ALTER TABLE "POLICYCONDITION" ADD CONSTRAINT "POLICYCONDITION_OPERATOR_check" - CHECK ("OPERATOR"::TEXT = ANY(ARRAY['IS', 'IS_NOT', 'MATCHES', 'NO_MATCH', 'NUMERIC_GREATER_THAN', 'NUMERIC_LESS_THAN', 'NUMERIC_EQUAL', 'NUMERIC_NOT_EQUAL', 'NUMERIC_GREATER_THAN_OR_EQUAL', 'NUMERIC_LESSER_THAN_OR_EQUAL', 'CONTAINS_ALL', 'CONTAINS_ANY'])); - - ALTER TABLE "POLICYCONDITION" DROP CONSTRAINT IF EXISTS "POLICYCONDITION_SUBJECT_check"; - ALTER TABLE "POLICYCONDITION" ADD CONSTRAINT "POLICYCONDITION_SUBJECT_check" - CHECK ("SUBJECT"::TEXT = ANY(ARRAY['AGE', 'COORDINATES', 'CPE', 'EXPRESSION', 'LICENSE', 'LICENSE_GROUP', 'PACKAGE_URL', 'SEVERITY', 'SWID_TAGID', 'VERSION', 'COMPONENT_HASH', 'CWE', 'VULNERABILITY_ID', 'VERSION_DISTANCE'])); - - ALTER TABLE "POLICYCONDITION" DROP CONSTRAINT IF EXISTS "POLICYCONDITION_VIOLATIONTYPE_check"; - ALTER TABLE "POLICYCONDITION" ADD CONSTRAINT "POLICYCONDITION_VIOLATIONTYPE_check" - CHECK ("VIOLATIONTYPE"::TEXT = ANY(ARRAY['LICENSE', 'SECURITY', 'OPERATIONAL'])); - - ALTER TABLE "POLICYVIOLATION" DROP CONSTRAINT IF EXISTS "POLICYVIOLATION_TYPE_check"; - ALTER TABLE "POLICYVIOLATION" ADD CONSTRAINT "POLICYVIOLATION_TYPE_check" - CHECK ("TYPE"::TEXT = ANY(ARRAY['LICENSE', 'SECURITY', 'OPERATIONAL'])); - - ALTER TABLE "PROJECT_PROPERTY" DROP CONSTRAINT IF EXISTS "PROJECT_PROPERTY_TYPE_check"; - ALTER TABLE "PROJECT_PROPERTY" ADD CONSTRAINT "PROJECT_PROPERTY_TYPE_check" - CHECK ("PROPERTYTYPE"::TEXT = ANY(ARRAY['BOOLEAN', 'INTEGER', 'NUMBER', 'STRING', 'ENCRYPTEDSTRING', 'TIMESTAMP', 'URL', 'UUID'])); - - ALTER TABLE "REPOSITORY" DROP CONSTRAINT IF EXISTS "REPOSITORY_TYPE_check"; - ALTER TABLE "REPOSITORY" ADD CONSTRAINT "REPOSITORY_TYPE_check" - CHECK ("TYPE"::TEXT = ANY(ARRAY['MAVEN', 'NPM', 'GEM', 'PYPI', 'NUGET', 'HEX', 'COMPOSER', 'CARGO', 'GO_MODULES', 'CPAN', 'GITHUB', 'UNSUPPORTED'])); - - ALTER TABLE "REPOSITORY_META_COMPONENT" DROP CONSTRAINT IF EXISTS "REPOSITORY_META_COMPONENT_REPOSITORY_TYPE_check"; - ALTER TABLE "REPOSITORY_META_COMPONENT" ADD CONSTRAINT "REPOSITORY_META_COMPONENT_REPOSITORY_TYPE_check" - CHECK ("REPOSITORY_TYPE"::TEXT = ANY(ARRAY['MAVEN', 'NPM', 'GEM', 'PYPI', 'NUGET', 'HEX', 'COMPOSER', 'CARGO', 'GO_MODULES', 'CPAN', 'GITHUB', 'UNSUPPORTED'])); - - ALTER TABLE "VIOLATIONANALYSIS" DROP CONSTRAINT IF EXISTS "VIOLATIONANALYSIS_STATE_check"; - ALTER TABLE "VIOLATIONANALYSIS" ADD CONSTRAINT "VIOLATIONANALYSIS_STATE_check" - CHECK ("STATE"::TEXT = ANY(ARRAY['APPROVED', 'REJECTED', 'NOT_SET'])); - - ALTER TABLE "VULNERABILITY" DROP CONSTRAINT IF EXISTS "VULNERABILITY_SEVERITY_check"; - ALTER TABLE "VULNERABILITY" ADD CONSTRAINT "VULNERABILITY_SEVERITY_check" - CHECK ("SEVERITY"::TEXT = ANY(ARRAY['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO', 'UNASSIGNED'])); - - ALTER TABLE "VULNERABILITYSCAN" DROP CONSTRAINT IF EXISTS "VULNERABILITYSCAN_STATUS_check"; - ALTER TABLE "VULNERABILITYSCAN" ADD CONSTRAINT "VULNERABILITYSCAN_STATUS_check" - CHECK ("STATUS"::TEXT = ANY(ARRAY['UNKNOWN', 'IN_PROGRESS', 'COMPLETED', 'FAILED'])); - - ALTER TABLE "VULNERABILITYSCAN" DROP CONSTRAINT IF EXISTS "VULNERABILITYSCAN_TARGET_TYPE_check"; - ALTER TABLE "VULNERABILITYSCAN" ADD CONSTRAINT "VULNERABILITYSCAN_TARGET_TYPE_check" - CHECK ("TARGET_TYPE"::TEXT = ANY(ARRAY['COMPONENT', 'PROJECT'])); - - - \ No newline at end of file diff --git a/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java b/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java index 997366759..86c1c8cd3 100644 --- a/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java +++ b/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java @@ -18,6 +18,7 @@ */ package org.dependencytrack.tasks; +import com.github.packageurl.PackageURL; import org.apache.kafka.clients.producer.ProducerRecord; import org.dependencytrack.PersistenceCapableTest; import org.dependencytrack.event.BomUploadEvent; @@ -216,6 +217,7 @@ public void informTestWithComponentAlreadyExistsForIntegrityCheck() throws Excep final var bomUploadEvent = new BomUploadEvent(qm.detach(Project.class, project.getId()), createTempBomFile("bom-1.xml")); qm.createWorkflowSteps(bomUploadEvent.getChainIdentifier()); + PackageURL packageUrl = new PackageURL("pkg:maven/com.example/xmlutil@1.0.0?download_url=https%3A%2F%2Fon-premises.url%2Frepository%2Fnpm%2F%40babel%2Fhelper-split-export-declaration%2Fhelper-split-export-declaration%2Fhelper-split-export-declaration%2Fhelper-split-export-declaration%2Fhelper-split-export-declaration-7.18.6.tgz"); var integrityMeta = new IntegrityMetaComponent(); integrityMeta.setPurl("pkg:maven/com.example/xmlutil@1.0.0?download_url=https%3A%2F%2Fon-premises.url%2Frepository%2Fnpm%2F%40babel%2Fhelper-split-export-declaration%2Fhelper-split-export-declaration%2Fhelper-split-export-declaration%2Fhelper-split-export-declaration%2Fhelper-split-export-declaration-7.18.6.tgz"); integrityMeta.setStatus(FetchStatus.IN_PROGRESS); From beb34ed9c33fa1f86638bbd4284661bee7c4bcc1 Mon Sep 17 00:00:00 2001 From: nscuro Date: Wed, 14 Feb 2024 13:06:54 +0100 Subject: [PATCH 04/34] Ingest `metadata.tools` from BOMs and make it available in CEL policies The internal model is aligned with CycloneDX v1.5, in that it differentiates between tools that are components, and tools that are services: https://cyclonedx.org/docs/1.5/json/#tab-pane_metadata_tools_oneOf_i0 When ingesting BOMs following v1.4 or older of the CycloneDX specification, `metadata.tools` array items will be converted to `metadata.tools.components`. For the time being, tools are persisted as JSON column in the `PROJECT_METADATA` table. As such, tools will not be analyzed for vulnerabilities or other kinds of risk. Tool components and services are treated as subsets of the internal `Component` and `ServiceComponent` models. This subset property is enforced via Jackson's `@JsonView`s, such that only specific fields are considered when serializing and deserializing to and from JSON. Tools are made available in CEL policy expressions under `project.metadata.tools.components`. Tool components use the existing `v1.Component` type, which means that functions like `matches_version` can be used on them. Signed-off-by: nscuro --- .../org/dependencytrack/model/Component.java | 24 ++ .../model/DataClassification.java | 4 + .../model/ExternalReference.java | 4 + .../org/dependencytrack/model/JsonViews.java | 16 + .../model/OrganizationalContact.java | 4 + .../model/OrganizationalEntity.java | 4 + .../model/ProjectMetadata.java | 14 + .../model/ServiceComponent.java | 11 + .../java/org/dependencytrack/model/Tools.java | 12 + .../model/mapping/PolicyProtoMapper.java | 80 +++++ .../parser/cyclonedx/util/ModelConverter.java | 84 ++++- .../converter/AbstractJsonConverter.java | 38 ++- .../converter/ToolsJsonConverter.java | 25 ++ .../policy/cel/CelCommonPolicyLibrary.java | 3 + .../policy/cel/CelPolicyEngine.java | 48 --- .../cel/CelVulnerabilityPolicyEvaluator.java | 6 + .../policy/cel/definition/CelPolicyTypes.java | 3 + .../policy/cel/mapping/ProjectProjection.java | 3 - .../policy/cel/persistence/CelPolicyDao.java | 12 + .../CelPolicyProjectRowMapper.java | 39 ++- .../tasks/BomUploadProcessingTask.java | 100 +++--- .../dependencytrack/policy/v1/policy.proto | 12 + .../resources/migration/changelog-main.xml | 1 + .../resources/migration/changelog-v5.4.0.xml | 16 + .../model/mapping/PolicyProtoMapperTest.java | 2 +- .../converter/ToolsJsonConverterTest.java | 305 ++++++++++++++++++ .../policy/cel/CelPolicyEngineTest.java | 107 +++++- .../tasks/BomUploadProcessingTaskTest.java | 57 ++++ .../unit/bom-metadata-tool-deprecated.json | 26 ++ .../resources/unit/bom-metadata-tool.json | 47 +++ 30 files changed, 998 insertions(+), 109 deletions(-) create mode 100644 src/main/java/org/dependencytrack/model/JsonViews.java create mode 100644 src/main/java/org/dependencytrack/model/Tools.java create mode 100644 src/main/java/org/dependencytrack/persistence/converter/ToolsJsonConverter.java create mode 100644 src/main/resources/migration/changelog-v5.4.0.xml create mode 100644 src/test/java/org/dependencytrack/persistence/converter/ToolsJsonConverterTest.java create mode 100644 src/test/resources/unit/bom-metadata-tool-deprecated.json create mode 100644 src/test/resources/unit/bom-metadata-tool.json diff --git a/src/main/java/org/dependencytrack/model/Component.java b/src/main/java/org/dependencytrack/model/Component.java index f0e0b65dc..cc33f9531 100644 --- a/src/main/java/org/dependencytrack/model/Component.java +++ b/src/main/java/org/dependencytrack/model/Component.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.github.packageurl.MalformedPackageURLException; @@ -114,17 +115,20 @@ public enum FetchGroup { @Persistent @Column(name = "AUTHOR", jdbcType = "CLOB") @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The author may only contain printable characters") + @JsonView(JsonViews.MetadataTools.class) private String author; @Persistent @Column(name = "PUBLISHER", jdbcType = "VARCHAR") @Size(max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The publisher may only contain printable characters") + @JsonView(JsonViews.MetadataTools.class) private String publisher; @Persistent(defaultFetchGroup = "true") @Convert(OrganizationalEntityJsonConverter.class) @Column(name = "SUPPLIER", jdbcType = "CLOB", allowsNull = "true") + @JsonView(JsonViews.MetadataTools.class) private OrganizationalEntity supplier; @Persistent @@ -132,6 +136,7 @@ public enum FetchGroup { @Index(name = "COMPONENT_GROUP_IDX") @Size(max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The group may only contain printable characters") + @JsonView(JsonViews.MetadataTools.class) private String group; @Persistent @@ -141,6 +146,7 @@ public enum FetchGroup { @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") + @JsonView(JsonViews.MetadataTools.class) private String name; @Persistent @@ -148,12 +154,14 @@ public enum FetchGroup { @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The version may only contain printable characters") + @JsonView(JsonViews.MetadataTools.class) private String version; @Persistent @Column(name = "CLASSIFIER", jdbcType = "VARCHAR") @Index(name = "COMPONENT_CLASSIFIER_IDX") @Extension(vendorName = "datanucleus", key = "enum-check-constraint", value = "true") + @JsonView(JsonViews.MetadataTools.class) private Classifier classifier; @Persistent @@ -174,72 +182,84 @@ public enum FetchGroup { @Index(name = "COMPONENT_MD5_IDX") @Column(name = "MD5", jdbcType = "VARCHAR", length = 32) @Pattern(regexp = "^[0-9a-fA-F]{32}$", message = "The MD5 hash must be a valid 32 character HEX number") + @JsonView(JsonViews.MetadataTools.class) private String md5; @Persistent @Index(name = "COMPONENT_SHA1_IDX") @Column(name = "SHA1", jdbcType = "VARCHAR", length = 40) @Pattern(regexp = "^[0-9a-fA-F]{40}$", message = "The SHA1 hash must be a valid 40 character HEX number") + @JsonView(JsonViews.MetadataTools.class) private String sha1; @Persistent @Index(name = "COMPONENT_SHA256_IDX") @Column(name = "SHA_256", jdbcType = "VARCHAR", length = 64) @Pattern(regexp = "^[0-9a-fA-F]{64}$", message = "The SHA-256 hash must be a valid 64 character HEX number") + @JsonView(JsonViews.MetadataTools.class) private String sha256; @Persistent @Index(name = "COMPONENT_SHA384_IDX") @Column(name = "SHA_384", jdbcType = "VARCHAR", length = 96) @Pattern(regexp = "^[0-9a-fA-F]{96}$", message = "The SHA-384 hash must be a valid 96 character HEX number") + @JsonView(JsonViews.MetadataTools.class) private String sha384; @Persistent @Index(name = "COMPONENT_SHA512_IDX") @Column(name = "SHA_512", jdbcType = "VARCHAR", length = 128) @Pattern(regexp = "^[0-9a-fA-F]{128}$", message = "The SHA-512 hash must be a valid 128 character HEX number") + @JsonView(JsonViews.MetadataTools.class) private String sha512; @Persistent @Index(name = "COMPONENT_SHA3_256_IDX") @Column(name = "SHA3_256", jdbcType = "VARCHAR", length = 64) @Pattern(regexp = "^[0-9a-fA-F]{64}$", message = "The SHA3-256 hash must be a valid 64 character HEX number") + @JsonView(JsonViews.MetadataTools.class) private String sha3_256; @Persistent @Index(name = "COMPONENT_SHA3_384_IDX") @Column(name = "SHA3_384", jdbcType = "VARCHAR", length = 96) @Pattern(regexp = "^[0-9a-fA-F]{96}$", message = "The SHA3-384 hash must be a valid 96 character HEX number") + @JsonView(JsonViews.MetadataTools.class) private String sha3_384; @Persistent @Index(name = "COMPONENT_SHA3_512_IDX") @Column(name = "SHA3_512", jdbcType = "VARCHAR", length = 128) @Pattern(regexp = "^[0-9a-fA-F]{128}$", message = "The SHA3-512 hash must be a valid 128 character HEX number") + @JsonView(JsonViews.MetadataTools.class) private String sha3_512; @Persistent @Index(name = "COMPONENT_BLAKE2B_256_IDX") @Column(name = "BLAKE2B_256", jdbcType = "VARCHAR", length = 64) @Pattern(regexp = RegexSequence.Definition.HASH_SHA256, message = "The BLAKE2b hash must be a valid 64 character HEX number") + @JsonView(JsonViews.MetadataTools.class) private String blake2b_256; @Persistent @Index(name = "COMPONENT_BLAKE2B_384_IDX") @Column(name = "BLAKE2B_384", jdbcType = "VARCHAR", length = 96) @Pattern(regexp = RegexSequence.Definition.HASH_SHA384, message = "The BLAKE2b hash must be a valid 96 character HEX number") + @JsonView(JsonViews.MetadataTools.class) private String blake2b_384; @Persistent @Index(name = "COMPONENT_BLAKE2B_512_IDX") @Column(name = "BLAKE2B_512", jdbcType = "VARCHAR", length = 128) @Pattern(regexp = RegexSequence.Definition.HASH_SHA512, message = "The BLAKE2b hash must be a valid 128 character HEX number") + @JsonView(JsonViews.MetadataTools.class) private String blake2b_512; @Persistent @Index(name = "COMPONENT_BLAKE3_IDX") @Column(name = "BLAKE3", jdbcType = "VARCHAR", length = 255) @Pattern(regexp = RegexSequence.Definition.HEXADECIMAL, message = "The BLAKE3 hash must be a valid HEX number") + @JsonView(JsonViews.MetadataTools.class) private String blake3; @Persistent @@ -248,6 +268,7 @@ public enum FetchGroup { @Size(max = 255) //Patterns obtained from https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd @Pattern(regexp = "(cpe:2\\.3:[aho\\*\\-](:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!\"#$$%&'\\(\\)\\+,/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\\*\\-]))(:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!\"#$$%&'\\(\\)\\+,/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){4})|([c][pP][eE]:/[AHOaho]?(:[A-Za-z0-9\\._\\-~%]*){0,6})", message = "The CPE must conform to the CPE v2.2 or v2.3 specification defined by NIST") + @JsonView(JsonViews.MetadataTools.class) private String cpe; @Persistent(defaultFetchGroup = "true") @@ -256,6 +277,7 @@ public enum FetchGroup { @Size(max = 1024) @com.github.packageurl.validator.PackageURL @JsonDeserialize(using = TrimmedStringDeserializer.class) + @JsonView(JsonViews.MetadataTools.class) private String purl; @Persistent(defaultFetchGroup = "true") @@ -270,6 +292,7 @@ public enum FetchGroup { @Index(name = "COMPONENT_SWID_TAGID_IDX") @Size(max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The SWID tagId may only contain printable characters") + @JsonView(JsonViews.MetadataTools.class) private String swidTagId; @Persistent @@ -323,6 +346,7 @@ public enum FetchGroup { @Persistent(defaultFetchGroup = "true") @Column(name = "EXTERNAL_REFERENCES") @Serialized + @JsonView(JsonViews.MetadataTools.class) private List externalReferences; @Persistent diff --git a/src/main/java/org/dependencytrack/model/DataClassification.java b/src/main/java/org/dependencytrack/model/DataClassification.java index 5092e777d..8238dc5ce 100644 --- a/src/main/java/org/dependencytrack/model/DataClassification.java +++ b/src/main/java/org/dependencytrack/model/DataClassification.java @@ -19,6 +19,7 @@ package org.dependencytrack.model; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonView; import java.io.Serializable; @@ -50,7 +51,10 @@ private Direction(String name) { } } + @JsonView(JsonViews.MetadataTools.class) private Direction direction; + + @JsonView(JsonViews.MetadataTools.class) private String name; public Direction getDirection() { diff --git a/src/main/java/org/dependencytrack/model/ExternalReference.java b/src/main/java/org/dependencytrack/model/ExternalReference.java index 1bd2925ac..e1b7a344b 100644 --- a/src/main/java/org/dependencytrack/model/ExternalReference.java +++ b/src/main/java/org/dependencytrack/model/ExternalReference.java @@ -21,6 +21,7 @@ import alpine.common.validation.RegexSequence; import alpine.server.json.TrimmedStringDeserializer; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import javax.validation.constraints.NotBlank; @@ -38,14 +39,17 @@ public class ExternalReference implements Serializable { private static final long serialVersionUID = -5885851731192037664L; + @JsonView(JsonViews.MetadataTools.class) private org.cyclonedx.model.ExternalReference.Type type; @NotBlank @JsonDeserialize(using = TrimmedStringDeserializer.class) + @JsonView(JsonViews.MetadataTools.class) private String url; @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The comment may only contain printable characters") + @JsonView(JsonViews.MetadataTools.class) private String comment; public org.cyclonedx.model.ExternalReference.Type getType() { diff --git a/src/main/java/org/dependencytrack/model/JsonViews.java b/src/main/java/org/dependencytrack/model/JsonViews.java new file mode 100644 index 000000000..9401cae31 --- /dev/null +++ b/src/main/java/org/dependencytrack/model/JsonViews.java @@ -0,0 +1,16 @@ +package org.dependencytrack.model; + +import com.fasterxml.jackson.annotation.JsonView; + +/** + * Marker interfaces to be used in conjunction with Jackson's {@link JsonView} annotation. + */ +public class JsonViews { + + /** + * Marks fields to be included when (de-)serializing {@link Tools}. + */ + public interface MetadataTools { + } + +} diff --git a/src/main/java/org/dependencytrack/model/OrganizationalContact.java b/src/main/java/org/dependencytrack/model/OrganizationalContact.java index 347bfd89e..ddd069009 100644 --- a/src/main/java/org/dependencytrack/model/OrganizationalContact.java +++ b/src/main/java/org/dependencytrack/model/OrganizationalContact.java @@ -20,6 +20,7 @@ import alpine.server.json.TrimmedStringDeserializer; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.io.Serializable; @@ -37,12 +38,15 @@ public class OrganizationalContact implements Serializable { private static final long serialVersionUID = -1026863376484187244L; @JsonDeserialize(using = TrimmedStringDeserializer.class) + @JsonView(JsonViews.MetadataTools.class) private String name; @JsonDeserialize(using = TrimmedStringDeserializer.class) + @JsonView(JsonViews.MetadataTools.class) private String email; @JsonDeserialize(using = TrimmedStringDeserializer.class) + @JsonView(JsonViews.MetadataTools.class) private String phone; public String getName() { diff --git a/src/main/java/org/dependencytrack/model/OrganizationalEntity.java b/src/main/java/org/dependencytrack/model/OrganizationalEntity.java index e8f682bac..2ab46493c 100644 --- a/src/main/java/org/dependencytrack/model/OrganizationalEntity.java +++ b/src/main/java/org/dependencytrack/model/OrganizationalEntity.java @@ -21,6 +21,7 @@ import alpine.server.json.TrimmedStringArrayDeserializer; import alpine.server.json.TrimmedStringDeserializer; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.io.Serializable; @@ -41,11 +42,14 @@ public class OrganizationalEntity implements Serializable { private static final long serialVersionUID = 5333594855427723634L; @JsonDeserialize(using = TrimmedStringDeserializer.class) + @JsonView(JsonViews.MetadataTools.class) private String name; @JsonDeserialize(using = TrimmedStringArrayDeserializer.class) + @JsonView(JsonViews.MetadataTools.class) private String[] urls; + @JsonView(JsonViews.MetadataTools.class) private List contacts; public String getName() { diff --git a/src/main/java/org/dependencytrack/model/ProjectMetadata.java b/src/main/java/org/dependencytrack/model/ProjectMetadata.java index ddc71647e..5378d91dd 100644 --- a/src/main/java/org/dependencytrack/model/ProjectMetadata.java +++ b/src/main/java/org/dependencytrack/model/ProjectMetadata.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import org.dependencytrack.persistence.converter.OrganizationalContactsJsonConverter; import org.dependencytrack.persistence.converter.OrganizationalEntityJsonConverter; +import org.dependencytrack.persistence.converter.ToolsJsonConverter; import javax.jdo.annotations.Column; import javax.jdo.annotations.Convert; @@ -66,6 +67,11 @@ public class ProjectMetadata { @Column(name = "AUTHORS", jdbcType = "CLOB", allowsNull = "true") private List authors; + @Persistent(defaultFetchGroup = "true") + @Convert(ToolsJsonConverter.class) + @Column(name = "TOOLS", jdbcType = "CLOB", allowsNull = "true") + private Tools tools; + public long getId() { return id; } @@ -98,4 +104,12 @@ public void setAuthors(final List authors) { this.authors = authors; } + public Tools getTools() { + return tools; + } + + public void setTools(final Tools tools) { + this.tools = tools; + } + } \ No newline at end of file diff --git a/src/main/java/org/dependencytrack/model/ServiceComponent.java b/src/main/java/org/dependencytrack/model/ServiceComponent.java index eeae2042b..5f34b968c 100644 --- a/src/main/java/org/dependencytrack/model/ServiceComponent.java +++ b/src/main/java/org/dependencytrack/model/ServiceComponent.java @@ -23,6 +23,7 @@ import alpine.server.json.TrimmedStringDeserializer; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import javax.jdo.annotations.Column; @@ -86,12 +87,14 @@ public enum FetchGroup { @Persistent(defaultFetchGroup = "true") @Column(name = "PROVIDER_ID") @Serialized + @JsonView(JsonViews.MetadataTools.class) private OrganizationalEntity provider; @Persistent @Column(name = "GROUP", jdbcType = "VARCHAR") @Size(max = 255) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The group may only contain printable characters") + @JsonView(JsonViews.MetadataTools.class) private String group; @Persistent @@ -100,6 +103,7 @@ public enum FetchGroup { @Size(min = 1, max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The name may only contain printable characters") + @JsonView(JsonViews.MetadataTools.class) private String name; @Persistent @@ -107,6 +111,7 @@ public enum FetchGroup { @Size(max = 255) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The version may only contain printable characters") + @JsonView(JsonViews.MetadataTools.class) private String version; @Persistent @@ -114,25 +119,30 @@ public enum FetchGroup { @Size(max = 1024) @JsonDeserialize(using = TrimmedStringDeserializer.class) @Pattern(regexp = RegexSequence.Definition.PRINTABLE_CHARS, message = "The description may only contain printable characters") + @JsonView(JsonViews.MetadataTools.class) private String description; @Persistent(defaultFetchGroup = "true") @Serialized @Column(name = "ENDPOINTS", jdbcType = "LONGVARBINARY") @JsonDeserialize(using = TrimmedStringArrayDeserializer.class) + @JsonView(JsonViews.MetadataTools.class) private String[] endpoints; @Persistent @Column(name = "AUTHENTICATED") + @JsonView(JsonViews.MetadataTools.class) private Boolean authenticated; @Persistent @Column(name = "X_TRUST_BOUNDARY") + @JsonView(JsonViews.MetadataTools.class) private Boolean crossesTrustBoundary; @Persistent(defaultFetchGroup = "true") @Column(name = "DATA") @Serialized + @JsonView(JsonViews.MetadataTools.class) private List data; //TODO add license support once Component license support is refactored @@ -140,6 +150,7 @@ public enum FetchGroup { @Persistent(defaultFetchGroup = "true") @Column(name = "EXTERNAL_REFERENCES") @Serialized + @JsonView(JsonViews.MetadataTools.class) private List externalReferences; @Persistent diff --git a/src/main/java/org/dependencytrack/model/Tools.java b/src/main/java/org/dependencytrack/model/Tools.java new file mode 100644 index 000000000..0d0753a74 --- /dev/null +++ b/src/main/java/org/dependencytrack/model/Tools.java @@ -0,0 +1,12 @@ +package org.dependencytrack.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonView; + +import java.util.List; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public record Tools( + @JsonView(JsonViews.MetadataTools.class) List components, + @JsonView(JsonViews.MetadataTools.class) List services) { +} diff --git a/src/main/java/org/dependencytrack/model/mapping/PolicyProtoMapper.java b/src/main/java/org/dependencytrack/model/mapping/PolicyProtoMapper.java index 03513167c..df4b9d485 100644 --- a/src/main/java/org/dependencytrack/model/mapping/PolicyProtoMapper.java +++ b/src/main/java/org/dependencytrack/model/mapping/PolicyProtoMapper.java @@ -2,6 +2,9 @@ import com.google.protobuf.Timestamp; import com.google.protobuf.util.Timestamps; +import org.dependencytrack.model.Component; +import org.dependencytrack.model.License; +import org.dependencytrack.model.LicenseGroup; import org.dependencytrack.model.Vulnerability; import org.dependencytrack.model.VulnerabilityAlias; @@ -20,6 +23,43 @@ */ public class PolicyProtoMapper { + public static org.dependencytrack.proto.policy.v1.Component mapToProto(final Component component) { + if (component == null) { + return org.dependencytrack.proto.policy.v1.Component.getDefaultInstance(); + } + + // An object attached to a persistence context could do lazy loading of fields when accessing them. + // Ensure this can't happen, as it could cause massive performance degradation. + assertNonPersistent(component, "component must not be persistent"); + + final org.dependencytrack.proto.policy.v1.Component.Builder protoBuilder = + org.dependencytrack.proto.policy.v1.Component.newBuilder(); + maybeSet(asString(component.getUuid()), protoBuilder::setUuid); + maybeSet(component::getGroup, protoBuilder::setGroup); + maybeSet(component::getName, protoBuilder::setName); + maybeSet(component::getVersion, protoBuilder::setVersion); + maybeSet(asString(component.getClassifier()), protoBuilder::setClassifier); + maybeSet(component::getCpe, protoBuilder::setCpe); + maybeSet(component::getPurl, purl -> protoBuilder.setPurl(purl.canonicalize())); + maybeSet(component::getSwidTagId, protoBuilder::setSwidTagId); + maybeSet(component::isInternal, protoBuilder::setIsInternal); + maybeSet(component::getMd5, protoBuilder::setMd5); + maybeSet(component::getSha1, protoBuilder::setSha1); + maybeSet(component::getSha256, protoBuilder::setSha256); + maybeSet(component::getSha384, protoBuilder::setSha384); + maybeSet(component::getSha512, protoBuilder::setSha512); + maybeSet(component::getSha3_256, protoBuilder::setSha3256); + maybeSet(component::getSha3_384, protoBuilder::setSha3384); + maybeSet(component::getSha3_512, protoBuilder::setSha3512); + maybeSet(component::getBlake2b_256, protoBuilder::setBlake2B256); + maybeSet(component::getBlake2b_384, protoBuilder::setBlake2B384); + maybeSet(component::getBlake2b_512, protoBuilder::setBlake2B512); + maybeSet(component::getBlake3, protoBuilder::setBlake3); + maybeSet(component::getResolvedLicense, license -> protoBuilder.setResolvedLicense(mapToProto(license))); + + return protoBuilder.build(); + } + public static org.dependencytrack.proto.policy.v1.Vulnerability mapToProto(final Vulnerability vuln) { if (vuln == null) { return org.dependencytrack.proto.policy.v1.Vulnerability.getDefaultInstance(); @@ -61,6 +101,46 @@ public static org.dependencytrack.proto.policy.v1.Vulnerability mapToProto(final return protoBuilder.build(); } + private static org.dependencytrack.proto.policy.v1.License mapToProto(final License license) { + if (license == null) { + return org.dependencytrack.proto.policy.v1.License.getDefaultInstance(); + } + + // An object attached to a persistence context could do lazy loading of fields when accessing them. + // Ensure this can't happen, as it could cause massive performance degradation. + assertNonPersistent(license, "license must not be persistent"); + + final org.dependencytrack.proto.policy.v1.License.Builder protoBuilder = + org.dependencytrack.proto.policy.v1.License.newBuilder(); + maybeSet(asString(license.getUuid()), protoBuilder::setUuid); + maybeSet(license::getLicenseId, protoBuilder::setId); + maybeSet(license::getName, protoBuilder::setName); + maybeSet(license::isOsiApproved, protoBuilder::setIsOsiApproved); + maybeSet(license::isFsfLibre, protoBuilder::setIsFsfLibre); + maybeSet(license::isDeprecatedLicenseId, protoBuilder::setIsDeprecatedId); + maybeSet(license::isCustomLicense, protoBuilder::setIsCustom); + maybeSet(license::getLicenseGroups, licenseGroups -> licenseGroups.stream() + .map(PolicyProtoMapper::mapToProto).forEach(protoBuilder::addGroups)); + + return protoBuilder.build(); + } + + private static org.dependencytrack.proto.policy.v1.License.Group mapToProto(final LicenseGroup licenseGroup) { + if (licenseGroup == null) { + return org.dependencytrack.proto.policy.v1.License.Group.getDefaultInstance(); + } + + // An object attached to a persistence context could do lazy loading of fields when accessing them. + // Ensure this can't happen, as it could cause massive performance degradation. + assertNonPersistent(licenseGroup, "licenseGroup must not be persistent"); + + final org.dependencytrack.proto.policy.v1.License.Group.Builder protoBuilder = + org.dependencytrack.proto.policy.v1.License.Group.newBuilder(); + maybeSet(asString(licenseGroup.getUuid()), protoBuilder::setUuid); + maybeSet(licenseGroup::getName, protoBuilder::setName); + return protoBuilder.build(); + } + private static Stream mapToProtos(final VulnerabilityAlias alias) { if (alias == null) { return Stream.empty(); 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 d3a41ec6e..a9e11aa88 100644 --- a/src/main/java/org/dependencytrack/parser/cyclonedx/util/ModelConverter.java +++ b/src/main/java/org/dependencytrack/parser/cyclonedx/util/ModelConverter.java @@ -27,6 +27,7 @@ import org.cyclonedx.model.LicenseChoice; import org.cyclonedx.model.Metadata; import org.cyclonedx.model.Swid; +import org.cyclonedx.model.Tool; import org.dependencytrack.model.Analysis; import org.dependencytrack.model.AnalysisJustification; import org.dependencytrack.model.AnalysisResponse; @@ -43,6 +44,7 @@ import org.dependencytrack.model.ProjectMetadata; import org.dependencytrack.model.ServiceComponent; import org.dependencytrack.model.Severity; +import org.dependencytrack.model.Tools; import org.dependencytrack.model.Vulnerability; import org.dependencytrack.parser.common.resolver.CweResolver; import org.dependencytrack.parser.cyclonedx.CycloneDXExporter; @@ -83,14 +85,49 @@ private ModelConverter() { } public static ProjectMetadata convertToProjectMetadata(final Metadata cdxMetadata) { + if (cdxMetadata == null) { + return null; + } + final var projectMetadata = new ProjectMetadata(); projectMetadata.setSupplier(ModelConverter.convert(cdxMetadata.getSupplier())); projectMetadata.setAuthors(ModelConverter.convertCdxContacts(cdxMetadata.getAuthors())); + + final var toolComponents = new ArrayList(); + final var toolServices = new ArrayList(); + if (cdxMetadata.getTools() != null) { + cdxMetadata.getTools().stream().map(ModelConverter::convert).forEach(toolComponents::add); + } + if (cdxMetadata.getToolChoice() != null) { + if (cdxMetadata.getToolChoice().getComponents() != null) { + cdxMetadata.getToolChoice().getComponents().stream().map(ModelConverter::convertComponent).forEach(toolComponents::add); + } + if (cdxMetadata.getToolChoice().getServices() != null) { + cdxMetadata.getToolChoice().getServices().stream().map(ModelConverter::convertService).forEach(toolServices::add); + } + } + if (!toolComponents.isEmpty() || !toolServices.isEmpty()) { + projectMetadata.setTools(new Tools( + toolComponents.isEmpty() ? null : toolComponents, + toolServices.isEmpty() ? null : toolServices + )); + } + return projectMetadata; } - public static Project convertToProject(final Metadata cdxMetadata, final ProjectMetadata projectMetadata) { - final var cdxComponent = cdxMetadata.getComponent(); + public static Project convertToProject(final org.cyclonedx.model.Metadata cdxMetadata) { + if (cdxMetadata == null || cdxMetadata.getComponent() == null) { + return null; + } + + final Project project = convertToProject(cdxMetadata.getComponent()); + project.setManufacturer(convert(cdxMetadata.getManufacture())); + + return project; + } + + public static Project convertToProject(final org.cyclonedx.model.Component cdxComponent) { final var project = new Project(); project.setAuthor(trimToNull(cdxComponent.getAuthor())); project.setPublisher(trimToNull(cdxComponent.getPublisher())); @@ -100,9 +137,7 @@ public static Project convertToProject(final Metadata cdxMetadata, final Project 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 { @@ -229,6 +264,47 @@ public static Component convertComponent(final org.cyclonedx.model.Component cdx return component; } + private static Component convert(@SuppressWarnings("deprecation") final Tool tool) { + if (tool == null) { + return null; + } + + final var component = new Component(); + if (tool.getVendor() != null && !tool.getVendor().isBlank()) { + final var supplier = new OrganizationalEntity(); + supplier.setName(trimToNull(tool.getVendor())); + component.setSupplier(supplier); + } + component.setName(trimToNull(tool.getName())); + component.setVersion(trimToNull(tool.getVersion())); + component.setExternalReferences(convertExternalReferences(tool.getExternalReferences())); + + if (tool.getHashes() != null && !tool.getHashes().isEmpty()) { + for (final org.cyclonedx.model.Hash cdxHash : tool.getHashes()) { + final Consumer hashSetter = switch (cdxHash.getAlgorithm().toLowerCase()) { + case "md5" -> component::setMd5; + case "sha-1" -> component::setSha1; + case "sha-256" -> component::setSha256; + case "sha-384" -> component::setSha384; + case "sha-512" -> component::setSha512; + case "sha3-256" -> component::setSha3_256; + case "sha3-384" -> component::setSha3_384; + case "sha3-512" -> component::setSha3_512; + case "blake2b-256" -> component::setBlake2b_256; + case "blake2b-384" -> component::setBlake2b_384; + case "blake2b-512" -> component::setBlake2b_512; + case "blake3" -> component::setBlake3; + default -> null; + }; + if (hashSetter != null) { + hashSetter.accept(cdxHash.getValue()); + } + } + } + + return component; + } + public static OrganizationalEntity convert(final org.cyclonedx.model.OrganizationalEntity cdxEntity) { if (cdxEntity == null) { return null; diff --git a/src/main/java/org/dependencytrack/persistence/converter/AbstractJsonConverter.java b/src/main/java/org/dependencytrack/persistence/converter/AbstractJsonConverter.java index c4f9fe19f..317157ee3 100644 --- a/src/main/java/org/dependencytrack/persistence/converter/AbstractJsonConverter.java +++ b/src/main/java/org/dependencytrack/persistence/converter/AbstractJsonConverter.java @@ -19,22 +19,35 @@ package org.dependencytrack.persistence.converter; import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.json.JsonMapper; import javax.jdo.AttributeConverter; +import java.io.IOException; /** * @since 4.10.0 */ abstract class AbstractJsonConverter implements AttributeConverter { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final JsonMapper JSON_MAPPER = JsonMapper.builder() + .disable(MapperFeature.DEFAULT_VIEW_INCLUSION) + .build(); private final TypeReference typeReference; + private final Class jsonView; AbstractJsonConverter(final TypeReference typeReference) { + this(typeReference, null); + } + + AbstractJsonConverter(final TypeReference typeReference, final Class jsonView) { this.typeReference = typeReference; + this.jsonView = jsonView; } @Override @@ -43,8 +56,15 @@ public String convertToDatastore(final T attributeValue) { return null; } + final ObjectWriter objectWriter; + if (jsonView == null) { + objectWriter = JSON_MAPPER.writer(); + } else { + objectWriter = JSON_MAPPER.writerWithView(jsonView); + } + try { - return OBJECT_MAPPER.writeValueAsString(attributeValue); + return objectWriter.writeValueAsString(attributeValue); } catch (JacksonException e) { throw new RuntimeException(e); } @@ -56,9 +76,17 @@ public T convertToAttribute(final String datastoreValue) { return null; } + final ObjectReader objectReader; + if (jsonView == null) { + objectReader = JSON_MAPPER.reader(); + } else { + objectReader = JSON_MAPPER.readerWithView(jsonView); + } + try { - return OBJECT_MAPPER.readValue(datastoreValue, typeReference); - } catch (JacksonException e) { + final JsonParser jsonParser = objectReader.createParser(datastoreValue); + return objectReader.readValue(jsonParser, typeReference); + } catch (IOException e) { throw new RuntimeException(e); } } diff --git a/src/main/java/org/dependencytrack/persistence/converter/ToolsJsonConverter.java b/src/main/java/org/dependencytrack/persistence/converter/ToolsJsonConverter.java new file mode 100644 index 000000000..aadd43184 --- /dev/null +++ b/src/main/java/org/dependencytrack/persistence/converter/ToolsJsonConverter.java @@ -0,0 +1,25 @@ +package org.dependencytrack.persistence.converter; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.dependencytrack.model.JsonViews; +import org.dependencytrack.model.Tools; + +public class ToolsJsonConverter extends AbstractJsonConverter { + + public ToolsJsonConverter() { + super(new TypeReference<>() {}, JsonViews.MetadataTools.class); + } + + @Override + public String convertToDatastore(final Tools attributeValue) { + // Overriding is required for DataNucleus to correctly detect the return type. + return super.convertToDatastore(attributeValue); + } + + @Override + public Tools convertToAttribute(final String datastoreValue) { + // Overriding is required for DataNucleus to correctly detect the return type. + return super.convertToAttribute(datastoreValue); + } + +} diff --git a/src/main/java/org/dependencytrack/policy/cel/CelCommonPolicyLibrary.java b/src/main/java/org/dependencytrack/policy/cel/CelCommonPolicyLibrary.java index 37e8f6e3c..f5ec3d27a 100644 --- a/src/main/java/org/dependencytrack/policy/cel/CelCommonPolicyLibrary.java +++ b/src/main/java/org/dependencytrack/policy/cel/CelCommonPolicyLibrary.java @@ -10,6 +10,7 @@ import org.dependencytrack.proto.policy.v1.Component; import org.dependencytrack.proto.policy.v1.License; import org.dependencytrack.proto.policy.v1.Project; +import org.dependencytrack.proto.policy.v1.Tools; import org.dependencytrack.proto.policy.v1.VersionDistance; import org.dependencytrack.proto.policy.v1.Vulnerability; import org.jdbi.v3.core.Handle; @@ -128,7 +129,9 @@ public List getCompileOptions() { License.getDefaultInstance(), License.Group.getDefaultInstance(), Project.getDefaultInstance(), + Project.Metadata.getDefaultInstance(), Project.Property.getDefaultInstance(), + Tools.getDefaultInstance(), Vulnerability.getDefaultInstance(), Vulnerability.Alias.getDefaultInstance(), VersionDistance.getDefaultInstance() diff --git a/src/main/java/org/dependencytrack/policy/cel/CelPolicyEngine.java b/src/main/java/org/dependencytrack/policy/cel/CelPolicyEngine.java index 85b3392b7..f38fbefae 100644 --- a/src/main/java/org/dependencytrack/policy/cel/CelPolicyEngine.java +++ b/src/main/java/org/dependencytrack/policy/cel/CelPolicyEngine.java @@ -42,8 +42,6 @@ import org.dependencytrack.policy.cel.compat.VulnerabilityIdCelPolicyScriptSourceBuilder; import org.dependencytrack.policy.cel.mapping.ComponentProjection; import org.dependencytrack.policy.cel.mapping.LicenseProjection; -import org.dependencytrack.policy.cel.mapping.ProjectProjection; -import org.dependencytrack.policy.cel.mapping.ProjectPropertyProjection; import org.dependencytrack.policy.cel.mapping.VulnerabilityProjection; import org.dependencytrack.policy.cel.persistence.CelPolicyDao; import org.dependencytrack.proto.policy.v1.Vulnerability; @@ -383,52 +381,6 @@ private static List evaluatePolicyOperators(final Collection properties = - OBJECT_MAPPER.readValue(projection.propertiesJson, new TypeReference<>() { - }); - for (final ProjectPropertyProjection property : properties) { - builder.addProperties(org.dependencytrack.proto.policy.v1.Project.Property.newBuilder() - .setGroup(trimToEmpty(property.group)) - .setName(trimToEmpty(property.name)) - .setValue(trimToEmpty(property.value)) - .setType(trimToEmpty(property.type)) - .build()); - } - } catch (JacksonException e) { - LOGGER.warn("Failed to parse properties from %s for project %s" - .formatted(projection.propertiesJson, projection.id), e); - } - } - - if (projection.tagsJson != null) { - try { - final List tags = OBJECT_MAPPER.readValue(projection.tagsJson, new TypeReference<>() { - }); - builder.addAllTags(tags); - } catch (JacksonException e) { - LOGGER.warn("Failed to parse tags from %s for project %s" - .formatted(projection.tagsJson, projection.id), e); - } - } - - return builder.build(); - } - private static org.dependencytrack.proto.policy.v1.Component mapToProto(final ComponentProjection projection, final Map protoLicenseById) { final org.dependencytrack.proto.policy.v1.Component.Builder componentBuilder = diff --git a/src/main/java/org/dependencytrack/policy/cel/CelVulnerabilityPolicyEvaluator.java b/src/main/java/org/dependencytrack/policy/cel/CelVulnerabilityPolicyEvaluator.java index 21238d817..68afa82d3 100644 --- a/src/main/java/org/dependencytrack/policy/cel/CelVulnerabilityPolicyEvaluator.java +++ b/src/main/java/org/dependencytrack/policy/cel/CelVulnerabilityPolicyEvaluator.java @@ -41,6 +41,7 @@ import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_LICENSE; import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_LICENSE_GROUP; import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_PROJECT; +import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_PROJECT_METADATA; import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_PROJECT_PROPERTY; import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_VULNERABILITY; @@ -282,6 +283,11 @@ private static String buildCacheKey(final Project project, final MultiValuedMap< cacheKeyParts.add("property.%s".formatted(propertyFieldName)); } } + if (cacheKeyParts.contains("metadata") && requirements.containsKey(TYPE_PROJECT_METADATA)) { + for (final String metadataFieldName : requirements.get(TYPE_PROJECT_METADATA)) { + cacheKeyParts.add("metadata.%s".formatted(metadataFieldName)); + } + } final String rawCacheKey = "%s|%s" .formatted(project.getUuid(), cacheKeyParts.stream().sorted().collect(Collectors.joining("|"))); diff --git a/src/main/java/org/dependencytrack/policy/cel/definition/CelPolicyTypes.java b/src/main/java/org/dependencytrack/policy/cel/definition/CelPolicyTypes.java index 672ce4e3e..821841e85 100644 --- a/src/main/java/org/dependencytrack/policy/cel/definition/CelPolicyTypes.java +++ b/src/main/java/org/dependencytrack/policy/cel/definition/CelPolicyTypes.java @@ -4,6 +4,7 @@ import org.dependencytrack.proto.policy.v1.Component; import org.dependencytrack.proto.policy.v1.License; import org.dependencytrack.proto.policy.v1.Project; +import org.dependencytrack.proto.policy.v1.Tools; import org.dependencytrack.proto.policy.v1.Vulnerability; import org.dependencytrack.proto.policy.v1.VersionDistance; import org.projectnessie.cel.checker.Decls; @@ -14,7 +15,9 @@ public class CelPolicyTypes { public static final Type TYPE_LICENSE = Decls.newObjectType(License.getDescriptor().getFullName()); public static final Type TYPE_LICENSE_GROUP = Decls.newObjectType(License.Group.getDescriptor().getFullName()); public static final Type TYPE_PROJECT = Decls.newObjectType(Project.getDescriptor().getFullName()); + public static final Type TYPE_PROJECT_METADATA = Decls.newObjectType(Project.Metadata.getDescriptor().getFullName()); public static final Type TYPE_PROJECT_PROPERTY = Decls.newObjectType(Project.Property.getDescriptor().getFullName()); + public static final Type TYPE_TOOLS = Decls.newObjectType(Tools.getDescriptor().getFullName()); public static final Type TYPE_VULNERABILITY = Decls.newObjectType(Vulnerability.getDescriptor().getFullName()); public static final Type TYPE_VULNERABILITIES = Decls.newListType(TYPE_VULNERABILITY); public static final Type TYPE_VULNERABILITY_ALIAS = Decls.newObjectType(Vulnerability.Alias.getDescriptor().getFullName()); diff --git a/src/main/java/org/dependencytrack/policy/cel/mapping/ProjectProjection.java b/src/main/java/org/dependencytrack/policy/cel/mapping/ProjectProjection.java index 40f9ab980..525bba031 100644 --- a/src/main/java/org/dependencytrack/policy/cel/mapping/ProjectProjection.java +++ b/src/main/java/org/dependencytrack/policy/cel/mapping/ProjectProjection.java @@ -38,7 +38,4 @@ public class ProjectProjection { @MappedField(protoFieldName = "last_bom_import", sqlColumnName = "LAST_BOM_IMPORTED") public Date lastBomImport; - public String propertiesJson; - public String tagsJson; - } diff --git a/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyDao.java b/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyDao.java index 3c8802f0e..f8c58bb0e 100644 --- a/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyDao.java +++ b/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyDao.java @@ -28,6 +28,7 @@ import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_COMPONENT; import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_PROJECT; +import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_PROJECT_METADATA; import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_PROJECT_PROPERTY; import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_VULNERABILITY; import static org.dependencytrack.policy.cel.mapping.FieldMappingUtil.getFieldMappings; @@ -41,6 +42,10 @@ public interface CelPolicyDao { ${fetchColumns?join(", ")} FROM "PROJECT" AS "P" + <#if fetchColumns?filter(col -> col?contains("\\"metadata_tools\\""))?size gt 0> + INNER JOIN + "PROJECT_METADATA" AS "PM" ON "PM"."PROJECT_ID" = "P"."ID" + <#if fetchPropertyColumns?size gt 0> LEFT JOIN LATERAL ( SELECT @@ -154,6 +159,13 @@ default Project loadRequiredFields(final Project project, final MultiValuedMap fieldsToLoad.contains(fieldMapping.protoFieldName())) .map(fieldMapping -> "\"P\".\"%s\" AS \"%s\"".formatted(fieldMapping.sqlColumnName(), fieldMapping.protoFieldName())) .collect(Collectors.toList()); + + if (fieldsToLoad.contains("metadata") + && requirements.containsKey(TYPE_PROJECT_METADATA) + && requirements.get(TYPE_PROJECT_METADATA).contains("tools")) { + sqlSelectColumns.add("\"PM\".\"TOOLS\" AS \"metadata_tools\""); + } + final var sqlPropertySelectColumns = new ArrayList(); if (fieldsToLoad.contains("properties") && requirements.containsKey(TYPE_PROJECT_PROPERTY)) { sqlSelectColumns.add("\"properties\""); diff --git a/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyProjectRowMapper.java b/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyProjectRowMapper.java index 443465014..5e21eb566 100644 --- a/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyProjectRowMapper.java +++ b/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyProjectRowMapper.java @@ -3,8 +3,10 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.google.protobuf.util.JsonFormat; +import org.dependencytrack.model.mapping.PolicyProtoMapper; import org.dependencytrack.persistence.jdbi.mapping.RowMapperUtil; import org.dependencytrack.proto.policy.v1.Project; +import org.dependencytrack.proto.policy.v1.Tools; import org.jdbi.v3.core.mapper.RowMapper; import org.jdbi.v3.core.result.UnableToProduceResultException; import org.jdbi.v3.core.statement.StatementContext; @@ -18,6 +20,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank; import static org.dependencytrack.persistence.jdbi.mapping.RowMapperUtil.OBJECT_MAPPER; +import static org.dependencytrack.persistence.jdbi.mapping.RowMapperUtil.hasColumn; import static org.dependencytrack.persistence.jdbi.mapping.RowMapperUtil.maybeSet; public class CelPolicyProjectRowMapper implements RowMapper { @@ -37,10 +40,44 @@ public Project map(final ResultSet rs, final StatementContext ctx) throws SQLExc maybeSet(rs, "last_bom_import", RowMapperUtil::nullableTimestamp, builder::setLastBomImport); maybeSet(rs, "tags", RowMapperUtil::stringArray, builder::addAllTags); maybeSet(rs, "properties", CelPolicyProjectRowMapper::maybeConvertProperties, builder::addAllProperties); + + if (hasColumn(rs, "metadata_tools")) { + builder.setMetadata(Project.Metadata.newBuilder() + .setTools(convertMetadataTools(rs)) + .build()); + } + return builder.build(); } - private static List maybeConvertProperties(final ResultSet rs, String columnName) throws SQLException { + private static Tools convertMetadataTools(final ResultSet rs) throws SQLException { + final String jsonString = rs.getString("metadata_tools"); + if (isBlank(jsonString)) { + return Tools.getDefaultInstance(); + } + + final org.dependencytrack.model.Tools modelTools; + try { + modelTools = OBJECT_MAPPER.readValue(jsonString, org.dependencytrack.model.Tools.class); + } catch (IOException e) { + throw new UnableToProduceResultException(e); + } + + if (modelTools == null) { + return Tools.getDefaultInstance(); + } + + final var toolsBuilder = Tools.newBuilder(); + if (modelTools.components() != null) { + modelTools.components().stream() + .map(PolicyProtoMapper::mapToProto) + .forEach(toolsBuilder::addComponents); + } + + return toolsBuilder.build(); + } + + private static List maybeConvertProperties(final ResultSet rs, final String columnName) throws SQLException { final String jsonString = rs.getString(columnName); if (isBlank(jsonString)) { return Collections.emptyList(); diff --git a/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java b/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java index 58cf22f36..5133e628b 100644 --- a/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java +++ b/src/main/java/org/dependencytrack/tasks/BomUploadProcessingTask.java @@ -92,6 +92,7 @@ import static org.apache.commons.lang3.StringUtils.trimToNull; import static org.datanucleus.PropertyNames.PROPERTY_FLUSH_MODE; import static org.datanucleus.PropertyNames.PROPERTY_PERSISTENCE_BY_REACHABILITY_AT_COMMIT; +import static org.datanucleus.PropertyNames.PROPERTY_RETAIN_VALUES; import static org.dependencytrack.common.ConfigKey.BOM_UPLOAD_PROCESSING_TRX_FLUSH_THRESHOLD; import static org.dependencytrack.event.kafka.componentmeta.RepoMetaConstants.SUPPORTED_PACKAGE_URLS_FOR_INTEGRITY_CHECK; import static org.dependencytrack.event.kafka.componentmeta.RepoMetaConstants.TIME_SPAN; @@ -220,15 +221,11 @@ 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(); - Project metadataComponent = null; - ProjectMetadata projectMetadata = null; + final ProjectMetadata projectMetadata = convertToProjectMetadata(cdxBom.getMetadata()); + final Project project = convertToProject(cdxBom.getMetadata()); List components = new ArrayList<>(); - if (cdxBom.getMetadata() != null) { - projectMetadata = convertToProjectMetadata(cdxBom.getMetadata()); - if (cdxBom.getMetadata().getComponent() != null) { - metadataComponent = convertToProject(cdxBom.getMetadata(), projectMetadata); - components.addAll(convertComponents(cdxBom.getMetadata().getComponent().getComponents())); - } + if (cdxBom.getMetadata() != null && cdxBom.getMetadata().getComponent() != null) { + components.addAll(convertComponents(cdxBom.getMetadata().getComponent().getComponents())); } components.addAll(convertComponents(cdxBom.getComponents())); components = flatten(components, Component::getChildren, Component::setChildren); @@ -304,30 +301,27 @@ private void processBom(final Context ctx, final File bomFile) throws BomConsump // BomUploadProcessingTaskTest#informWithBloatedBomTest can be used to profile the impact on large BOMs. pm.setProperty(PROPERTY_FLUSH_MODE, FlushMode.MANUAL.name()); + // Prevent object fields from being unloaded upon commit. + // + // DataNucleus transitions objects into the "hollow" state after the transaction is committed. + // In hollow state, all fields except the ID are unloaded. Accessing fields afterward triggers + // one or more database queries to load them again. + // See https://www.datanucleus.org/products/accessplatform_6_0/jdo/persistence.html#lifecycle + qm.getPersistenceManager().setProperty(PROPERTY_RETAIN_VALUES, "true"); + LOGGER.info("Processing %d components and %d services from BOM (%s)" .formatted(components.size(), services.size(), ctx)); final Transaction trx = pm.currentTransaction(); try { trx.begin(); - final Project project = processMetadataComponent(ctx, pm, metadataComponent); - 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() != null - ? new ArrayList<>(projectMetadata.getAuthors()) - : null); - } - } + final Project persistentProject = processProject(ctx, pm, project, projectMetadata); final Map persistentComponents = - processComponents(qm, project, components, identitiesByBomRef, bomRefsByIdentity); + processComponents(qm, persistentProject, components, identitiesByBomRef, bomRefsByIdentity); final Map persistentServices = - processServices(qm, project, services, identitiesByBomRef, bomRefsByIdentity); - processDependencyGraph(ctx, pm, cdxBom, project, persistentComponents, persistentServices, identitiesByBomRef); - recordBomImport(ctx, pm, project); + processServices(qm, persistentProject, services, identitiesByBomRef, bomRefsByIdentity); + processDependencyGraph(ctx, pm, cdxBom, persistentProject, persistentComponents, persistentServices, identitiesByBomRef); + recordBomImport(ctx, pm, persistentProject); // BOM ref <-> ComponentIdentity indexes are no longer needed. // Let go of their contents to make it eligible for GC sooner. @@ -484,42 +478,58 @@ private static org.cyclonedx.model.Bom parseBom(final Context ctx, final File bo return bom; } - private static Project processMetadataComponent(final Context ctx, final PersistenceManager pm, final Project metadataComponent) throws BomProcessingException { + private static Project processProject(final Context ctx, final PersistenceManager pm, + final Project project, final ProjectMetadata projectMetadata) throws BomProcessingException { final Query query = pm.newQuery(Project.class); query.setFilter("uuid == :uuid"); query.setParameters(ctx.project.getUuid()); - final Project project; + final Project persistentProject; try { - project = query.executeUnique(); + persistentProject = query.executeUnique(); } finally { query.closeAll(); } - if (project == null) { + if (persistentProject == null) { throw new BomProcessingException(ctx, "Project does not exist"); } - if (metadataComponent != null) { - boolean changed = false; - 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); + boolean hasChanged = false; + if (project != null) { + hasChanged |= applyIfChanged(persistentProject, project, Project::getAuthor, persistentProject::setAuthor); + hasChanged |= applyIfChanged(persistentProject, project, Project::getPublisher, persistentProject::setPublisher); + hasChanged |= applyIfChanged(persistentProject, project, Project::getClassifier, persistentProject::setClassifier); + hasChanged |= applyIfChanged(persistentProject, project, Project::getSupplier, persistentProject::setSupplier); + hasChanged |= applyIfChanged(persistentProject, project, Project::getManufacturer, persistentProject::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); - // changed |= applyIfChanged(project, metadataComponent, Project::getName, project::setName); - // changed |= applyIfChanged(project, metadataComponent, Project::getVersion, project::setVersion); - // changed |= applyIfChanged(project, metadataComponent, Project::getDescription, project::setDescription); - changed |= applyIfChanged(project, metadataComponent, Project::getExternalReferences, project::setExternalReferences); - changed |= applyIfChanged(project, metadataComponent, Project::getPurl, project::setPurl); - changed |= applyIfChanged(project, metadataComponent, Project::getSwidTagId, project::setSwidTagId); - if (changed) { - pm.flush(); + // hasChanged |= applyIfChanged(persistentProject, project, Project::getGroup, persistentProject::setGroup); + // hasChanged |= applyIfChanged(persistentProject, project, Project::getName, persistentProject::setName); + // hasChanged |= applyIfChanged(persistentProject, project, Project::getVersion, persistentProject::setVersion); + // hasChanged |= applyIfChanged(persistentProject, project, Project::getDescription, persistentProject::setDescription); + hasChanged |= applyIfChanged(persistentProject, project, Project::getExternalReferences, persistentProject::setExternalReferences); + hasChanged |= applyIfChanged(persistentProject, project, Project::getPurl, persistentProject::setPurl); + hasChanged |= applyIfChanged(persistentProject, project, Project::getSwidTagId, persistentProject::setSwidTagId); + } + + if (projectMetadata != null) { + if (persistentProject.getMetadata() == null) { + projectMetadata.setProject(persistentProject); + pm.makePersistent(projectMetadata); + hasChanged = true; + } else { + hasChanged |= applyIfChanged(persistentProject.getMetadata(), projectMetadata, ProjectMetadata::getAuthors, + authors -> persistentProject.getMetadata().setAuthors(authors != null ? new ArrayList<>(authors) : null)); + hasChanged |= applyIfChanged(persistentProject.getMetadata(), projectMetadata, ProjectMetadata::getSupplier, persistentProject.getMetadata()::setSupplier); + hasChanged |= applyIfChanged(persistentProject.getMetadata(), projectMetadata, ProjectMetadata::getTools, persistentProject.getMetadata()::setTools); } } - return project; + + if (hasChanged) { + pm.flush(); + } + + return persistentProject; } private static Map processComponents(final QueryManager qm, diff --git a/src/main/proto/org/dependencytrack/policy/v1/policy.proto b/src/main/proto/org/dependencytrack/policy/v1/policy.proto index 545a9b74e..c4fe36ba8 100644 --- a/src/main/proto/org/dependencytrack/policy/v1/policy.proto +++ b/src/main/proto/org/dependencytrack/policy/v1/policy.proto @@ -97,6 +97,11 @@ message Project { optional string purl = 10; optional string swid_tag_id = 11; optional google.protobuf.Timestamp last_bom_import = 12; + optional Metadata metadata = 13; + + message Metadata { + optional Tools tools = 1; + } message Property { string group = 1; @@ -106,6 +111,13 @@ message Project { } } +message Tools { + // Components used as tools. + repeated Component components = 1; + + // TODO: Add services. +} + message Vulnerability { string uuid = 1; string id = 2; diff --git a/src/main/resources/migration/changelog-main.xml b/src/main/resources/migration/changelog-main.xml index 58a4a9b67..5ab794a8e 100644 --- a/src/main/resources/migration/changelog-main.xml +++ b/src/main/resources/migration/changelog-main.xml @@ -10,5 +10,6 @@ http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd"> + \ No newline at end of file diff --git a/src/main/resources/migration/changelog-v5.4.0.xml b/src/main/resources/migration/changelog-v5.4.0.xml new file mode 100644 index 000000000..56e32f357 --- /dev/null +++ b/src/main/resources/migration/changelog-v5.4.0.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/src/test/java/org/dependencytrack/model/mapping/PolicyProtoMapperTest.java b/src/test/java/org/dependencytrack/model/mapping/PolicyProtoMapperTest.java index 05002134f..e048618a0 100644 --- a/src/test/java/org/dependencytrack/model/mapping/PolicyProtoMapperTest.java +++ b/src/test/java/org/dependencytrack/model/mapping/PolicyProtoMapperTest.java @@ -112,7 +112,7 @@ public void testMapVulnerabilityWithNoFieldsSet() throws Exception { @Test public void testMapVulnerabilityToProtoWhenNull() { - assertThat(PolicyProtoMapper.mapToProto(null)) + assertThat(PolicyProtoMapper.mapToProto((Vulnerability) null)) .isEqualTo(org.dependencytrack.proto.policy.v1.Vulnerability.getDefaultInstance()); } diff --git a/src/test/java/org/dependencytrack/persistence/converter/ToolsJsonConverterTest.java b/src/test/java/org/dependencytrack/persistence/converter/ToolsJsonConverterTest.java new file mode 100644 index 000000000..e6d057c42 --- /dev/null +++ b/src/test/java/org/dependencytrack/persistence/converter/ToolsJsonConverterTest.java @@ -0,0 +1,305 @@ +package org.dependencytrack.persistence.converter; + +import org.dependencytrack.model.Classifier; +import org.dependencytrack.model.Component; +import org.dependencytrack.model.DataClassification; +import org.dependencytrack.model.ExternalReference; +import org.dependencytrack.model.OrganizationalEntity; +import org.dependencytrack.model.Project; +import org.dependencytrack.model.ServiceComponent; +import org.dependencytrack.model.Tools; +import org.dependencytrack.model.Vulnerability; +import org.junit.Test; + +import java.util.List; +import java.util.UUID; + +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; +import static org.assertj.core.api.Assertions.assertThat; + +public class ToolsJsonConverterTest { + + @Test + public void testConvertToDatastore() { + final var project = new Project(); + project.setName("acme-app"); + + final var componentSupplier = new OrganizationalEntity(); + componentSupplier.setName("componentSupplierName"); + + final var externalReference = new ExternalReference(); + externalReference.setType(org.cyclonedx.model.ExternalReference.Type.DOCUMENTATION); + externalReference.setUrl("https://example.com"); + + final var vuln = new Vulnerability(); + vuln.setVulnId("INT-001"); + vuln.setSource(Vulnerability.Source.INTERNAL); + + final var component = new Component(); + component.setProject(project); + component.setId(123); + component.setUuid(UUID.randomUUID()); + component.setAuthor("componentAuthor"); + component.setPublisher("componentPublisher"); + component.setSupplier(componentSupplier); + component.setGroup("componentGroup"); + component.setName("componentName"); + component.setVersion("componentVersion"); + component.setClassifier(Classifier.LIBRARY); + component.setFilename("componentFilename"); + component.setExtension("componentExtension"); + component.setMd5("componentMd5"); + component.setSha1("componentSha1"); + component.setSha256("componentSha256"); + component.setSha384("componentSha384"); + component.setSha512("componentSha512"); + component.setSha3_256("componentSha3_256"); + component.setSha3_384("componentSha3_384"); + component.setSha3_512("componentSha3_512"); + component.setBlake2b_256("componentBlake2b_256"); + component.setBlake2b_384("componentBlake2b_384"); + component.setBlake2b_512("componentBlake2b_512"); + component.setBlake3("componentBlake3"); + component.setCpe("componentCpe"); + component.setPurl("pkg:maven/componentGroup/componentName@componentVersion?foo=bar"); + component.setPurlCoordinates("pkg:maven/componentGroup/componentName@componentVersion"); + component.setSwidTagId("componentSwidTagId"); + component.setInternal(true); + component.setDescription("componentDescription"); + component.setCopyright("componentCopyright"); + component.setLicense("componentLicense"); + component.setLicenseExpression("componentLicenseExpression"); + component.setLicenseUrl("componentLicenseUrl"); + component.setDirectDependencies("componentDirectDependencies"); + component.setExternalReferences(List.of(externalReference)); + component.setParent(component); + component.setChildren(List.of(component)); + component.setVulnerabilities(List.of(vuln)); + component.setLastInheritedRiskScore(10.0); + component.setNotes("componentNotes"); + + final var serviceProvider = new OrganizationalEntity(); + serviceProvider.setName("serviceProviderName"); + + final var serviceDataClassification = new DataClassification(); + serviceDataClassification.setDirection(DataClassification.Direction.OUTBOUND); + serviceDataClassification.setName("serviceDataClassificationName"); + + final var service = new ServiceComponent(); + service.setProject(project); + service.setId(123); + service.setUuid(UUID.randomUUID()); + service.setProvider(serviceProvider); + service.setGroup("serviceGroup"); + service.setName("serviceName"); + service.setVersion("serviceVersion"); + service.setDescription("serviceDescription"); + service.setEndpoints(new String[]{"https://example.com"}); + service.setAuthenticated(true); + service.setCrossesTrustBoundary(true); + service.setData(List.of(serviceDataClassification)); + service.setExternalReferences(List.of(externalReference)); + service.setParent(service); + service.setChildren(List.of(service)); + service.setVulnerabilities(List.of(vuln)); + service.setLastInheritedRiskScore(11.0); + service.setNotes("serviceNotes"); + + assertThatJson(new ToolsJsonConverter().convertToDatastore(new Tools(List.of(component), List.of(service)))) + .isEqualTo(""" + { + "components": [ + { + "author": "componentAuthor", + "blake2b_256": "componentBlake2b_256", + "blake2b_384": "componentBlake2b_384", + "blake2b_512": "componentBlake2b_512", + "blake3": "componentBlake3", + "classifier": "LIBRARY", + "cpe": "componentCpe", + "externalReferences": [ + { + "type": "documentation", + "url": "https://example.com" + } + ], + "group": "componentGroup", + "md5": "componentmd5", + "name": "componentName", + "publisher": "componentPublisher", + "purl": "pkg:maven/componentGroup/componentName@componentVersion?foo=bar", + "sha1": "componentsha1", + "sha256": "componentsha256", + "sha384": "componentsha384", + "sha3_256": "componentsha3_256", + "sha3_384": "componentsha3_384", + "sha3_512": "componentsha3_512", + "sha512": "componentsha512", + "supplier": { + "name": "componentSupplierName" + }, + "swidTagId": "componentSwidTagId", + "version": "componentVersion" + } + ], + "services": [ + { + "provider": { + "name": "serviceProviderName" + }, + "group": "serviceGroup", + "name": "serviceName", + "version": "serviceVersion", + "description": "serviceDescription", + "endpoints": [ + "https://example.com" + ], + "authenticated": true, + "crossesTrustBoundary": true, + "data": [ + { + "direction": "OUTBOUND", + "name": "serviceDataClassificationName" + } + ], + "externalReferences": [ + { + "type": "documentation", + "url": "https://example.com" + } + ] + } + ] + } + """); + } + + @Test + public void testConvertToAttribute() { + final Tools tools = new ToolsJsonConverter().convertToAttribute(""" + { + "components": [ + { + "author": "componentAuthor", + "blake2b_256": "componentBlake2b_256", + "blake2b_384": "componentBlake2b_384", + "blake2b_512": "componentBlake2b_512", + "blake3": "componentBlake3", + "classifier": "LIBRARY", + "cpe": "componentCpe", + "externalReferences": [ + { + "type": "documentation", + "url": "https://example.com" + } + ], + "group": "componentGroup", + "md5": "componentmd5", + "name": "componentName", + "publisher": "componentPublisher", + "purl": "pkg:maven/componentGroup/componentName@componentVersion?foo=bar", + "sha1": "componentsha1", + "sha256": "componentsha256", + "sha384": "componentsha384", + "sha3_256": "componentsha3_256", + "sha3_384": "componentsha3_384", + "sha3_512": "componentsha3_512", + "sha512": "componentsha512", + "supplier": { + "name": "componentSupplierName" + }, + "swidTagId": "componentSwidTagId", + "version": "componentVersion" + } + ], + "services": [ + { + "provider": { + "name": "serviceProviderName" + }, + "group": "serviceGroup", + "name": "serviceName", + "version": "serviceVersion", + "description": "serviceDescription", + "endpoints": [ + "https://example.com" + ], + "authenticated": true, + "crossesTrustBoundary": true, + "data": [ + { + "direction": "OUTBOUND", + "name": "serviceDataClassificationName" + } + ], + "externalReferences": [ + { + "type": "documentation", + "url": "https://example.com" + } + ] + } + ] + } + """); + + assertThat(tools).isNotNull(); + assertThat(tools.components()).satisfiesExactly(component -> { + assertThat(component.getAuthor()).isEqualTo("componentAuthor"); + assertThat(component.getBlake2b_256()).isEqualTo("componentBlake2b_256"); + assertThat(component.getBlake2b_384()).isEqualTo("componentBlake2b_384"); + assertThat(component.getBlake2b_512()).isEqualTo("componentBlake2b_512"); + assertThat(component.getBlake3()).isEqualTo("componentBlake3"); + assertThat(component.getClassifier()).isEqualTo(Classifier.LIBRARY); + assertThat(component.getCpe()).isEqualTo("componentCpe"); + assertThat(component.getExternalReferences()).satisfiesExactly(externalReference -> { + assertThat(externalReference.getType()).isEqualTo(org.cyclonedx.model.ExternalReference.Type.DOCUMENTATION); + assertThat(externalReference.getUrl()).isEqualTo("https://example.com"); + }); + assertThat(component.getGroup()).isEqualTo("componentGroup"); + assertThat(component.getMd5()).isEqualTo("componentmd5"); + assertThat(component.getName()).isEqualTo("componentName"); + assertThat(component.getPublisher()).isEqualTo("componentPublisher"); + assertThat(component.getPurl()).asString().isEqualTo("pkg:maven/componentGroup/componentName@componentVersion?foo=bar"); + assertThat(component.getSha1()).isEqualTo("componentsha1"); + assertThat(component.getSha256()).isEqualTo("componentsha256"); + assertThat(component.getSha384()).isEqualTo("componentsha384"); + assertThat(component.getSha512()).isEqualTo("componentsha512"); + assertThat(component.getSha3_256()).isEqualTo("componentsha3_256"); + assertThat(component.getSha3_384()).isEqualTo("componentsha3_384"); + assertThat(component.getSha3_512()).isEqualTo("componentsha3_512"); + assertThat(component.getSupplier()).satisfies(supplier -> assertThat(supplier.getName()).isEqualTo("componentSupplierName")); + assertThat(component.getSwidTagId()).isEqualTo("componentSwidTagId"); + assertThat(component.getVersion()).isEqualTo("componentVersion"); + }); + assertThat(tools.services()).satisfiesExactly(service -> { + assertThat(service.getProvider()).satisfies(provider -> assertThat(provider.getName()).isEqualTo("serviceProviderName")); + assertThat(service.getGroup()).isEqualTo("serviceGroup"); + assertThat(service.getName()).isEqualTo("serviceName"); + assertThat(service.getVersion()).isEqualTo("serviceVersion"); + assertThat(service.getDescription()).isEqualTo("serviceDescription"); + assertThat(service.getEndpoints()).containsOnly("https://example.com"); + assertThat(service.getAuthenticated()).isTrue(); + assertThat(service.getCrossesTrustBoundary()).isTrue(); + assertThat(service.getData()).satisfiesExactly(classification -> { + assertThat(classification.getDirection()).isEqualTo(DataClassification.Direction.OUTBOUND); + assertThat(classification.getName()).isEqualTo("serviceDataClassificationName"); + }); + assertThat(service.getExternalReferences()).satisfiesExactly(externalReference -> { + assertThat(externalReference.getType()).isEqualTo(org.cyclonedx.model.ExternalReference.Type.DOCUMENTATION); + assertThat(externalReference.getUrl()).isEqualTo("https://example.com"); + }); + }); + } + + @Test + public void testConvertToDatastoreNull() { + assertThat(new ToolsJsonConverter().convertToDatastore(null)).isNull(); + } + + @Test + public void testConvertToAttributeNull() { + assertThat(new ToolsJsonConverter().convertToAttribute(null)).isNull(); + } + +} \ No newline at end of file diff --git a/src/test/java/org/dependencytrack/policy/cel/CelPolicyEngineTest.java b/src/test/java/org/dependencytrack/policy/cel/CelPolicyEngineTest.java index 9469a9468..a4e216d4f 100644 --- a/src/test/java/org/dependencytrack/policy/cel/CelPolicyEngineTest.java +++ b/src/test/java/org/dependencytrack/policy/cel/CelPolicyEngineTest.java @@ -18,10 +18,12 @@ import org.dependencytrack.model.PolicyCondition; import org.dependencytrack.model.PolicyViolation; import org.dependencytrack.model.Project; +import org.dependencytrack.model.ProjectMetadata; import org.dependencytrack.model.RepositoryMetaComponent; import org.dependencytrack.model.RepositoryType; import org.dependencytrack.model.Severity; import org.dependencytrack.model.Tag; +import org.dependencytrack.model.Tools; import org.dependencytrack.model.ViolationAnalysisState; import org.dependencytrack.model.Vulnerability; import org.dependencytrack.model.VulnerabilityAlias; @@ -69,7 +71,7 @@ public void before() throws Exception { * Data being available means: *
    *
  • Expression requirements were analyzed correctly
  • - *
  • Database was retrieved from the database correctly
  • + *
  • Data was retrieved from the database correctly
  • *
  • The mapping from DB data to CEL Protobuf models worked as expected
  • *
*/ @@ -88,6 +90,43 @@ public void testEvaluateProjectWithAllFields() { project.setLastBomImport(new java.util.Date()); qm.persist(project); + final var toolComponentLicense = new License(); + toolComponentLicense.setUuid(UUID.randomUUID()); + toolComponentLicense.setLicenseId("toolComponentLicenseId"); + + final var toolComponent = new Component(); + toolComponent.setGroup("toolComponentGroup"); + toolComponent.setName("toolComponentName"); + toolComponent.setVersion("toolComponentVersion"); + toolComponent.setClassifier(Classifier.APPLICATION); + toolComponent.setCpe("toolComponentCpe"); + toolComponent.setPurl("pkg:maven/toolComponentGroup/toolComponentName@toolComponentVersion"); // NB: Must be valid PURL, otherwise it's being JSON serialized as null + toolComponent.setSwidTagId("toolComponentSwidTagId"); + toolComponent.setInternal(true); // NB: Currently ignored for tool components. + toolComponent.setMd5("toolComponentMd5"); + toolComponent.setSha1("toolComponentSha1"); + toolComponent.setSha256("toolComponentSha256"); + toolComponent.setSha384("toolComponentSha384"); + toolComponent.setSha512("toolComponentSha512"); + toolComponent.setSha3_256("toolComponentSha3_256"); + toolComponent.setSha3_384("toolComponentSha3_384"); + toolComponent.setSha3_512("toolComponentSha3_512"); + toolComponent.setBlake2b_256("toolComponentBlake2b_256"); + toolComponent.setBlake2b_384("toolComponentBlake2b_384"); + toolComponent.setBlake2b_512("toolComponentBlake2b_512"); + toolComponent.setBlake3("toolComponentBlake3"); + // NB: License data is currently ignored for tool components. + // Including it in the test for documentation purposes. + toolComponent.setLicense("toolComponentLicense"); + toolComponent.setLicenseExpression("toolComponentLicenseExpression"); + toolComponent.setLicenseUrl("toolComponentLicenseUrl"); + toolComponent.setResolvedLicense(toolComponentLicense); + + final var projectMetadata = new ProjectMetadata(); + projectMetadata.setProject(project); + projectMetadata.setTools(new Tools(List.of(toolComponent), null)); + qm.persist(projectMetadata); + qm.createProjectProperty(project, "propertyGroup", "propertyName", "propertyValue", IConfigProperty.PropertyType.STRING, null); qm.bind(project, List.of( @@ -231,6 +270,31 @@ public void testEvaluateProjectWithAllFields() { && project.purl == "projectPurl" && project.swid_tag_id == "projectSwidTagId" && has(project.last_bom_import) + && project.metadata.tools.components.all(tool, + tool.group == "toolComponentGroup" + && tool.name == "toolComponentName" + && tool.version == "toolComponentVersion" + && tool.classifier == "APPLICATION" + && tool.cpe == "toolComponentCpe" + && tool.purl == "pkg:maven/toolComponentGroup/toolComponentName@toolComponentVersion" + && tool.swid_tag_id == "toolComponentSwidTagId" + && !tool.is_internal + && tool.md5 == "toolcomponentmd5" + && tool.sha1 == "toolcomponentsha1" + && tool.sha256 == "toolcomponentsha256" + && tool.sha384 == "toolcomponentsha384" + && tool.sha512 == "toolcomponentsha512" + && tool.sha3_256 == "toolcomponentsha3_256" + && tool.sha3_384 == "toolcomponentsha3_384" + && tool.sha3_512 == "toolcomponentsha3_512" + && tool.blake2b_256 == "toolComponentBlake2b_256" + && tool.blake2b_384 == "toolComponentBlake2b_384" + && tool.blake2b_512 == "toolComponentBlake2b_512" + && tool.blake3 == "toolComponentBlake3" + && !has(tool.license_name) + && !has(tool.license_expression) + && !has(tool.resolved_license) + ) && "projecttaga" in project.tags && project.properties.all(property, property.group == "propertyGroup" @@ -1582,7 +1646,7 @@ public void testEvaluateProjectWithFuncMatchesRangeWithInvalidRange() { qm.createPolicyCondition(policy, PolicyCondition.Subject.EXPRESSION, PolicyCondition.Operator.MATCHES, """ project.matches_range("foo") && component.matches_range("bar") - """); + """, PolicyViolation.Type.OPERATIONAL); final var project = new Project(); project.setName("acme-app"); @@ -1606,6 +1670,45 @@ public void testEvaluateProjectWithFuncMatchesRangeWithInvalidRange() { assertThat(qm.getAllPolicyViolations(componentB)).isEmpty(); } + @Test + public void testEvaluateProjectWithToolMetadata() { + final var policy = qm.createPolicy("policy", Policy.Operator.ANY, Policy.ViolationState.FAIL); + qm.createPolicyCondition(policy, PolicyCondition.Subject.EXPRESSION, PolicyCondition.Operator.MATCHES, """ + project.metadata.tools.components.exists(tool, + tool.name == "toolName" && tool.matches_range("vers:generic/>=1.2.3|<3")) + """, PolicyViolation.Type.OPERATIONAL); + + final var project = new Project(); + project.setName("acme-app"); + project.setVersion("0.1"); + qm.persist(project); + + final var toolComponent = new Component(); + toolComponent.setName("toolName"); + toolComponent.setVersion("2.3.1"); + + final var projectMetadata = new ProjectMetadata(); + projectMetadata.setProject(project); + projectMetadata.setTools(new Tools(List.of(toolComponent), null)); + qm.persist(projectMetadata); + + final var componentA = new Component(); + componentA.setProject(project); + componentA.setName("acme-lib-a"); + componentA.setVersion("v1.9.3"); + qm.persist(componentA); + + assertThatNoException().isThrownBy(() -> new CelPolicyEngine().evaluateProject(project.getUuid())); + assertThat(qm.getAllPolicyViolations(componentA)).hasSize(1); + + toolComponent.setVersion("3.1"); + projectMetadata.setTools(new Tools(List.of(toolComponent), null)); + qm.persist(projectMetadata); + + assertThatNoException().isThrownBy(() -> new CelPolicyEngine().evaluateProject(project.getUuid())); + assertThat(qm.getAllPolicyViolations(componentA)).isEmpty(); + } + @Test public void testEvaluateProjectWhenProjectDoesNotExist() { assertThatNoException().isThrownBy(() -> new CelPolicyEngine().evaluateProject(UUID.randomUUID())); diff --git a/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java b/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java index 86c1c8cd3..876ef1503 100644 --- a/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java +++ b/src/test/java/org/dependencytrack/tasks/BomUploadProcessingTaskTest.java @@ -907,6 +907,63 @@ public void informWithBomContainingServiceTest() throws Exception { assertThat(qm.getAllServiceComponents(project)).isNotEmpty(); } + @Test + public void informWithBomContainingMetadataToolsDeprecatedTest() throws Exception { + final Project project = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); + + final var bomUploadEvent = new BomUploadEvent(qm.detach(Project.class, project.getId()), createTempBomFile("bom-metadata-tool-deprecated.json")); + qm.createWorkflowSteps(bomUploadEvent.getChainIdentifier()); + new BomUploadProcessingTask().inform(bomUploadEvent); + assertBomProcessedNotification(); + + qm.getPersistenceManager().refresh(project); + assertThat(project.getMetadata()).isNotNull(); + assertThat(project.getMetadata().getTools()).isNotNull(); + assertThat(project.getMetadata().getTools().components()).satisfiesExactly(component -> { + assertThat(component.getSupplier()).isNotNull(); + assertThat(component.getSupplier().getName()).isEqualTo("Awesome Vendor"); + assertThat(component.getName()).isEqualTo("Awesome Tool"); + assertThat(component.getVersion()).isEqualTo("9.1.2"); + assertThat(component.getSha1()).isEqualTo("25ed8e31b995bb927966616df2a42b979a2717f0"); + assertThat(component.getSha256()).isEqualTo("a74f733635a19aefb1f73e5947cef59cd7440c6952ef0f03d09d974274cbd6df"); + }); + assertThat(project.getMetadata().getTools().services()).isNull(); + } + + @Test + public void informWithBomContainingMetadataToolsTest() throws Exception { + final Project project = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); + + final var bomUploadEvent = new BomUploadEvent(qm.detach(Project.class, project.getId()), createTempBomFile("bom-metadata-tool.json")); + qm.createWorkflowSteps(bomUploadEvent.getChainIdentifier()); + new BomUploadProcessingTask().inform(bomUploadEvent); + assertBomProcessedNotification(); + + qm.getPersistenceManager().refresh(project); + assertThat(project.getMetadata()).isNotNull(); + assertThat(project.getMetadata().getTools()).isNotNull(); + assertThat(project.getMetadata().getTools().components()).satisfiesExactly(component -> { + assertThat(component.getGroup()).isEqualTo("Awesome Vendor"); + assertThat(component.getName()).isEqualTo("Awesome Tool"); + assertThat(component.getVersion()).isEqualTo("9.1.2"); + assertThat(component.getSha1()).isEqualTo("25ed8e31b995bb927966616df2a42b979a2717f0"); + assertThat(component.getSha256()).isEqualTo("a74f733635a19aefb1f73e5947cef59cd7440c6952ef0f03d09d974274cbd6df"); + }); + assertThat(project.getMetadata().getTools().services()).satisfiesExactly(service -> { + assertThat(service.getProvider()).isNotNull(); + assertThat(service.getProvider().getName()).isEqualTo("Acme Org"); + assertThat(service.getProvider().getUrls()).containsOnly("https://example.com"); + assertThat(service.getGroup()).isEqualTo("com.example"); + assertThat(service.getName()).isEqualTo("Acme Signing Server"); + assertThat(service.getDescription()).isEqualTo("Signs artifacts"); + assertThat(service.getEndpoints()).containsExactlyInAnyOrder( + "https://example.com/sign", + "https://example.com/verify", + "https://example.com/tsa" + ); + }); + } + private void assertBomProcessedNotification() throws Exception { try { assertThat(kafkaMockProducer.history()).anySatisfy(record -> { diff --git a/src/test/resources/unit/bom-metadata-tool-deprecated.json b/src/test/resources/unit/bom-metadata-tool-deprecated.json new file mode 100644 index 000000000..7e578d7f5 --- /dev/null +++ b/src/test/resources/unit/bom-metadata-tool-deprecated.json @@ -0,0 +1,26 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", + "version": 1, + "metadata": { + "tools": [ + { + "vendor": "Awesome Vendor", + "name": "Awesome Tool", + "version": "9.1.2", + "hashes": [ + { + "alg": "SHA-1", + "content": "25ed8e31b995bb927966616df2a42b979a2717f0" + }, + { + "alg": "SHA-256", + "content": "a74f733635a19aefb1f73e5947cef59cd7440c6952ef0f03d09d974274cbd6df" + } + ] + } + ] + }, + "components": [] +} \ No newline at end of file diff --git a/src/test/resources/unit/bom-metadata-tool.json b/src/test/resources/unit/bom-metadata-tool.json new file mode 100644 index 000000000..aa55d6765 --- /dev/null +++ b/src/test/resources/unit/bom-metadata-tool.json @@ -0,0 +1,47 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", + "version": 1, + "metadata": { + "tools": { + "components": [ + { + "type": "application", + "group": "Awesome Vendor", + "name": "Awesome Tool", + "version": "9.1.2", + "hashes": [ + { + "alg": "SHA-1", + "content": "25ed8e31b995bb927966616df2a42b979a2717f0" + }, + { + "alg": "SHA-256", + "content": "a74f733635a19aefb1f73e5947cef59cd7440c6952ef0f03d09d974274cbd6df" + } + ] + } + ], + "services": [ + { + "provider": { + "name": "Acme Org", + "url": [ + "https://example.com" + ] + }, + "group": "com.example", + "name": "Acme Signing Server", + "description": "Signs artifacts", + "endpoints": [ + "https://example.com/sign", + "https://example.com/verify", + "https://example.com/tsa" + ] + } + ] + } + }, + "components": [] +} \ No newline at end of file From a2dc4ab4e06d19e30aafd1500d2136b2392a7bcf Mon Sep 17 00:00:00 2001 From: nscuro Date: Wed, 28 Feb 2024 13:18:41 +0100 Subject: [PATCH 05/34] Fix race condition in `doesProjectExist` ... by enforcing uniqueness as database constraints, instead of trying to do it in the application code. This bug affects DT v4.x as well, but because we can't create partial indexes there (DataNucleus doesn't give us that much control), we can't really fix it. Fixes https://github.com/DependencyTrack/hyades/issues/1101 Signed-off-by: nscuro --- .../resources/v1/ProjectResource.java | 77 +++++++++++-------- .../dependencytrack/util/PersistenceUtil.java | 7 +- .../resources/migration/changelog-v5.4.0.xml | 15 ++++ .../resources/v1/ProjectResourceTest.java | 41 ++++++++++ 4 files changed, 102 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java index 7f6f668ca..d5de08850 100644 --- a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java @@ -61,6 +61,8 @@ import java.util.function.BiConsumer; import java.util.function.Function; +import static org.dependencytrack.util.PersistenceUtil.isUniqueConstraintViolation; + /** * JAX-RS resources for processing projects. * @@ -248,21 +250,24 @@ public Response createProject(Project jsonProject) { Project parent = qm.getObjectByUuid(Project.class, jsonProject.getParent().getUuid()); jsonProject.setParent(parent); } - if (!qm.doesProjectExist(StringUtils.trimToNull(jsonProject.getName()), StringUtils.trimToNull(jsonProject.getVersion()))) { - final Project project; - try { - project = qm.createProject(jsonProject, jsonProject.getTags(), true); - } catch (IllegalArgumentException e) { - LOGGER.debug(e.getMessage()); - return Response.status(Response.Status.CONFLICT).entity("An inactive Parent cannot be selected as parent").build(); + final Project project; + try { + project = qm.createProject(jsonProject, jsonProject.getTags(), true); + } catch (IllegalArgumentException e) { + LOGGER.debug("Failed to create project %s".formatted(jsonProject), e); + return Response.status(Response.Status.CONFLICT).entity("An inactive Parent cannot be selected as parent").build(); + } catch (RuntimeException e) { + if (isUniqueConstraintViolation(e)) { + return Response.status(Response.Status.CONFLICT).entity("A project with the specified name already exists.").build(); } - Principal principal = getPrincipal(); - qm.updateNewProjectACL(project, principal); - LOGGER.info("Project " + project.toString() + " created by " + super.getPrincipal().getName()); - return Response.status(Response.Status.CREATED).entity(project).build(); - } else { - return Response.status(Response.Status.CONFLICT).entity("A project with the specified name already exists.").build(); + + LOGGER.error("Failed to create project %s".formatted(jsonProject), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } + Principal principal = getPrincipal(); + qm.updateNewProjectACL(project, principal); + LOGGER.info("Project " + project.toString() + " created by " + super.getPrincipal().getName()); + return Response.status(Response.Status.CREATED).entity(project).build(); } } @@ -303,24 +308,25 @@ public Response updateProject(Project jsonProject) { return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build(); } final String name = StringUtils.trimToNull(jsonProject.getName()); - final String version = StringUtils.trimToNull(jsonProject.getVersion()); - final Project tmpProject = qm.getProject(name, version); - if (tmpProject == null || (tmpProject.getUuid().equals(project.getUuid()))) { - // Name cannot be empty or null - prevent it - if (name == null) { - jsonProject.setName(project.getName()); - } - try { - project = qm.updateProject(jsonProject, true); - } catch (IllegalArgumentException e) { - LOGGER.debug(e.getMessage()); - return Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build(); + // Name cannot be empty or null - prevent it + if (name == null) { + jsonProject.setName(project.getName()); + } + try { + project = qm.updateProject(jsonProject, true); + } catch (IllegalArgumentException e) { + LOGGER.debug("Failed to update project %s".formatted(jsonProject.getUuid()), e); + return Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build(); + } catch (RuntimeException e) { + if (isUniqueConstraintViolation(e)) { + return Response.status(Response.Status.CONFLICT).entity("A project with the specified name and version already exists.").build(); } - LOGGER.info("Project " + project.toString() + " updated by " + super.getPrincipal().getName()); - return Response.ok(project).build(); - } else { - return Response.status(Response.Status.CONFLICT).entity("A project with the specified name and version already exists.").build(); + + LOGGER.error("Failed to update project %s".formatted(jsonProject.getUuid()), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } + LOGGER.info("Project " + project.toString() + " updated by " + super.getPrincipal().getName()); + return Response.ok(project).build(); } else { return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build(); } @@ -369,10 +375,6 @@ public Response patchProject( project = qm.detachWithGroups(project, List.of(FetchGroup.DEFAULT, Project.FetchGroup.PARENT.name())); modified |= setIfDifferent(jsonProject, project, Project::getName, Project::setName); modified |= setIfDifferent(jsonProject, project, Project::getVersion, Project::setVersion); - // if either name or version has been changed, verify that this new combination does not already exist - if (modified && qm.doesProjectExist(project.getName(), project.getVersion())) { - return Response.status(Response.Status.CONFLICT).entity("A project with the specified name and version already exists.").build(); - } modified |= setIfDifferent(jsonProject, project, Project::getAuthor, Project::setAuthor); modified |= setIfDifferent(jsonProject, project, Project::getPublisher, Project::setPublisher); modified |= setIfDifferent(jsonProject, project, Project::getGroup, Project::setGroup); @@ -407,8 +409,15 @@ public Response patchProject( try { project = qm.updateProject(project, true); } catch (IllegalArgumentException e) { - LOGGER.debug(e.getMessage()); + LOGGER.debug("Failed to patch project %s".formatted(uuid)); return Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build(); + } catch (RuntimeException e) { + if (isUniqueConstraintViolation(e)) { + return Response.status(Response.Status.CONFLICT).entity("A project with the specified name and version already exists.").build(); + } + + LOGGER.error("Failed to patch project %s".formatted(uuid), e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); } LOGGER.info("Project " + project.toString() + " updated by " + super.getPrincipal().getName()); return Response.ok(project).build(); diff --git a/src/main/java/org/dependencytrack/util/PersistenceUtil.java b/src/main/java/org/dependencytrack/util/PersistenceUtil.java index 4ee83db0b..0344fdb48 100644 --- a/src/main/java/org/dependencytrack/util/PersistenceUtil.java +++ b/src/main/java/org/dependencytrack/util/PersistenceUtil.java @@ -85,10 +85,9 @@ public static boolean applyIfChanged(final T existingObject, final T newO } public static boolean isUniqueConstraintViolation(final Throwable throwable) { - // TODO: DataNucleus doesn't map constraint violation exceptions very well, - // so we have to depend on the exception of the underlying JDBC driver to - // tell us what happened. We currently only handle PostgreSQL, but we'll have - // to do the same for at least H2 and MSSQL. + // NB: DataNucleus doesn't map constraint violation exceptions very well, + // so we have to depend on the exception of the underlying JDBC driver to + // tell us what happened. return ExceptionUtils.getRootCause(throwable) instanceof final SQLException se && PSQLState.UNIQUE_VIOLATION.getState().equals(se.getSQLState()); } diff --git a/src/main/resources/migration/changelog-v5.4.0.xml b/src/main/resources/migration/changelog-v5.4.0.xml index 56e32f357..cc624c035 100644 --- a/src/main/resources/migration/changelog-v5.4.0.xml +++ b/src/main/resources/migration/changelog-v5.4.0.xml @@ -13,4 +13,19 @@ + + + + + CREATE UNIQUE INDEX "PROJECT_NAME_VERSION_IDX" ON "PROJECT" ("NAME", "VERSION") + WHERE "VERSION" IS NOT NULL; + CREATE UNIQUE INDEX "PROJECT_NAME_VERSION_NULL_IDX" ON "PROJECT" ("NAME") + WHERE "VERSION" IS NULL; + + \ No newline at end of file diff --git a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java index 9342e27ac..6e1ecf0e3 100644 --- a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java @@ -64,6 +64,11 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -389,6 +394,42 @@ public void createProjectDuplicateTest() { Assert.assertEquals("A project with the specified name already exists.", body); } + @Test + public void createProjectDuplicateRaceConditionTest() throws Exception { + final ExecutorService executor = Executors.newFixedThreadPool(10); + final var countDownLatch = new CountDownLatch(1); + + final var responses = new ArrayBlockingQueue(50); + for (int i = 0; i < 50; i++) { + executor.submit(() -> { + try { + countDownLatch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + final Response response = target(V1_PROJECT) + .request() + .header(X_API_KEY, apiKey) + .put(Entity.entity(""" + { + "name": "acme-app", + "version": "1.0.0" + } + """, MediaType.APPLICATION_JSON)); + responses.offer(response); + }); + } + + countDownLatch.countDown(); + executor.shutdown(); + assertThat(executor.awaitTermination(15, TimeUnit.SECONDS)).isTrue(); + + assertThat(responses).hasSize(50); + assertThat(responses).satisfiesOnlyOnce(response -> assertThat(response.getStatus()).isEqualTo(201)); + assertThat(responses.stream().map(Response::getStatus).filter(status -> status != 201)).containsOnly(409); + } + @Test public void createProjectEmptyTest() { Project project = new Project(); From e8b8158f31a70c4f5e28959a384f20e4cd28d32c Mon Sep 17 00:00:00 2001 From: nscuro Date: Wed, 28 Feb 2024 15:37:56 +0100 Subject: [PATCH 06/34] Fix failing tests due to new `UNIQUE` constraints Tests made bad assumptions about duplicate projects being allowed. Also, remove `JdbiTestUtil` which was only necessary to run JDBI tests with H2. Since we dropped H2, this is no longer required. Signed-off-by: nscuro --- .../persistence/jdbi/JdbiTestUtil.java | 22 --- .../jdbi/binding/DefineOrderingTest.java | 172 +++++++++--------- .../jdbi/binding/DefinePaginationTest.java | 4 +- .../PaginatedResultRowReducerTest.java | 4 +- .../resources/v1/FindingResourceTest.java | 6 +- 5 files changed, 97 insertions(+), 111 deletions(-) delete mode 100644 src/test/java/org/dependencytrack/persistence/jdbi/JdbiTestUtil.java diff --git a/src/test/java/org/dependencytrack/persistence/jdbi/JdbiTestUtil.java b/src/test/java/org/dependencytrack/persistence/jdbi/JdbiTestUtil.java deleted file mode 100644 index 1c80d9001..000000000 --- a/src/test/java/org/dependencytrack/persistence/jdbi/JdbiTestUtil.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.dependencytrack.persistence.jdbi; - -import org.dependencytrack.persistence.QueryManager; -import org.jdbi.v3.core.Jdbi; - -public final class JdbiTestUtil { - - private JdbiTestUtil() { - } - - /** - * Create a {@link Jdbi} instance from a {@link QueryManager}, without any of - * the plugins and extensions registered by {@link JdbiFactory}. - * - * @param qm The {@link QueryManager} to use - * @return A new {@link Jdbi} instance - */ - public static Jdbi createLocalVanillaJdbi(final QueryManager qm) { - return Jdbi.create(new JdoConnectionFactory(qm.getPersistenceManager())); - } - -} diff --git a/src/test/java/org/dependencytrack/persistence/jdbi/binding/DefineOrderingTest.java b/src/test/java/org/dependencytrack/persistence/jdbi/binding/DefineOrderingTest.java index 88f45f099..8a2ab920e 100644 --- a/src/test/java/org/dependencytrack/persistence/jdbi/binding/DefineOrderingTest.java +++ b/src/test/java/org/dependencytrack/persistence/jdbi/binding/DefineOrderingTest.java @@ -2,6 +2,7 @@ import alpine.persistence.OrderDirection; import org.dependencytrack.PersistenceCapableTest; +import org.dependencytrack.model.Component; import org.dependencytrack.model.Project; import org.dependencytrack.persistence.Ordering; import org.jdbi.v3.core.Jdbi; @@ -17,53 +18,59 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.dependencytrack.persistence.jdbi.JdbiTestUtil.createLocalVanillaJdbi; +import static org.dependencytrack.persistence.jdbi.JdbiFactory.jdbi; public class DefineOrderingTest extends PersistenceCapableTest { public interface TestDao { - @SqlQuery("SELECT \"ID\" AS \"id\", \"NAME\" AS \"nameAlias\" FROM \"PROJECT\" ${ordering!}") - List getProjects(@DefineOrdering(allowedColumns = "nameAlias") Ordering ordering); + @SqlQuery("SELECT \"ID\" AS \"id\", \"NAME\" AS \"nameAlias\" FROM \"COMPONENT\" ${ordering!}") + List getComponents(@DefineOrdering(allowedColumns = "nameAlias") Ordering ordering); - @SqlQuery("SELECT \"ID\" AS \"id\", \"NAME\" AS \"nameAlias\" FROM \"PROJECT\" ${ordering!}") - List getProjectsWithOrderingAlsoById(@DefineOrdering(allowedColumns = {"id", "nameAlias"}, alsoBy = "id DESC") Ordering ordering); + @SqlQuery("SELECT \"ID\" AS \"id\", \"NAME\" AS \"nameAlias\" FROM \"COMPONENT\" ${ordering!}") + List getComponentsWithOrderingAlsoById(@DefineOrdering(allowedColumns = {"id", "nameAlias"}, alsoBy = "id DESC") Ordering ordering); } private Jdbi jdbi; - private final Map projectIdsByName = new HashMap<>(); + private Project project; + private final Map componentIdsByName = new HashMap<>(); @Before public void setUp() { - jdbi = createLocalVanillaJdbi(qm) + jdbi = jdbi(qm) .installPlugin(new SqlObjectPlugin()) .setTemplateEngine(FreemarkerEngine.instance()) - .registerRowMapper(Project.class, (rs, ctx) -> { - final var project = new Project(); - project.setId(rs.getLong("id")); - project.setName(rs.getString("nameAlias")); - return project; + .registerRowMapper(Component.class, (rs, ctx) -> { + final var component = new Component(); + component.setId(rs.getLong("id")); + component.setName(rs.getString("nameAlias")); + return component; }); + project = new Project(); + project.setName("project"); + qm.persist(project); + for (int i = 0; i < 5; i++) { - final var project = new Project(); - project.setName("project-" + i); - qm.persist(project); + final var component = new Component(); + component.setProject(project); + component.setName("component-" + i); + qm.persist(component); - projectIdsByName.put(project.getName(), project.getId()); + componentIdsByName.put(component.getName(), component.getId()); } } @Test public void testWithNullOrdering() { - final List projects = jdbi.withExtension(TestDao.class, dao -> dao.getProjects(null)); - assertThat(projects).extracting(Project::getName).containsExactlyInAnyOrder( - "project-0", - "project-1", - "project-2", - "project-3", - "project-4" + final List components = jdbi.withExtension(TestDao.class, dao -> dao.getComponents(null)); + assertThat(components).extracting(Component::getName).containsExactlyInAnyOrder( + "component-0", + "component-1", + "component-2", + "component-3", + "component-4" ); } @@ -71,93 +78,94 @@ public void testWithNullOrdering() { public void testWithDisallowedColumn() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> jdbi.useExtension(TestDao.class, - dao -> dao.getProjects(new Ordering("NAME", OrderDirection.ASCENDING)))); + dao -> dao.getComponents(new Ordering("NAME", OrderDirection.ASCENDING)))); } @Test public void testWithOrderDirectionAscending() { - final List projects = jdbi.withExtension(TestDao.class, - dao -> dao.getProjects(new Ordering("nameAlias", OrderDirection.ASCENDING))); - - assertThat(projects).extracting(Project::getName).containsExactly( - "project-0", - "project-1", - "project-2", - "project-3", - "project-4" + final List components = jdbi.withExtension(TestDao.class, + dao -> dao.getComponents(new Ordering("nameAlias", OrderDirection.ASCENDING))); + + assertThat(components).extracting(Component::getName).containsExactly( + "component-0", + "component-1", + "component-2", + "component-3", + "component-4" ); } @Test public void testWithOrderDirectionDescending() { - final List projects = jdbi.withExtension(TestDao.class, - dao -> dao.getProjects(new Ordering("nameAlias", OrderDirection.DESCENDING))); - - assertThat(projects).extracting(Project::getName).containsExactly( - "project-4", - "project-3", - "project-2", - "project-1", - "project-0" + final List components = jdbi.withExtension(TestDao.class, + dao -> dao.getComponents(new Ordering("nameAlias", OrderDirection.DESCENDING))); + + assertThat(components).extracting(Component::getName).containsExactly( + "component-4", + "component-3", + "component-2", + "component-1", + "component-0" ); } @Test public void testWithOrderDirectionUnspecified() { - final List projects = jdbi.withExtension(TestDao.class, - dao -> dao.getProjects(new Ordering("nameAlias", OrderDirection.UNSPECIFIED))); - - assertThat(projects).extracting(Project::getName).containsExactly( - "project-0", - "project-1", - "project-2", - "project-3", - "project-4" + final List components = jdbi.withExtension(TestDao.class, + dao -> dao.getComponents(new Ordering("nameAlias", OrderDirection.UNSPECIFIED))); + + assertThat(components).extracting(Component::getName).containsExactly( + "component-0", + "component-1", + "component-2", + "component-3", + "component-4" ); } @Test public void testWithOrderingAlsoBy() { - final var duplicateProjectIdsByName = new HashMap(); + final var duplicateComponentIdsByName = new HashMap(); for (int i = 0; i < 2; i++) { - final var project = new Project(); - project.setName("project-" + i); - qm.persist(project); + final var component = new Component(); + component.setProject(project); + component.setName("component-" + i); + qm.persist(component); - duplicateProjectIdsByName.put(project.getName(), project.getId()); + duplicateComponentIdsByName.put(component.getName(), component.getId()); } - assertThat(qm.getCount(Project.class)).isEqualTo(7); - - final List projects = jdbi.withExtension(TestDao.class, - dao -> dao.getProjectsWithOrderingAlsoById(new Ordering("nameAlias", OrderDirection.ASCENDING))); - assertThat(projects).satisfiesExactly( - project -> { - assertThat(project.getId()).isEqualTo(duplicateProjectIdsByName.get("project-0")); - assertThat(project.getName()).isEqualTo("project-0"); + assertThat(qm.getCount(Component.class)).isEqualTo(7); + + final List components = jdbi.withExtension(TestDao.class, + dao -> dao.getComponentsWithOrderingAlsoById(new Ordering("nameAlias", OrderDirection.ASCENDING))); + assertThat(components).satisfiesExactly( + component -> { + assertThat(component.getId()).isEqualTo(duplicateComponentIdsByName.get("component-0")); + assertThat(component.getName()).isEqualTo("component-0"); }, - project -> { - assertThat(project.getId()).isEqualTo(projectIdsByName.get("project-0")); - assertThat(project.getName()).isEqualTo("project-0"); + component -> { + assertThat(component.getId()).isEqualTo(componentIdsByName.get("component-0")); + assertThat(component.getName()).isEqualTo("component-0"); }, - project -> { - assertThat(project.getId()).isEqualTo(duplicateProjectIdsByName.get("project-1")); - assertThat(project.getName()).isEqualTo("project-1"); + component -> { + assertThat(component.getId()).isEqualTo(duplicateComponentIdsByName.get("component-1")); + assertThat(component.getName()).isEqualTo("component-1"); }, - project -> { - assertThat(project.getId()).isEqualTo(projectIdsByName.get("project-1")); - assertThat(project.getName()).isEqualTo("project-1"); + component -> { + assertThat(component.getId()).isEqualTo(componentIdsByName.get("component-1")); + assertThat(component.getName()).isEqualTo("component-1"); }, - project -> { - assertThat(project.getId()).isEqualTo(projectIdsByName.get("project-2")); - assertThat(project.getName()).isEqualTo("project-2"); + component -> { + assertThat(component.getId()).isEqualTo(componentIdsByName.get("component-2")); + assertThat(component.getName()).isEqualTo("component-2"); }, - project -> { - assertThat(project.getId()).isEqualTo(projectIdsByName.get("project-3")); - assertThat(project.getName()).isEqualTo("project-3"); + component -> { + assertThat(component.getId()).isEqualTo(componentIdsByName.get("component-3")); + assertThat(component.getName()).isEqualTo("component-3"); }, - project -> { - assertThat(project.getId()).isEqualTo(projectIdsByName.get("project-4")); - assertThat(project.getName()).isEqualTo("project-4"); + component -> { + assertThat(component.getId()).isEqualTo(componentIdsByName.get("component-4")); + assertThat(component.getName()).isEqualTo("component-4"); } ); } diff --git a/src/test/java/org/dependencytrack/persistence/jdbi/binding/DefinePaginationTest.java b/src/test/java/org/dependencytrack/persistence/jdbi/binding/DefinePaginationTest.java index 1b0df0cf0..0e13eb5e1 100644 --- a/src/test/java/org/dependencytrack/persistence/jdbi/binding/DefinePaginationTest.java +++ b/src/test/java/org/dependencytrack/persistence/jdbi/binding/DefinePaginationTest.java @@ -13,7 +13,7 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.dependencytrack.persistence.jdbi.JdbiTestUtil.createLocalVanillaJdbi; +import static org.dependencytrack.persistence.jdbi.JdbiFactory.jdbi; public class DefinePaginationTest extends PersistenceCapableTest { @@ -28,7 +28,7 @@ public interface TestDao { @Before public void setUp() { - jdbi = createLocalVanillaJdbi(qm) + jdbi = jdbi(qm) .installPlugin(new SqlObjectPlugin()) .setTemplateEngine(FreemarkerEngine.instance()); diff --git a/src/test/java/org/dependencytrack/persistence/jdbi/mapping/PaginatedResultRowReducerTest.java b/src/test/java/org/dependencytrack/persistence/jdbi/mapping/PaginatedResultRowReducerTest.java index 987c2f942..2e0ec60b1 100644 --- a/src/test/java/org/dependencytrack/persistence/jdbi/mapping/PaginatedResultRowReducerTest.java +++ b/src/test/java/org/dependencytrack/persistence/jdbi/mapping/PaginatedResultRowReducerTest.java @@ -15,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.dependencytrack.persistence.jdbi.JdbiTestUtil.createLocalVanillaJdbi; +import static org.dependencytrack.persistence.jdbi.JdbiFactory.jdbi; public class PaginatedResultRowReducerTest extends PersistenceCapableTest { @@ -48,7 +48,7 @@ public interface TestDao { @Before public void setUp() { - jdbi = createLocalVanillaJdbi(qm) + jdbi = jdbi(qm) .installPlugin(new SqlObjectPlugin()) .setTemplateEngine(FreemarkerEngine.instance()) .registerRowMapper(String.class, (rs, ctx) -> rs.getString("NAME")); diff --git a/src/test/java/org/dependencytrack/resources/v1/FindingResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/FindingResourceTest.java index 6a683a63f..bfee0c95b 100644 --- a/src/test/java/org/dependencytrack/resources/v1/FindingResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/FindingResourceTest.java @@ -72,7 +72,7 @@ public void setUp() throws Exception { @Test public void getFindingsByProjectTest() { Project p1 = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); - Project p2 = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); + Project p2 = qm.createProject("Acme Example", null, "2.0", null, null, null, true, false); Component c1 = createComponent(p1, "Component A", "1.0"); Component c2 = createComponent(p1, "Component B", "1.0"); Component c3 = createComponent(p1, "Component C", "1.0"); @@ -151,7 +151,7 @@ public void getFindingsByProjectInvalidTest() { @Test public void exportFindingsByProjectTest() { Project p1 = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); - Project p2 = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); + Project p2 = qm.createProject("Acme Example", null, "2.0", null, null, null, true, false); Component c1 = createComponent(p1, "Component A", "1.0"); Component c2 = createComponent(p1, "Component B", "1.0"); Component c3 = createComponent(p1, "Component C", "1.0"); @@ -238,7 +238,7 @@ public void exportFindingsByProjectInvalidTest() { @Test public void getFindingsByProjectWithComponentLatestVersionTest() { Project p1 = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); - Project p2 = qm.createProject("Acme Example", null, "1.0", null, null, null, true, false); + Project p2 = qm.createProject("Acme Example", null, "2.0", null, null, null, true, false); Component c1 = createComponent(p1, "Component A", "1.0"); c1.setPurl("pkg:/maven/org.acme/component-a@1.0.0"); RepositoryMetaComponent r1 = new RepositoryMetaComponent(); From 80434c04e06f3b713e29ae7d0a92ee25d54994a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 14:57:07 +0000 Subject: [PATCH 07/34] Bump lib.net.javacrumbs.shedlock.version from 5.11.0 to 5.12.0 Bumps `lib.net.javacrumbs.shedlock.version` from 5.11.0 to 5.12.0. Updates `net.javacrumbs.shedlock:shedlock-provider-jdbc` from 5.11.0 to 5.12.0 Updates `net.javacrumbs.shedlock:shedlock-provider-jdbc-internal` from 5.11.0 to 5.12.0 --- updated-dependencies: - dependency-name: net.javacrumbs.shedlock:shedlock-provider-jdbc dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: net.javacrumbs.shedlock:shedlock-provider-jdbc-internal dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 41360c40e..be92841f1 100644 --- a/pom.xml +++ b/pom.xml @@ -115,7 +115,7 @@ 1.1.1 2.0.12 4.5.14 - 5.11.0 + 5.12.0 1.4.0 42.7.1 From 9f4e6d5872d3e6125982ccd87bb8313db2da2be9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:06:46 +0000 Subject: [PATCH 08/34] Bump actions/setup-java from 4.0.0 to 4.1.0 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4.0.0 to 4.1.0. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4.0.0...v4.1.0) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/_meta-build.yaml | 2 +- .github/workflows/ci-release.yaml | 2 +- .github/workflows/ci-test.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/_meta-build.yaml b/.github/workflows/_meta-build.yaml index 63bdb74a5..8069a8caf 100644 --- a/.github/workflows/_meta-build.yaml +++ b/.github/workflows/_meta-build.yaml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Set up JDK - uses: actions/setup-java@v4.0.0 + uses: actions/setup-java@v4.1.0 with: distribution: 'temurin' java-version: '17' diff --git a/.github/workflows/ci-release.yaml b/.github/workflows/ci-release.yaml index e2280f44c..829566b1d 100644 --- a/.github/workflows/ci-release.yaml +++ b/.github/workflows/ci-release.yaml @@ -54,7 +54,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Set up JDK - uses: actions/setup-java@v4.0.0 + uses: actions/setup-java@v4.1.0 with: distribution: 'temurin' java-version: '17' diff --git a/.github/workflows/ci-test.yaml b/.github/workflows/ci-test.yaml index 31624e79b..cc6eb2d41 100644 --- a/.github/workflows/ci-test.yaml +++ b/.github/workflows/ci-test.yaml @@ -34,7 +34,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Set up JDK - uses: actions/setup-java@v4.0.0 + uses: actions/setup-java@v4.1.0 with: distribution: 'temurin' java-version: '17' From ec925c285f8d397eba33d99494c78c0b9a9b4756 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:06:49 +0000 Subject: [PATCH 09/34] Bump actions/download-artifact from 4.1.2 to 4.1.4 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.2 to 4.1.4. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4.1.2...v4.1.4) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/_meta-build.yaml | 2 +- .github/workflows/ci-publish.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_meta-build.yaml b/.github/workflows/_meta-build.yaml index 63bdb74a5..beae23025 100644 --- a/.github/workflows/_meta-build.yaml +++ b/.github/workflows/_meta-build.yaml @@ -70,7 +70,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Download Artifacts - uses: actions/download-artifact@v4.1.2 + uses: actions/download-artifact@v4.1.4 with: name: assembled-wars path: target diff --git a/.github/workflows/ci-publish.yaml b/.github/workflows/ci-publish.yaml index 61cce0de8..2a19625b8 100644 --- a/.github/workflows/ci-publish.yaml +++ b/.github/workflows/ci-publish.yaml @@ -55,7 +55,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Download Artifacts - uses: actions/download-artifact@v4.1.2 + uses: actions/download-artifact@v4.1.4 with: name: assembled-wars path: target From a69d0c302a46d7e4a6e2d3eb72da4eb6ebbb82a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:06:52 +0000 Subject: [PATCH 10/34] Bump aquasecurity/trivy-action from 0.17.0 to 0.18.0 Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.17.0 to 0.18.0. - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/0.17.0...0.18.0) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/_meta-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_meta-build.yaml b/.github/workflows/_meta-build.yaml index 63bdb74a5..bbb09aafc 100644 --- a/.github/workflows/_meta-build.yaml +++ b/.github/workflows/_meta-build.yaml @@ -116,7 +116,7 @@ jobs: - name: Run Trivy Vulnerability Scanner if: ${{ inputs.publish-container }} - uses: aquasecurity/trivy-action@0.17.0 + uses: aquasecurity/trivy-action@0.18.0 with: image-ref: ghcr.io/dependencytrack/hyades-apiserver:${{ inputs.app-version }} format: 'sarif' From 1f5d524c5530fae10b76dc3bfcd5ee8e1fb15a08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:06:56 +0000 Subject: [PATCH 11/34] Bump docker/setup-buildx-action from 3.0.0 to 3.1.0 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v3.0.0...v3.1.0) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/_meta-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_meta-build.yaml b/.github/workflows/_meta-build.yaml index 63bdb74a5..170bba0c8 100644 --- a/.github/workflows/_meta-build.yaml +++ b/.github/workflows/_meta-build.yaml @@ -79,7 +79,7 @@ jobs: uses: docker/setup-qemu-action@v3.0.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.0.0 + uses: docker/setup-buildx-action@v3.1.0 id: buildx with: install: true From 4a331fa18bd1369f023c8dc5fe69c5d6c787cfae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:54:16 +0000 Subject: [PATCH 12/34] Bump io.minio:minio from 8.5.8 to 8.5.9 Bumps [io.minio:minio](https://github.com/minio/minio-java) from 8.5.8 to 8.5.9. - [Release notes](https://github.com/minio/minio-java/releases) - [Commits](https://github.com/minio/minio-java/compare/8.5.8...8.5.9) --- updated-dependencies: - dependency-name: io.minio:minio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index be92841f1..9b8d54b89 100644 --- a/pom.xml +++ b/pom.xml @@ -102,7 +102,7 @@ 3.6.0 4.26.0 0.2.2 - 8.5.8 + 8.5.9 1.5.0 0.5.2.8 3.2.2 From 7107f90db4642242b4c4a505bb3c6114e16625a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:54:23 +0000 Subject: [PATCH 13/34] Bump org.json:json from 20240205 to 20240303 Bumps [org.json:json](https://github.com/douglascrockford/JSON-java) from 20240205 to 20240303. - [Release notes](https://github.com/douglascrockford/JSON-java/releases) - [Changelog](https://github.com/stleary/JSON-java/blob/master/docs/RELEASES.md) - [Commits](https://github.com/douglascrockford/JSON-java/commits) --- updated-dependencies: - dependency-name: org.json:json dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index be92841f1..8c777ea56 100644 --- a/pom.xml +++ b/pom.xml @@ -257,7 +257,7 @@ org.json json - 20240205 + 20240303 com.fasterxml.jackson.datatype From dcff669a7ce6f99dc1e803f09fe6d7142913a6d7 Mon Sep 17 00:00:00 2001 From: Niklas Date: Mon, 4 Mar 2024 17:29:08 +0100 Subject: [PATCH 14/34] Report test coverage to Codacy (#602) --- .github/workflows/ci-test-pr-coverage.yml | 32 +++++++++++++++++++++++ .github/workflows/ci-test.yaml | 27 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 .github/workflows/ci-test-pr-coverage.yml diff --git a/.github/workflows/ci-test-pr-coverage.yml b/.github/workflows/ci-test-pr-coverage.yml new file mode 100644 index 000000000..949805988 --- /dev/null +++ b/.github/workflows/ci-test-pr-coverage.yml @@ -0,0 +1,32 @@ +name: Report PR Test Coverage + +on: + workflow_run: + workflows: + - Tests CI + types: + - completed + +permissions: { } + +jobs: + publish: + name: Report Coverage + runs-on: ubuntu-latest + if: |- + github.event.workflow_run.event == 'pull_request' + && github.event.workflow_run.conclusion == 'success' + steps: + - name: Download PR test coverage report + uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # tag=v4.1.4 + with: + name: pr-test-coverage-report + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + - name: Report Coverage to Codacy + run: |- + bash <(curl -Ls https://coverage.codacy.com/get.sh) report \ + --project-token ${{ secrets.CODACY_PROJECT_TOKEN }} \ + --commit-uuid ${{ github.event.workflow_run.head_sha }} \ + --coverage-reports ./target/jacoco-ut/jacoco.xml \ + --language Java \ No newline at end of file diff --git a/.github/workflows/ci-test.yaml b/.github/workflows/ci-test.yaml index cc6eb2d41..770a0bb71 100644 --- a/.github/workflows/ci-test.yaml +++ b/.github/workflows/ci-test.yaml @@ -47,3 +47,30 @@ jobs: run: |- mvn clean mvn test -P enhance + + # Publishing coverage to Codacy is only possible for builds of push events. + # PRs from forks do not get access to repository secrets. + # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + - name: Publish test coverage + if: ${{ github.event_name != 'pull_request' && github.repository_owner == 'DependencyTrack' }} + uses: codacy/codacy-coverage-reporter-action@v1.3.0 + with: + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + language: Java + coverage-reports: target/jacoco-ut/jacoco.xml + + - name: Save PR details + if: ${{ github.event_name == 'pull_request' }} + run: |- + echo ${{ github.sha }} > pr-commit.txt + echo ${{ github.event.number }} > pr-number.txt + + - name: Upload PR test coverage report + if: ${{ github.event_name == 'pull_request' }} + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # tag=v4.3.1 + with: + name: pr-test-coverage-report + path: |- + pr-commit.txt + pr-number.txt + target/jacoco-ut/jacoco.xml From ed5feb862dbf13ecc1061ac8e24d04420e6dacc6 Mon Sep 17 00:00:00 2001 From: nscuro Date: Mon, 4 Mar 2024 18:09:52 +0100 Subject: [PATCH 15/34] Bump PostgreSQL JDBC driver to v42.7.2 Resolves CVE-2024-1597 Signed-off-by: nscuro --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9a950db12..0c553409b 100644 --- a/pom.xml +++ b/pom.xml @@ -117,7 +117,7 @@ 4.5.14 5.12.0 1.4.0 - 42.7.1 + 42.7.2 application json From 1d305b871de62b67cb4d2ed5f85ddea2e2fca689 Mon Sep 17 00:00:00 2001 From: Dependency-Track Bot <106437498+dependencytrack-bot@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:16:12 +0000 Subject: [PATCH 16/34] prepare-release: set version to 5.4.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0c553409b..e7c01ebaa 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ org.dependencytrack dependency-track war - 5.4.0-SNAPSHOT + 5.4.0 Dependency-Track https://dependencytrack.org/ From 245a9f670f2164b011728613a65ee51ae1be83dd Mon Sep 17 00:00:00 2001 From: Niklas Date: Tue, 5 Mar 2024 17:28:03 +0100 Subject: [PATCH 17/34] Bump version to `5.5.0-SNAPSHOT` Signed-off-by: Niklas --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e7c01ebaa..3c12e7b5d 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ org.dependencytrack dependency-track war - 5.4.0 + 5.5.0-SNAPSHOT Dependency-Track https://dependencytrack.org/ From 18173946461e7769ab8ede990507d128fd260339 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:07:25 +0000 Subject: [PATCH 18/34] Bump lib.testcontainers.version from 1.19.6 to 1.19.7 Bumps `lib.testcontainers.version` from 1.19.6 to 1.19.7. Updates `org.testcontainers:redpanda` from 1.19.6 to 1.19.7 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.6...1.19.7) Updates `org.testcontainers:postgresql` from 1.19.6 to 1.19.7 - [Release notes](https://github.com/testcontainers/testcontainers-java/releases) - [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md) - [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.6...1.19.7) --- updated-dependencies: - dependency-name: org.testcontainers:redpanda dependency-type: direct:development update-type: version-update:semver-patch - dependency-name: org.testcontainers:postgresql dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3c12e7b5d..8204f7eca 100644 --- a/pom.xml +++ b/pom.xml @@ -107,7 +107,7 @@ 0.5.2.8 3.2.2 3.25.3 - 1.19.6 + 1.19.7 2.2.0 1.19.0 0.4.1 From b1e8f8e872f4cfa744fef231213467d9cafaa3ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:15:16 +0000 Subject: [PATCH 19/34] Bump org.apache.commons:commons-compress from 1.26.0 to 1.26.1 Bumps org.apache.commons:commons-compress from 1.26.0 to 1.26.1. --- updated-dependencies: - dependency-name: org.apache.commons:commons-compress dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8204f7eca..d2a15c87c 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ 0.4.4 10.13.0 1.16.0 - 1.26.0 + 1.26.1 2.1.0 1.4.2 1.0.1 From 84442e787a286d4843a064fcdfa4ea7df56c5d4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:42:28 +0000 Subject: [PATCH 20/34] Bump bufbuild/buf-setup-action from 1.29.0 to 1.30.0 Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.29.0 to 1.30.0. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/88db93f5d74ffa329bb43e42aa95cd822697d214...517ee23296d5caf38df31c21945e6a54bbc8a89f) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/buf.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/buf.yml b/.github/workflows/buf.yml index 41a665425..6c75083b0 100644 --- a/.github/workflows/buf.yml +++ b/.github/workflows/buf.yml @@ -15,7 +15,7 @@ jobs: - name: Checkout Repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # tag=v4.1.1 - name: Setup buf - uses: bufbuild/buf-setup-action@88db93f5d74ffa329bb43e42aa95cd822697d214 # tag=v1.29.0 + uses: bufbuild/buf-setup-action@517ee23296d5caf38df31c21945e6a54bbc8a89f # tag=v1.30.0 with: github_token: ${{ github.token }} - name: Lint Protobuf From 27153aa4c7158cf90c764202b4d9fd3daec87b68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:42:31 +0000 Subject: [PATCH 21/34] Bump docker/build-push-action from 5.1.0 to 5.2.0 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.1.0 to 5.2.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v5.1.0...v5.2.0) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/_meta-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_meta-build.yaml b/.github/workflows/_meta-build.yaml index 05ff5af6b..feedf2751 100644 --- a/.github/workflows/_meta-build.yaml +++ b/.github/workflows/_meta-build.yaml @@ -102,7 +102,7 @@ jobs: echo "tags=${TAGS}" >> $GITHUB_OUTPUT - name: Build multi-arch Container Image - uses: docker/build-push-action@v5.1.0 + uses: docker/build-push-action@v5.2.0 with: tags: ${{ steps.tags.outputs.tags }} build-args: |- From dced14403dd18099243ecebdcbf70b1771e726da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:47:31 +0000 Subject: [PATCH 22/34] Bump eclipse-temurin from `636b9a7` to `d9f7b83` in /src/main/docker Bumps eclipse-temurin from `636b9a7` to `d9f7b83`. --- updated-dependencies: - dependency-name: eclipse-temurin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- src/main/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/docker/Dockerfile b/src/main/docker/Dockerfile index a82ed8e16..2d1c2e130 100644 --- a/src/main/docker/Dockerfile +++ b/src/main/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM eclipse-temurin:21.0.2_13-jre-jammy@sha256:636b9a74a45bd1845bb49d1aca8763a2477b50717f831bf0818d0acab2cb5e1d AS jre-build +FROM eclipse-temurin:21.0.2_13-jre-jammy@sha256:d9f7b8326b9d396d070432982a998015a04ffb8885b145e97777a5ae324a8df1 AS jre-build FROM debian:stable-slim@sha256:435ba09b2e259426c0552a041eef609b01d4655d9c8467d75be390801068baf3 From 6359503e871c58f9ff94932b4b6f8374a6aa256f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 14:13:36 +0000 Subject: [PATCH 23/34] Bump docker/setup-buildx-action from 3.1.0 to 3.2.0 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v3.1.0...v3.2.0) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/_meta-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_meta-build.yaml b/.github/workflows/_meta-build.yaml index feedf2751..83b982f96 100644 --- a/.github/workflows/_meta-build.yaml +++ b/.github/workflows/_meta-build.yaml @@ -79,7 +79,7 @@ jobs: uses: docker/setup-qemu-action@v3.0.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3.1.0 + uses: docker/setup-buildx-action@v3.2.0 id: buildx with: install: true From a67c458774e236b879809761b03ad732922b17d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 14:13:39 +0000 Subject: [PATCH 24/34] Bump docker/login-action from 3.0.0 to 3.1.0 Bumps [docker/login-action](https://github.com/docker/login-action) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v3.0.0...v3.1.0) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/_meta-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_meta-build.yaml b/.github/workflows/_meta-build.yaml index feedf2751..51886079b 100644 --- a/.github/workflows/_meta-build.yaml +++ b/.github/workflows/_meta-build.yaml @@ -85,7 +85,7 @@ jobs: install: true - name: Login to GitHub Container Registry - uses: docker/login-action@v3.0.0 + uses: docker/login-action@v3.1.0 if: ${{ inputs.publish-container }} with: registry: ghcr.io From f31416cfa532833793eb514342114da01bd39cd7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 14:13:42 +0000 Subject: [PATCH 25/34] Bump docker/build-push-action from 5.2.0 to 5.3.0 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.2.0 to 5.3.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v5.2.0...v5.3.0) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/_meta-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_meta-build.yaml b/.github/workflows/_meta-build.yaml index feedf2751..1bd31a1a0 100644 --- a/.github/workflows/_meta-build.yaml +++ b/.github/workflows/_meta-build.yaml @@ -102,7 +102,7 @@ jobs: echo "tags=${TAGS}" >> $GITHUB_OUTPUT - name: Build multi-arch Container Image - uses: docker/build-push-action@v5.2.0 + uses: docker/build-push-action@v5.3.0 with: tags: ${{ steps.tags.outputs.tags }} build-args: |- From 222e8de470e0c0f25c3772b4f88589fb0fb10c3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 14:13:46 +0000 Subject: [PATCH 26/34] Bump actions/setup-java from 4.1.0 to 4.2.1 Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4.1.0 to 4.2.1. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4.1.0...v4.2.1) --- updated-dependencies: - dependency-name: actions/setup-java dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/_meta-build.yaml | 2 +- .github/workflows/ci-release.yaml | 2 +- .github/workflows/ci-test.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/_meta-build.yaml b/.github/workflows/_meta-build.yaml index feedf2751..450a16466 100644 --- a/.github/workflows/_meta-build.yaml +++ b/.github/workflows/_meta-build.yaml @@ -27,7 +27,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Set up JDK - uses: actions/setup-java@v4.1.0 + uses: actions/setup-java@v4.2.1 with: distribution: 'temurin' java-version: '21' diff --git a/.github/workflows/ci-release.yaml b/.github/workflows/ci-release.yaml index fd7c1c2cf..c9f9a846f 100644 --- a/.github/workflows/ci-release.yaml +++ b/.github/workflows/ci-release.yaml @@ -54,7 +54,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Set up JDK - uses: actions/setup-java@v4.1.0 + uses: actions/setup-java@v4.2.1 with: distribution: 'temurin' java-version: '21' diff --git a/.github/workflows/ci-test.yaml b/.github/workflows/ci-test.yaml index b16645bcf..0063dc97d 100644 --- a/.github/workflows/ci-test.yaml +++ b/.github/workflows/ci-test.yaml @@ -34,7 +34,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: Set up JDK - uses: actions/setup-java@v4.1.0 + uses: actions/setup-java@v4.2.1 with: distribution: 'temurin' java-version: '21' From 45afe261532a3cfc8adca83343f1b7cede7b3ff6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 14:18:57 +0000 Subject: [PATCH 27/34] Bump debian from `435ba09` to `d10f054` in /src/main/docker Bumps debian from `435ba09` to `d10f054`. --- updated-dependencies: - dependency-name: debian dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- src/main/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/docker/Dockerfile b/src/main/docker/Dockerfile index 2d1c2e130..76a9b6032 100644 --- a/src/main/docker/Dockerfile +++ b/src/main/docker/Dockerfile @@ -1,6 +1,6 @@ FROM eclipse-temurin:21.0.2_13-jre-jammy@sha256:d9f7b8326b9d396d070432982a998015a04ffb8885b145e97777a5ae324a8df1 AS jre-build -FROM debian:stable-slim@sha256:435ba09b2e259426c0552a041eef609b01d4655d9c8467d75be390801068baf3 +FROM debian:stable-slim@sha256:d10f0545d14bad5f4d230301f7e4fd904384f2dd16fda16d708f936c2fa1db3e # Arguments that can be passed at build time # Directory names must end with / to avoid errors when ADDing and COPYing From 8d7d2e3a3def2840c672e5f81aa0e20e831b24b3 Mon Sep 17 00:00:00 2001 From: nscuro Date: Tue, 19 Mar 2024 14:48:45 +0100 Subject: [PATCH 28/34] WIP: Fix incorrect coverage variation reported by Codacy The build on `main` does not include coverage for generated Proto classes, but PR builds do. Signed-off-by: nscuro --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index d2a15c87c..a4ee6b5d4 100644 --- a/pom.xml +++ b/pom.xml @@ -635,6 +635,16 @@ + + org.jacoco + jacoco-maven-plugin + + + org/cyclonedx/proto/**/* + org/dependencytrack/proto/**/* + + + org.codehaus.mojo exec-maven-plugin From 4aa68ef125e5b100bb22e627d8ec4f4d1f0fe922 Mon Sep 17 00:00:00 2001 From: nscuro Date: Mon, 5 Feb 2024 13:50:38 +0100 Subject: [PATCH 29/34] Migrate `MirrorVulnerabilityProcessor` from Kafka Streams to Parallel Consumer Depends on https://github.com/DependencyTrack/hyades-apiserver/pull/552 Relates to https://github.com/DependencyTrack/hyades/issues/346 Relates to https://github.com/DependencyTrack/hyades/issues/901 Relates to https://github.com/DependencyTrack/hyades/issues/907 Signed-off-by: nscuro --- .../kafka/processor/ProcessorInitializer.java | 4 +- .../VulnerabilityMirrorProcessor.java} | 38 ++++---- .../streams/KafkaStreamsTopologyFactory.java | 7 -- src/main/resources/application.properties | 13 +++ .../VulnerabilityMirrorProcessorTest.java} | 90 +++++++++---------- 5 files changed, 72 insertions(+), 80 deletions(-) rename src/main/java/org/dependencytrack/event/kafka/{streams/processor/MirrorVulnerabilityProcessor.java => processor/VulnerabilityMirrorProcessor.java} (91%) rename src/test/java/org/dependencytrack/event/kafka/{streams/processor/MirrorVulnerabilityProcessorTest.java => processor/VulnerabilityMirrorProcessorTest.java} (95%) diff --git a/src/main/java/org/dependencytrack/event/kafka/processor/ProcessorInitializer.java b/src/main/java/org/dependencytrack/event/kafka/processor/ProcessorInitializer.java index 81aa599cf..fc47ab994 100644 --- a/src/main/java/org/dependencytrack/event/kafka/processor/ProcessorInitializer.java +++ b/src/main/java/org/dependencytrack/event/kafka/processor/ProcessorInitializer.java @@ -1,6 +1,7 @@ package org.dependencytrack.event.kafka.processor; import alpine.common.logging.Logger; +import org.dependencytrack.event.kafka.KafkaTopics; import org.dependencytrack.event.kafka.processor.api.ProcessorManager; import javax.servlet.ServletContextEvent; @@ -16,7 +17,8 @@ public class ProcessorInitializer implements ServletContextListener { public void contextInitialized(final ServletContextEvent event) { LOGGER.info("Initializing processors"); - // TODO: Register processor here! + PROCESSOR_MANAGER.registerProcessor(VulnerabilityMirrorProcessor.PROCESSOR_NAME, + KafkaTopics.NEW_VULNERABILITY, new VulnerabilityMirrorProcessor()); PROCESSOR_MANAGER.startAll(); } diff --git a/src/main/java/org/dependencytrack/event/kafka/streams/processor/MirrorVulnerabilityProcessor.java b/src/main/java/org/dependencytrack/event/kafka/processor/VulnerabilityMirrorProcessor.java similarity index 91% rename from src/main/java/org/dependencytrack/event/kafka/streams/processor/MirrorVulnerabilityProcessor.java rename to src/main/java/org/dependencytrack/event/kafka/processor/VulnerabilityMirrorProcessor.java index 0b99c2420..323a62ca7 100644 --- a/src/main/java/org/dependencytrack/event/kafka/streams/processor/MirrorVulnerabilityProcessor.java +++ b/src/main/java/org/dependencytrack/event/kafka/processor/VulnerabilityMirrorProcessor.java @@ -1,16 +1,14 @@ -package org.dependencytrack.event.kafka.streams.processor; +package org.dependencytrack.event.kafka.processor; import alpine.common.logging.Logger; -import alpine.common.metrics.Metrics; import com.github.packageurl.MalformedPackageURLException; import com.github.packageurl.PackageURL; -import io.micrometer.core.instrument.Timer; import org.apache.commons.lang3.StringUtils; -import org.apache.kafka.streams.processor.api.Processor; -import org.apache.kafka.streams.processor.api.Record; +import org.apache.kafka.clients.consumer.ConsumerRecord; import org.cyclonedx.proto.v1_4.Bom; import org.cyclonedx.proto.v1_4.Component; import org.cyclonedx.proto.v1_4.VulnerabilityAffects; +import org.dependencytrack.event.kafka.processor.api.Processor; import org.dependencytrack.model.Vulnerability; import org.dependencytrack.model.VulnerableSoftware; import org.dependencytrack.parser.dependencytrack.ModelConverterCdxToVuln; @@ -27,19 +25,18 @@ import java.util.List; import java.util.Optional; +/** + * A {@link Processor} that ingests vulnerability data from CycloneDX Bill of Vulnerabilities. + */ +public class VulnerabilityMirrorProcessor implements Processor { -public class MirrorVulnerabilityProcessor implements Processor { + static final String PROCESSOR_NAME = "vuln.mirror"; - private static final Logger LOGGER = Logger.getLogger(MirrorVulnerabilityProcessor.class); - private static final Timer TIMER = Timer.builder("vuln_mirror_processing") - .description("Time taken to process mirrored vulnerabilities") - .register(Metrics.getRegistry()); + private static final Logger LOGGER = Logger.getLogger(VulnerabilityMirrorProcessor.class); @Override - public void process(final Record record) { - final Timer.Sample timerSample = Timer.start(); - - try (QueryManager qm = new QueryManager().withL2CacheDisabled()) { + public void process(final ConsumerRecord record) { + try (QueryManager qm = new QueryManager()) { LOGGER.debug("Synchronizing Mirrored Vulnerability : " + record.key()); Bom bom = record.value(); String key = record.key(); @@ -112,11 +109,6 @@ public void process(final Record record) { synchronizedVulnerability.setVulnerableSoftware(reconciledVsList); } qm.persist(synchronizedVulnerability); - } catch (Exception e) { - // TODO: Send record to a dead letter topic. - LOGGER.error("Synchronizing vulnerability %s failed".formatted(record.key()), e); - } finally { - timerSample.stop(TIMER); } } @@ -230,8 +222,8 @@ public VulnerableSoftware mapAffectedRangeToVulnerableSoftware(final QueryManage for (final Constraint constraint : vers.constraints()) { if (constraint.version() == null - || constraint.version().equals("0") - || constraint.version().equals("*")) { + || constraint.version().equals("0") + || constraint.version().equals("*")) { // Semantically, ">=0" is equivalent to versionStartIncluding=null, // and ">0" is equivalent to versionStartExcluding=null. // @@ -253,12 +245,12 @@ public VulnerableSoftware mapAffectedRangeToVulnerableSoftware(final QueryManage } if (versionStartIncluding == null && versionStartExcluding == null - && versionEndIncluding == null && versionEndExcluding == null) { + && versionEndIncluding == null && versionEndExcluding == null) { LOGGER.warn("Unable to assemble a version range from %s for %s".formatted(vers, vulnId)); return null; } if ((versionStartIncluding != null || versionStartExcluding != null) - && (versionEndIncluding == null && versionEndExcluding == null)) { + && (versionEndIncluding == null && versionEndExcluding == null)) { LOGGER.warn("Skipping indefinite version range assembled from %s for %s".formatted(vers, vulnId)); return null; } diff --git a/src/main/java/org/dependencytrack/event/kafka/streams/KafkaStreamsTopologyFactory.java b/src/main/java/org/dependencytrack/event/kafka/streams/KafkaStreamsTopologyFactory.java index 96f446392..f971841df 100644 --- a/src/main/java/org/dependencytrack/event/kafka/streams/KafkaStreamsTopologyFactory.java +++ b/src/main/java/org/dependencytrack/event/kafka/streams/KafkaStreamsTopologyFactory.java @@ -23,7 +23,6 @@ import org.dependencytrack.event.ProjectPolicyEvaluationEvent; import org.dependencytrack.event.kafka.KafkaTopics; import org.dependencytrack.event.kafka.streams.processor.DelayedBomProcessedNotificationProcessor; -import org.dependencytrack.event.kafka.streams.processor.MirrorVulnerabilityProcessor; import org.dependencytrack.event.kafka.streams.processor.RepositoryMetaResultProcessor; import org.dependencytrack.event.kafka.streams.processor.VulnerabilityScanResultProcessor; import org.dependencytrack.model.VulnerabilityScan; @@ -224,12 +223,6 @@ Topology createTopology() { .withName("consume_from_%s_topic".formatted(KafkaTopics.REPO_META_ANALYSIS_RESULT.name()))) .process(RepositoryMetaResultProcessor::new, Named.as("process_repo_meta_analysis_result")); - streamsBuilder - .stream(KafkaTopics.NEW_VULNERABILITY.name(), - Consumed.with(KafkaTopics.NEW_VULNERABILITY.keySerde(), KafkaTopics.NEW_VULNERABILITY.valueSerde()) - .withName("consume_from_%s_topic".formatted(KafkaTopics.NEW_VULNERABILITY.name()))) - .process(MirrorVulnerabilityProcessor::new, Named.as("process_mirror_vulnerability")); - return streamsBuilder.build(streamsProperties); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ef044624c..1fac3c5bc 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -491,6 +491,19 @@ kafka.streams.transient.processing.exception.threshold.interval=PT30M # Refer to https://kafka.apache.org/documentation/#consumerconfigs for available options. # alpine.kafka.processor..consumer.= +# Required +# Configures the Kafka processor responsible for ingesting mirrored vulnerability +# data from the dtrack.vulnerability topic. The processor only occasionally receives +# records, such that high concurrency is usually not justified. +alpine.kafka.processor.vuln.mirror.max.concurrency=-1 +alpine.kafka.processor.vuln.mirror.processing.order=partition +alpine.kafka.processor.vuln.mirror.retry.initial.delay.ms=3000 +alpine.kafka.processor.vuln.mirror.retry.multiplier=2 +alpine.kafka.processor.vuln.mirror.retry.randomization.factor=0.3 +alpine.kafka.processor.vuln.mirror.retry.max.delay.ms=180000 +alpine.kafka.processor.vuln.mirror.consumer.group.id=dtrack-apiserver-processor +alpine.kafka.processor.vuln.mirror.consumer.auto.offset.reset=earliest + # Scheduling tasks after 3 minutes (3*60*1000) of starting application task.scheduler.initial.delay=180000 diff --git a/src/test/java/org/dependencytrack/event/kafka/streams/processor/MirrorVulnerabilityProcessorTest.java b/src/test/java/org/dependencytrack/event/kafka/processor/VulnerabilityMirrorProcessorTest.java similarity index 95% rename from src/test/java/org/dependencytrack/event/kafka/streams/processor/MirrorVulnerabilityProcessorTest.java rename to src/test/java/org/dependencytrack/event/kafka/processor/VulnerabilityMirrorProcessorTest.java index b8a57778c..afbb6149d 100644 --- a/src/test/java/org/dependencytrack/event/kafka/streams/processor/MirrorVulnerabilityProcessorTest.java +++ b/src/test/java/org/dependencytrack/event/kafka/processor/VulnerabilityMirrorProcessorTest.java @@ -1,55 +1,26 @@ -package org.dependencytrack.event.kafka.streams.processor; +package org.dependencytrack.event.kafka.processor; -import org.apache.kafka.common.serialization.Serdes; -import org.apache.kafka.common.serialization.StringSerializer; -import org.apache.kafka.streams.StreamsBuilder; -import org.apache.kafka.streams.TestInputTopic; -import org.apache.kafka.streams.TopologyTestDriver; -import org.apache.kafka.streams.kstream.Consumed; -import org.cyclonedx.proto.v1_4.Bom; -import org.dependencytrack.PersistenceCapableTest; -import org.dependencytrack.event.kafka.serialization.KafkaProtobufSerde; -import org.dependencytrack.event.kafka.serialization.KafkaProtobufSerializer; import org.dependencytrack.model.Severity; import org.dependencytrack.model.Vulnerability; import org.dependencytrack.persistence.CweImporter; -import org.dependencytrack.util.KafkaTestUtil; -import org.junit.After; import org.junit.Before; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.dependencytrack.util.KafkaTestUtil.generateBomFromJson; -public class MirrorVulnerabilityProcessorTest extends PersistenceCapableTest { - - private TopologyTestDriver testDriver; - private TestInputTopic inputTopic; +public class VulnerabilityMirrorProcessorTest extends AbstractProcessorTest { @Before - public void setUp() throws Exception { - final var streamsBuilder = new StreamsBuilder(); - streamsBuilder - .stream("input-topic", Consumed - .with(Serdes.String(), new KafkaProtobufSerde<>(Bom.parser()))) - .process(MirrorVulnerabilityProcessor::new); - - testDriver = new TopologyTestDriver(streamsBuilder.build()); - inputTopic = testDriver.createInputTopic("input-topic", - new StringSerializer(), new KafkaProtobufSerializer<>()); + public void before() throws Exception { + super.before(); new CweImporter().processCweDefinitions(); // Required for CWE mapping } - @After - public void tearDown() { - if (testDriver != null) { - testDriver.close(); - } - } - @Test public void testProcessNvdVuln() throws Exception { - inputTopic.pipeInput("NVD/CVE-2022-40489", KafkaTestUtil.generateBomFromJson(""" + final var bovJson = """ { "components": [ { @@ -91,7 +62,10 @@ public void testProcessNvdVuln() throws Exception { { "url": "https://github.com/thinkcmf/thinkcmf/issues/736" } ] } - """)); + """; + + final var processor = new VulnerabilityMirrorProcessor(); + processor.process(aConsumerRecord("NVD/CVE-2022-40489", generateBomFromJson(bovJson)).build()); final Vulnerability vuln = qm.getVulnerabilityByVulnId("NVD", "CVE-2022-40489"); assertThat(vuln).isNotNull(); @@ -160,7 +134,7 @@ public void testProcessNvdVuln() throws Exception { @Test public void testProcessGitHubVuln() throws Exception { - inputTopic.pipeInput("GITHUB/GHSA-fxwm-579q-49qq", KafkaTestUtil.generateBomFromJson(""" + final var bovJson = """ { "components": [ { @@ -223,7 +197,10 @@ public void testProcessGitHubVuln() throws Exception { { "url": "https://github.com/advisories/GHSA-fxwm-579q-49qq" } ] } - """)); + """; + + final var processor = new VulnerabilityMirrorProcessor(); + processor.process(aConsumerRecord("GITHUB/GHSA-fxwm-579q-49qq", generateBomFromJson(bovJson)).build()); final Vulnerability vuln = qm.getVulnerabilityByVulnId("GITHUB", "GHSA-fxwm-579q-49qq"); assertThat(vuln).isNotNull(); @@ -375,7 +352,7 @@ public void testProcessGitHubVuln() throws Exception { @Test public void testProcessOsvVuln() throws Exception { - inputTopic.pipeInput("OSV/GHSA-2cc5-23r7-vc4v", KafkaTestUtil.generateBomFromJson(""" + final var bovJson = """ { "components": [ { @@ -427,7 +404,10 @@ public void testProcessOsvVuln() throws Exception { { "url": "https://github.com/ratpack/ratpack/blob/29434f7ac6fd4b36a4495429b70f4c8163100332/ratpack-session/src/main/java/ratpack/session/clientside/ClientSideSessionConfig.java#L29" } ] } - """)); + """; + + final var processor = new VulnerabilityMirrorProcessor(); + processor.process(aConsumerRecord("OSV/GHSA-2cc5-23r7-vc4v", generateBomFromJson(bovJson)).build()); final Vulnerability vuln = qm.getVulnerabilityByVulnId("GITHUB", "GHSA-2cc5-23r7-vc4v"); assertThat(vuln).isNotNull(); @@ -555,7 +535,7 @@ public void testProcessOsvVuln() throws Exception { @Test public void testProcessVulnWithoutAffects() throws Exception { - inputTopic.pipeInput("NVD/CVE-2022-40489", KafkaTestUtil.generateBomFromJson(""" + final var bovJson = """ { "components": [ { @@ -573,7 +553,10 @@ public void testProcessVulnWithoutAffects() throws Exception { } ] } - """)); + """; + + final var processor = new VulnerabilityMirrorProcessor(); + processor.process(aConsumerRecord("NVD/CVE-2022-40489", generateBomFromJson(bovJson)).build()); final Vulnerability vuln = qm.getVulnerabilityByVulnId("NVD", "CVE-2022-40489"); assertThat(vuln).isNotNull(); @@ -613,7 +596,7 @@ public void testProcessVulnWithoutAffects() throws Exception { @Test public void testProcessVulnWithUnmatchedAffectsBomRef() throws Exception { - inputTopic.pipeInput("NVD/CVE-2022-40489", KafkaTestUtil.generateBomFromJson(""" + final var bovJson = """ { "components": [ { @@ -639,7 +622,10 @@ public void testProcessVulnWithUnmatchedAffectsBomRef() throws Exception { } ] } - """)); + """; + + final var processor = new VulnerabilityMirrorProcessor(); + processor.process(aConsumerRecord("NVD/CVE-2022-40489", generateBomFromJson(bovJson)).build()); final Vulnerability vuln = qm.getVulnerabilityByVulnId("NVD", "CVE-2022-40489"); assertThat(vuln).isNotNull(); @@ -679,7 +665,7 @@ public void testProcessVulnWithUnmatchedAffectsBomRef() throws Exception { @Test public void testProcessVulnWithVersConstraints() throws Exception { - inputTopic.pipeInput("NVD/CVE-2022-40489", KafkaTestUtil.generateBomFromJson(""" + final var bovJson = """ { "components": [ { @@ -731,7 +717,10 @@ public void testProcessVulnWithVersConstraints() throws Exception { } ] } - """)); + """; + + final var processor = new VulnerabilityMirrorProcessor(); + processor.process(aConsumerRecord("NVD/CVE-2022-40489", generateBomFromJson(bovJson)).build()); final Vulnerability vuln = qm.getVulnerabilityByVulnId("NVD", "CVE-2022-40489"); assertThat(vuln).isNotNull(); @@ -935,7 +924,7 @@ public void testProcessVulnWithVersConstraints() throws Exception { @Test public void testProcessVulnWithInvalidCpeOrPurl() throws Exception { - inputTopic.pipeInput("NVD/CVE-2022-40489", KafkaTestUtil.generateBomFromJson(""" + final var bovJson = """ { "components": [ { @@ -997,7 +986,10 @@ public void testProcessVulnWithInvalidCpeOrPurl() throws Exception { } ] } - """)); + """; + + final var processor = new VulnerabilityMirrorProcessor(); + processor.process(aConsumerRecord("NVD/CVE-2022-40489", generateBomFromJson(bovJson)).build()); final Vulnerability vuln = qm.getVulnerabilityByVulnId("NVD", "CVE-2022-40489"); assertThat(vuln).isNotNull(); @@ -1035,4 +1027,4 @@ public void testProcessVulnWithInvalidCpeOrPurl() throws Exception { assertThat(vuln.getVulnerableSoftware()).isEmpty(); } -} +} \ No newline at end of file From 1475a0dcc354e4c602f8269fe3f907e8f2fec1cd Mon Sep 17 00:00:00 2001 From: nscuro Date: Wed, 20 Mar 2024 12:52:33 +0100 Subject: [PATCH 30/34] Bump Alpine to 2.2.5 It's a precondition for Java 21 support :) Signed-off-by: nscuro --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a4ee6b5d4..a0e74937d 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ us.springett alpine-parent - 2.2.4 + 2.2.5 4.0.0 From b954579b0d08204f6334e1c47f3d0c21a6c812c8 Mon Sep 17 00:00:00 2001 From: nscuro Date: Wed, 20 Mar 2024 12:52:52 +0100 Subject: [PATCH 31/34] Raise baseline Java version to 21 Signed-off-by: nscuro --- pom.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pom.xml b/pom.xml index a0e74937d..5b03a37e3 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,9 @@ + 21 + 21 + ${project.parent.version} 4.2.0 From 05e256083e15f948f47692a87a3f90a40f38ae92 Mon Sep 17 00:00:00 2001 From: nscuro Date: Wed, 20 Mar 2024 13:59:45 +0100 Subject: [PATCH 32/34] Add new `APIKEY` columns As introduced in https://github.com/stevespringett/Alpine/pull/537 Signed-off-by: nscuro --- .../resources/migration/changelog-main.xml | 1 + .../resources/migration/changelog-v5.5.0.xml | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 src/main/resources/migration/changelog-v5.5.0.xml diff --git a/src/main/resources/migration/changelog-main.xml b/src/main/resources/migration/changelog-main.xml index 5ab794a8e..5e7614397 100644 --- a/src/main/resources/migration/changelog-main.xml +++ b/src/main/resources/migration/changelog-main.xml @@ -11,5 +11,6 @@ + \ No newline at end of file diff --git a/src/main/resources/migration/changelog-v5.5.0.xml b/src/main/resources/migration/changelog-v5.5.0.xml new file mode 100644 index 000000000..3209ad4eb --- /dev/null +++ b/src/main/resources/migration/changelog-v5.5.0.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file From d09159a676e55f32cfe5ee8f0cc06fb05abc1c63 Mon Sep 17 00:00:00 2001 From: nscuro Date: Wed, 20 Mar 2024 14:00:33 +0100 Subject: [PATCH 33/34] Fix tests due to DataNucleus behavior change This was also done for DT v4.11.0: https://github.com/DependencyTrack/dependency-track/pull/3515 Signed-off-by: nscuro --- .../v1/NotificationRuleResourceTest.java | 3 ++- .../resources/v1/ProjectResourceTest.java | 22 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/dependencytrack/resources/v1/NotificationRuleResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/NotificationRuleResourceTest.java index 700d66a13..14b1b4279 100644 --- a/src/test/java/org/dependencytrack/resources/v1/NotificationRuleResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/NotificationRuleResourceTest.java @@ -432,7 +432,8 @@ public void addTeamToRuleWithCustomEmailPublisherTest() { "teams": [ { "uuid": "${json-unit.matches:teamUuid}", - "name": "Team Example" + "name": "Team Example", + "permissions": [] } ], "notifyOn": [], diff --git a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java index 6e1ecf0e3..2ac4e03c6 100644 --- a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java @@ -49,6 +49,7 @@ import org.glassfish.jersey.servlet.ServletContainer; import org.glassfish.jersey.test.DeploymentContext; import org.glassfish.jersey.test.ServletDeploymentContext; +import org.hamcrest.CoreMatchers; import org.junit.After; import org.junit.Assert; import org.junit.Test; @@ -639,9 +640,24 @@ public void patchProjectParentTest() { assertThat(response.getStatus()).isEqualTo(Response.Status.OK.getStatusCode()); - final JsonObject responseJson = parseJsonObject(response); - assertThat(responseJson.getString("uuid")).isEqualTo(project.getUuid().toString()); - assertThat(responseJson.getJsonObject("parent")).isNull(); // Parents are currently not returned + assertThatJson(getPlainTextBody(response)) + .withMatcher("projectUuid", CoreMatchers.equalTo(project.getUuid().toString())) + .withMatcher("parentProjectUuid", CoreMatchers.equalTo(newParent.getUuid().toString())) + .isEqualTo(""" + { + "name": "DEF", + "version": "2.0", + "uuid": "${json-unit.matches:projectUuid}", + "parent": { + "name": "GHI", + "version": "3.0", + "uuid": "${json-unit.matches:parentProjectUuid}" + }, + "properties": [], + "tags": [], + "active": true + } + """); // Ensure the parent was updated. qm.getPersistenceManager().refresh(project); From 61ed36ff9bb26053f5527e69e106c2d59ceac87f Mon Sep 17 00:00:00 2001 From: nscuro Date: Wed, 20 Mar 2024 19:07:05 +0100 Subject: [PATCH 34/34] Transfer copyright from Steve Springett to OWASP Foundation Signed-off-by: nscuro --- .checkstyle-header | 2 +- pom.xml | 2 +- .../java/alpine/server/persistence/PersistenceInitializer.java | 2 +- src/main/java/org/dependencytrack/auth/Permissions.java | 2 +- src/main/java/org/dependencytrack/auth/package-info.java | 2 +- src/main/java/org/dependencytrack/common/HttpClientPool.java | 2 +- src/main/java/org/dependencytrack/common/ManagedHttpClient.java | 2 +- .../org/dependencytrack/common/ManagedHttpClientFactory.java | 2 +- .../event/AbstractVulnerabilityManagementUploadEvent.java | 2 +- src/main/java/org/dependencytrack/event/BomUploadEvent.java | 2 +- src/main/java/org/dependencytrack/event/CallbackEvent.java | 2 +- src/main/java/org/dependencytrack/event/CloneProjectEvent.java | 2 +- .../org/dependencytrack/event/ComponentMetricsUpdateEvent.java | 2 +- .../dependencytrack/event/DefectDojoUploadEventAbstract.java | 2 +- src/main/java/org/dependencytrack/event/EpssMirrorEvent.java | 2 +- .../org/dependencytrack/event/EventSubsystemInitializer.java | 2 +- .../dependencytrack/event/FortifySscUploadEventAbstract.java | 2 +- .../org/dependencytrack/event/GitHubAdvisoryMirrorEvent.java | 2 +- .../event/InternalComponentIdentificationEvent.java | 2 +- .../dependencytrack/event/KennaSecurityUploadEventAbstract.java | 2 +- .../event/NewVulnerableDependencyAnalysisEvent.java | 2 +- src/main/java/org/dependencytrack/event/NistMirrorEvent.java | 2 +- .../org/dependencytrack/event/PortfolioMetricsUpdateEvent.java | 2 +- .../event/PortfolioVulnerabilityAnalysisEvent.java | 2 +- .../org/dependencytrack/event/ProjectMetricsUpdateEvent.java | 2 +- .../event/ProjectVulnerabilityAnalysisEvent.java | 2 +- src/main/java/org/dependencytrack/event/VexUploadEvent.java | 2 +- .../dependencytrack/event/VulnerabilityMetricsUpdateEvent.java | 2 +- src/main/java/org/dependencytrack/event/package-info.java | 2 +- .../java/org/dependencytrack/health/HealthCheckInitializer.java | 2 +- .../dependencytrack/integrations/AbstractIntegrationPoint.java | 2 +- .../dependencytrack/integrations/FindingPackagingFormat.java | 2 +- .../java/org/dependencytrack/integrations/FindingUploader.java | 2 +- .../java/org/dependencytrack/integrations/IntegrationPoint.java | 2 +- .../dependencytrack/integrations/PortfolioFindingUploader.java | 2 +- .../dependencytrack/integrations/ProjectFindingUploader.java | 2 +- .../integrations/defectdojo/DefectDojoClient.java | 2 +- .../integrations/defectdojo/DefectDojoUploader.java | 2 +- .../integrations/fortifyssc/FortifySscClient.java | 2 +- .../integrations/fortifyssc/FortifySscUploader.java | 2 +- .../integrations/kenna/KennaDataTransformer.java | 2 +- .../integrations/kenna/KennaSecurityUploader.java | 2 +- src/main/java/org/dependencytrack/metrics/Metrics.java | 2 +- src/main/java/org/dependencytrack/metrics/package-info.java | 2 +- .../org/dependencytrack/model/AffectedVersionAttribution.java | 2 +- src/main/java/org/dependencytrack/model/Analysis.java | 2 +- src/main/java/org/dependencytrack/model/AnalysisComment.java | 2 +- .../java/org/dependencytrack/model/AnalysisJustification.java | 2 +- src/main/java/org/dependencytrack/model/AnalysisResponse.java | 2 +- src/main/java/org/dependencytrack/model/AnalysisState.java | 2 +- src/main/java/org/dependencytrack/model/AnalyzerIdentity.java | 2 +- src/main/java/org/dependencytrack/model/Bom.java | 2 +- src/main/java/org/dependencytrack/model/Classifier.java | 2 +- src/main/java/org/dependencytrack/model/Component.java | 2 +- src/main/java/org/dependencytrack/model/ComponentIdentity.java | 2 +- .../java/org/dependencytrack/model/ConfigPropertyConstants.java | 2 +- src/main/java/org/dependencytrack/model/Coordinates.java | 2 +- src/main/java/org/dependencytrack/model/Cwe.java | 2 +- src/main/java/org/dependencytrack/model/DataClassification.java | 2 +- src/main/java/org/dependencytrack/model/DependencyMetrics.java | 2 +- src/main/java/org/dependencytrack/model/ExternalReference.java | 2 +- src/main/java/org/dependencytrack/model/Finding.java | 2 +- src/main/java/org/dependencytrack/model/FindingAttribution.java | 2 +- src/main/java/org/dependencytrack/model/ICpe.java | 2 +- src/main/java/org/dependencytrack/model/IdentifiableObject.java | 2 +- .../java/org/dependencytrack/model/IntegrityMetaComponent.java | 2 +- src/main/java/org/dependencytrack/model/License.java | 2 +- src/main/java/org/dependencytrack/model/LicenseGroup.java | 2 +- src/main/java/org/dependencytrack/model/MetaModel.java | 2 +- .../java/org/dependencytrack/model/NotificationPublisher.java | 2 +- src/main/java/org/dependencytrack/model/NotificationRule.java | 2 +- .../java/org/dependencytrack/model/OrganizationalContact.java | 2 +- .../java/org/dependencytrack/model/OrganizationalEntity.java | 2 +- src/main/java/org/dependencytrack/model/Policy.java | 2 +- src/main/java/org/dependencytrack/model/PolicyCondition.java | 2 +- src/main/java/org/dependencytrack/model/PolicyViolation.java | 2 +- src/main/java/org/dependencytrack/model/PortfolioMetrics.java | 2 +- src/main/java/org/dependencytrack/model/Project.java | 2 +- src/main/java/org/dependencytrack/model/ProjectMetadata.java | 2 +- src/main/java/org/dependencytrack/model/ProjectMetrics.java | 2 +- src/main/java/org/dependencytrack/model/ProjectProperty.java | 2 +- src/main/java/org/dependencytrack/model/ProjectVersion.java | 2 +- src/main/java/org/dependencytrack/model/Repository.java | 2 +- .../java/org/dependencytrack/model/RepositoryMetaComponent.java | 2 +- src/main/java/org/dependencytrack/model/RepositoryType.java | 2 +- src/main/java/org/dependencytrack/model/ServiceComponent.java | 2 +- src/main/java/org/dependencytrack/model/Severity.java | 2 +- src/main/java/org/dependencytrack/model/Tag.java | 2 +- src/main/java/org/dependencytrack/model/VersionDistance.java | 2 +- src/main/java/org/dependencytrack/model/Vex.java | 2 +- src/main/java/org/dependencytrack/model/ViolationAnalysis.java | 2 +- .../org/dependencytrack/model/ViolationAnalysisComment.java | 2 +- .../java/org/dependencytrack/model/ViolationAnalysisState.java | 2 +- src/main/java/org/dependencytrack/model/Vulnerability.java | 2 +- src/main/java/org/dependencytrack/model/VulnerabilityAlias.java | 2 +- .../java/org/dependencytrack/model/VulnerabilityMetrics.java | 2 +- src/main/java/org/dependencytrack/model/VulnerableSoftware.java | 2 +- src/main/java/org/dependencytrack/model/package-info.java | 2 +- .../model/validation/SpdxExpressionValidator.java | 2 +- .../dependencytrack/model/validation/ValidSpdxExpression.java | 2 +- .../org/dependencytrack/notification/NotificationConstants.java | 2 +- .../org/dependencytrack/notification/NotificationGroup.java | 2 +- .../org/dependencytrack/notification/NotificationScope.java | 2 +- .../notification/publisher/DefaultNotificationPublishers.java | 2 +- .../dependencytrack/notification/vo/AnalysisDecisionChange.java | 2 +- .../dependencytrack/notification/vo/BomConsumedOrProcessed.java | 2 +- .../dependencytrack/notification/vo/BomProcessingFailed.java | 2 +- .../notification/vo/NewVulnerabilityIdentified.java | 2 +- .../notification/vo/NewVulnerableDependency.java | 2 +- .../notification/vo/PolicyViolationIdentified.java | 2 +- .../dependencytrack/notification/vo/VexConsumedOrProcessed.java | 2 +- .../notification/vo/ViolationAnalysisDecisionChange.java | 2 +- .../org/dependencytrack/parser/common/resolver/CweResolver.java | 2 +- .../org/dependencytrack/parser/cyclonedx/CycloneDXExporter.java | 2 +- .../dependencytrack/parser/cyclonedx/CycloneDXVexImporter.java | 2 +- .../java/org/dependencytrack/parser/cyclonedx/package-info.java | 2 +- .../dependencytrack/parser/cyclonedx/util/ModelConverter.java | 2 +- src/main/java/org/dependencytrack/parser/epss/EpssParser.java | 2 +- .../parser/github/graphql/GitHubSecurityAdvisoryParser.java | 2 +- .../parser/github/graphql/model/GitHubSecurityAdvisory.java | 2 +- .../parser/github/graphql/model/GitHubVulnerability.java | 2 +- .../parser/github/graphql/model/PageableList.java | 2 +- .../java/org/dependencytrack/parser/nvd/ModelConverter.java | 2 +- src/main/java/org/dependencytrack/parser/package-info.java | 2 +- .../parser/spdx/expression/SpdxExpressionParser.java | 2 +- .../parser/spdx/expression/model/SpdxExpression.java | 2 +- .../parser/spdx/expression/model/SpdxExpressionOperation.java | 2 +- .../parser/spdx/expression/model/SpdxOperator.java | 2 +- .../dependencytrack/parser/spdx/expression/package-info.java | 2 +- .../parser/spdx/json/SpdxLicenseDetailParser.java | 2 +- .../java/org/dependencytrack/parser/spdx/json/package-info.java | 2 +- src/main/java/org/dependencytrack/parser/spdx/package-info.java | 2 +- .../java/org/dependencytrack/persistence/BomQueryManager.java | 2 +- .../dependencytrack/persistence/CollectionIntegerConverter.java | 2 +- .../org/dependencytrack/persistence/ComponentQueryManager.java | 2 +- src/main/java/org/dependencytrack/persistence/CweImporter.java | 2 +- .../org/dependencytrack/persistence/DefaultObjectGenerator.java | 2 +- .../org/dependencytrack/persistence/FindingsQueryManager.java | 2 +- .../java/org/dependencytrack/persistence/IQueryManager.java | 2 +- .../org/dependencytrack/persistence/LicenseQueryManager.java | 2 +- .../org/dependencytrack/persistence/MetricsQueryManager.java | 2 +- .../dependencytrack/persistence/NotificationQueryManager.java | 2 +- .../dependencytrack/persistence/PackageURLStringConverter.java | 2 +- .../org/dependencytrack/persistence/PolicyQueryManager.java | 2 +- .../org/dependencytrack/persistence/ProjectQueryManager.java | 2 +- src/main/java/org/dependencytrack/persistence/QueryManager.java | 2 +- .../org/dependencytrack/persistence/RepositoryQueryManager.java | 2 +- .../persistence/ServiceComponentQueryManager.java | 2 +- .../java/org/dependencytrack/persistence/VexQueryManager.java | 2 +- .../dependencytrack/persistence/VulnerabilityQueryManager.java | 2 +- .../persistence/VulnerableSoftwareQueryManager.java | 2 +- .../persistence/converter/AbstractJsonConverter.java | 2 +- .../converter/OrganizationalContactsJsonConverter.java | 2 +- .../converter/OrganizationalEntityJsonConverter.java | 2 +- .../persistence/defaults/DefaultLicenseGroupImporter.java | 2 +- .../persistence/defaults/IDefaultObjectImporter.java | 2 +- src/main/java/org/dependencytrack/persistence/package-info.java | 2 +- .../policy/cel/compat/LicenseCelPolicyScriptSourceBuilder.java | 2 +- .../cel/compat/VulnerabilityIdCelPolicyScriptSourceBuilder.java | 2 +- src/main/java/org/dependencytrack/resources/package-info.java | 2 +- .../resources/v1/AbstractConfigPropertyResource.java | 2 +- .../org/dependencytrack/resources/v1/AccessControlResource.java | 2 +- .../java/org/dependencytrack/resources/v1/AnalysisResource.java | 2 +- .../java/org/dependencytrack/resources/v1/BadgeResource.java | 2 +- src/main/java/org/dependencytrack/resources/v1/BomResource.java | 2 +- .../org/dependencytrack/resources/v1/CalculatorResource.java | 2 +- .../org/dependencytrack/resources/v1/ComponentResource.java | 2 +- .../dependencytrack/resources/v1/ConfigPropertyResource.java | 2 +- src/main/java/org/dependencytrack/resources/v1/CweResource.java | 2 +- .../dependencytrack/resources/v1/DependencyGraphResource.java | 2 +- .../java/org/dependencytrack/resources/v1/FindingResource.java | 2 +- .../java/org/dependencytrack/resources/v1/LdapResource.java | 2 +- .../org/dependencytrack/resources/v1/LicenseGroupResource.java | 2 +- .../java/org/dependencytrack/resources/v1/LicenseResource.java | 2 +- .../java/org/dependencytrack/resources/v1/MetricsResource.java | 2 +- .../resources/v1/NotificationPublisherResource.java | 2 +- .../dependencytrack/resources/v1/NotificationRuleResource.java | 2 +- .../org/dependencytrack/resources/v1/OsvEcosytemResource.java | 2 +- .../org/dependencytrack/resources/v1/PermissionResource.java | 2 +- .../dependencytrack/resources/v1/PolicyConditionResource.java | 2 +- .../java/org/dependencytrack/resources/v1/PolicyResource.java | 2 +- .../dependencytrack/resources/v1/PolicyViolationResource.java | 2 +- .../dependencytrack/resources/v1/ProjectPropertyResource.java | 2 +- .../java/org/dependencytrack/resources/v1/ProjectResource.java | 2 +- .../org/dependencytrack/resources/v1/RepositoryResource.java | 2 +- .../java/org/dependencytrack/resources/v1/ServiceResource.java | 2 +- .../java/org/dependencytrack/resources/v1/TeamResource.java | 2 +- .../java/org/dependencytrack/resources/v1/UserResource.java | 2 +- src/main/java/org/dependencytrack/resources/v1/VexResource.java | 2 +- .../dependencytrack/resources/v1/ViolationAnalysisResource.java | 2 +- .../resources/v1/VulnerabilityPolicyResource.java | 2 +- .../org/dependencytrack/resources/v1/VulnerabilityResource.java | 2 +- src/main/java/org/dependencytrack/resources/v1/misc/Badger.java | 2 +- .../java/org/dependencytrack/resources/v1/package-info.java | 2 +- .../resources/v1/serializers/CustomPackageURLSerializer.java | 2 +- .../resources/v1/serializers/CweDeserializer.java | 2 +- .../dependencytrack/resources/v1/serializers/CweSerializer.java | 2 +- .../resources/v1/serializers/Iso8601DateSerializer.java | 2 +- .../org/dependencytrack/resources/v1/vo/AclMappingRequest.java | 2 +- .../org/dependencytrack/resources/v1/vo/AffectedComponent.java | 2 +- .../org/dependencytrack/resources/v1/vo/AnalysisRequest.java | 2 +- .../org/dependencytrack/resources/v1/vo/BomSubmitRequest.java | 2 +- .../dependencytrack/resources/v1/vo/CloneProjectRequest.java | 2 +- .../resources/v1/vo/DependencyGraphResponse.java | 2 +- .../org/dependencytrack/resources/v1/vo/DependencyRequest.java | 2 +- .../dependencytrack/resources/v1/vo/MappedLdapGroupRequest.java | 2 +- .../org/dependencytrack/resources/v1/vo/VexSubmitRequest.java | 2 +- .../resources/v1/vo/ViolationAnalysisRequest.java | 2 +- .../java/org/dependencytrack/resources/v1/vo/package-info.java | 2 +- .../java/org/dependencytrack/tasks/BomUploadProcessingTask.java | 2 +- src/main/java/org/dependencytrack/tasks/CallbackTask.java | 2 +- src/main/java/org/dependencytrack/tasks/CloneProjectTask.java | 2 +- .../java/org/dependencytrack/tasks/DefectDojoUploadTask.java | 2 +- src/main/java/org/dependencytrack/tasks/EpssMirrorTask.java | 2 +- .../java/org/dependencytrack/tasks/FortifySscUploadTask.java | 2 +- .../tasks/InternalComponentIdentificationTask.java | 2 +- .../java/org/dependencytrack/tasks/KennaSecurityUploadTask.java | 2 +- .../org/dependencytrack/tasks/RepositoryMetaAnalyzerTask.java | 2 +- src/main/java/org/dependencytrack/tasks/TaskScheduler.java | 2 +- .../java/org/dependencytrack/tasks/VexUploadProcessingTask.java | 2 +- .../org/dependencytrack/tasks/VulnerabilityAnalysisTask.java | 2 +- .../tasks/VulnerabilityManagementUploadTask.java | 2 +- .../tasks/metrics/ComponentMetricsUpdateTask.java | 2 +- .../tasks/metrics/PortfolioMetricsUpdateTask.java | 2 +- .../dependencytrack/tasks/metrics/ProjectMetricsUpdateTask.java | 2 +- .../tasks/metrics/VulnerabilityMetricsUpdateTask.java | 2 +- .../java/org/dependencytrack/tasks/metrics/YearMonthMetric.java | 2 +- src/main/java/org/dependencytrack/tasks/package-info.java | 2 +- .../java/org/dependencytrack/upgrade/UpgradeInitializer.java | 2 +- src/main/java/org/dependencytrack/upgrade/UpgradeItems.java | 2 +- src/main/java/org/dependencytrack/util/AnalysisCommentUtil.java | 2 +- .../org/dependencytrack/util/ComponentIdentificationUtil.java | 2 +- src/main/java/org/dependencytrack/util/ComponentVersion.java | 2 +- src/main/java/org/dependencytrack/util/CompressUtil.java | 2 +- src/main/java/org/dependencytrack/util/DateUtil.java | 2 +- src/main/java/org/dependencytrack/util/HashUtil.java | 2 +- src/main/java/org/dependencytrack/util/HttpUtil.java | 2 +- .../util/InternalComponentIdentificationUtil.java | 2 +- src/main/java/org/dependencytrack/util/JsonUtil.java | 2 +- src/main/java/org/dependencytrack/util/NotificationUtil.java | 2 +- src/main/java/org/dependencytrack/util/PurlUtil.java | 2 +- src/main/java/org/dependencytrack/util/VulnerabilityUtil.java | 2 +- src/main/resources/META-INF/persistence.xml | 2 +- src/main/webapp/WEB-INF/web.xml | 2 +- src/test/java/org/dependencytrack/PersistenceCapableTest.java | 2 +- src/test/java/org/dependencytrack/ResourceTest.java | 2 +- src/test/java/org/dependencytrack/auth/PermissionsTest.java | 2 +- .../java/org/dependencytrack/common/HttpClientPoolTest.java | 2 +- .../dependencytrack/common/ManagedHttpClientFactoryTest.java | 2 +- .../java/org/dependencytrack/common/ManagedHttpClientTest.java | 2 +- src/test/java/org/dependencytrack/event/BomUploadEventTest.java | 2 +- .../java/org/dependencytrack/event/CloneProjectEventTest.java | 2 +- .../org/dependencytrack/event/FortifySscUploadEventTest.java | 2 +- .../org/dependencytrack/event/KennaSecurityUploadEventTest.java | 2 +- .../java/org/dependencytrack/event/NistMirrorEventTest.java | 2 +- .../event/ProjectVulnerabilityAnalysisEventTest.java | 2 +- .../integrations/AbstractIntegrationPointTest.java | 2 +- .../integrations/FindingPackagingFormatTest.java | 2 +- .../org/dependencytrack/integrations/FindingUploaderTest.java | 2 +- .../org/dependencytrack/integrations/IntegrationPointTest.java | 2 +- .../integrations/PortfolioFindingUploaderTest.java | 2 +- .../integrations/ProjectFindingUploaderTest.java | 2 +- .../integrations/defectdojo/DefectDojoUploaderTest.java | 2 +- .../integrations/fortifyssc/FortifySscClientTest.java | 2 +- .../integrations/fortifyssc/FortifySscUploaderTest.java | 2 +- .../integrations/kenna/KennaSecurityUploaderTest.java | 2 +- src/test/java/org/dependencytrack/metrics/MetricsTest.java | 2 +- .../java/org/dependencytrack/model/AnalysisCommentTest.java | 2 +- src/test/java/org/dependencytrack/model/AnalysisStateTest.java | 2 +- src/test/java/org/dependencytrack/model/AnalysisTest.java | 2 +- src/test/java/org/dependencytrack/model/BomTest.java | 2 +- src/test/java/org/dependencytrack/model/ClassifierTest.java | 2 +- src/test/java/org/dependencytrack/model/ComponentTest.java | 2 +- src/test/java/org/dependencytrack/model/CweTest.java | 2 +- .../java/org/dependencytrack/model/DependencyMetricsTest.java | 2 +- src/test/java/org/dependencytrack/model/FindingTest.java | 2 +- src/test/java/org/dependencytrack/model/LicenseGroupTest.java | 2 +- src/test/java/org/dependencytrack/model/LicenseTest.java | 2 +- .../org/dependencytrack/model/NotificationPublisherTest.java | 2 +- .../java/org/dependencytrack/model/NotificationRuleTest.java | 2 +- .../java/org/dependencytrack/model/PolicyConditionTest.java | 2 +- src/test/java/org/dependencytrack/model/PolicyTest.java | 2 +- .../java/org/dependencytrack/model/PortfolioMetricsTest.java | 2 +- src/test/java/org/dependencytrack/model/ProjectMetricsTest.java | 2 +- .../java/org/dependencytrack/model/ProjectPropertyTest.java | 2 +- src/test/java/org/dependencytrack/model/ProjectTest.java | 2 +- .../org/dependencytrack/model/RepositoryMetaComponentTest.java | 2 +- src/test/java/org/dependencytrack/model/RepositoryTest.java | 2 +- src/test/java/org/dependencytrack/model/RepositoryTypeTest.java | 2 +- src/test/java/org/dependencytrack/model/SeverityTest.java | 2 +- src/test/java/org/dependencytrack/model/TagTest.java | 2 +- .../org/dependencytrack/model/VulnerabilityMetricsTest.java | 2 +- src/test/java/org/dependencytrack/model/VulnerabilityTest.java | 2 +- .../java/org/dependencytrack/model/VulnerableSoftwareTest.java | 2 +- .../model/validation/SpdxExpressionValidatorTest.java | 2 +- .../dependencytrack/notification/NotificationConstantsTest.java | 2 +- .../org/dependencytrack/notification/NotificationGroupTest.java | 2 +- .../org/dependencytrack/notification/NotificationScopeTest.java | 2 +- .../publisher/DefaultNotificationPublishersTest.java | 2 +- .../notification/vo/AnalysisDecisionChangeTest.java | 2 +- .../notification/vo/NewVulnerabilityIdentifiedTest.java | 2 +- .../notification/vo/NewVulnerableDependencyTest.java | 2 +- .../dependencytrack/parser/common/resolver/CweResolverTest.java | 2 +- .../java/org/dependencytrack/persistence/CweImporterTest.java | 2 +- .../dependencytrack/persistence/DefaultObjectGeneratorTest.java | 2 +- .../persistence/PackageURLStringConverterTest.java | 2 +- .../org/dependencytrack/persistence/PolicyQueryManagerTest.java | 2 +- .../persistence/VulnerabilityQueryManagerTest.java | 2 +- .../converter/OrganizationalContactsJsonConverterTest.java | 2 +- .../converter/OrganizationalEntityJsonConverterTest.java | 2 +- .../cel/compat/VersionDistanceCelPolicyEvaluatorTest.java | 2 +- .../org/dependencytrack/resources/v1/AnalysisResourceTest.java | 2 +- .../org/dependencytrack/resources/v1/BadgeResourceTest.java | 2 +- .../java/org/dependencytrack/resources/v1/BomResourceTest.java | 2 +- .../dependencytrack/resources/v1/CalculatorResourceTest.java | 2 +- .../org/dependencytrack/resources/v1/ComponentResourceTest.java | 2 +- .../resources/v1/ConfigPropertyResourceTest.java | 2 +- .../java/org/dependencytrack/resources/v1/CweResourceTest.java | 2 +- .../resources/v1/DependencyGraphResourceTest.java | 2 +- .../org/dependencytrack/resources/v1/FindingResourceTest.java | 2 +- .../java/org/dependencytrack/resources/v1/LdapResourceTest.java | 2 +- .../dependencytrack/resources/v1/LicenseGroupResourceTest.java | 2 +- .../org/dependencytrack/resources/v1/LicenseResourceTest.java | 2 +- .../resources/v1/NotificationPublisherResourceTest.java | 2 +- .../resources/v1/NotificationRuleResourceTest.java | 2 +- .../dependencytrack/resources/v1/OsvEcosystemResourceTest.java | 2 +- .../dependencytrack/resources/v1/PermissionResourceTest.java | 2 +- .../org/dependencytrack/resources/v1/PolicyResourceTest.java | 2 +- .../resources/v1/PolicyViolationResourceTest.java | 2 +- .../resources/v1/ProjectPropertyResourceTest.java | 2 +- .../org/dependencytrack/resources/v1/ProjectResourceTest.java | 2 +- .../dependencytrack/resources/v1/RepositoryResourceTest.java | 2 +- .../java/org/dependencytrack/resources/v1/TeamResourceTest.java | 2 +- .../resources/v1/UserResourceAuthenticatedTest.java | 2 +- .../resources/v1/UserResourceUnauthenticatedTest.java | 2 +- .../java/org/dependencytrack/resources/v1/VexResourceTest.java | 2 +- .../resources/v1/ViolationAnalysisResourceTest.java | 2 +- .../resources/v1/VulnerabilityPolicyResourceTest.java | 2 +- .../dependencytrack/resources/v1/VulnerabilityResourceTest.java | 2 +- .../java/org/dependencytrack/resources/v1/misc/BadgerTest.java | 2 +- .../org/dependencytrack/tasks/BomUploadProcessingTaskTest.java | 2 +- .../tasks/InternalComponentIdentificationTaskTest.java | 2 +- .../tasks/metrics/AbstractMetricsUpdateTaskTest.java | 2 +- .../tasks/metrics/ComponentMetricsUpdateTaskTest.java | 2 +- .../tasks/metrics/PortfolioMetricsUpdateTaskTest.java | 2 +- .../tasks/metrics/ProjectMetricsUpdateTaskTest.java | 2 +- .../tasks/metrics/VulnerabilityMetricsUpdateTaskTest.java | 2 +- src/test/java/org/dependencytrack/util/DateUtilTest.java | 2 +- src/test/java/org/dependencytrack/util/HashUtilTest.java | 2 +- src/test/java/org/dependencytrack/util/HttpUtilTest.java | 2 +- 350 files changed, 350 insertions(+), 350 deletions(-) diff --git a/.checkstyle-header b/.checkstyle-header index c773577a1..a89578d34 100644 --- a/.checkstyle-header +++ b/.checkstyle-header @@ -14,5 +14,5 @@ * limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * Copyright (c) Steve Springett. All Rights Reserved. + * Copyright (c) OWASP Foundation. All Rights Reserved. */ \ No newline at end of file diff --git a/pom.xml b/pom.xml index a4ee6b5d4..428f71179 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ ~ limitations under the License. ~ ~ SPDX-License-Identifier: Apache-2.0 - ~ Copyright (c) Steve Springett. All Rights Reserved. + ~ Copyright (c) OWASP Foundation. All Rights Reserved. -->