From 9182e9d3ae5e8abf10545fd471ab95c6a1033f0a Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Wed, 29 Nov 2023 13:47:51 +0000 Subject: [PATCH] Convert Jdo to Sql query to fix sorting on components fields (#441) * add repo url in integrity meta information in endpoint * Squashed commit of the following: commit fb5a5202f8d75d9699bc19f0354b97eae0f95777 Merge: 8944f926 805582b6 Author: VithikaS <122881935+VithikaS@users.noreply.github.com> Date: Tue Oct 31 14:53:11 2023 +0000 Merge pull request #410 from DependencyTrack/fix-integrity-meta-query fix query for fetching integrity data commit 805582b62bc30e087d74956ef784b5e6aaab7eb9 Author: vithikashukla Date: Tue Oct 31 13:56:53 2023 +0000 fix query for fetching integrity data Signed-off-by: vithikashukla commit 8944f9264b69c1d863fa5dec8508af773405ebfb Author: VithikaS <122881935+VithikaS@users.noreply.github.com> Date: Tue Oct 31 09:15:13 2023 +0000 Integrity analysis if integrity metadata is present (#409) commit cf6e732cec66fa4a618be6781299851aebe74f89 Merge: a1ebb13e 4111a37c Author: VithikaS <122881935+VithikaS@users.noreply.github.com> Date: Mon Oct 30 19:02:05 2023 +0000 Merge pull request #408 from DependencyTrack/remove-mockserver Remove `mockserver-netty` dependency commit a1ebb13e26914a99960892deafc23d4e34b77a0d Merge: 42acfab7 32545115 Author: Niklas Date: Mon Oct 30 17:16:49 2023 +0100 Merge pull request #407 from DependencyTrack/dependabot/github_actions/aquasecurity/trivy-action-0.13.0 Bump aquasecurity/trivy-action from 0.12.0 to 0.13.0 commit 42acfab71572f1005623307ddeed57e2f65aa4b5 Merge: 529da266 0aee9746 Author: Niklas Date: Mon Oct 30 17:16:40 2023 +0100 Merge pull request #406 from DependencyTrack/dependabot/github_actions/bufbuild/buf-setup-action-1.27.2 Bump bufbuild/buf-setup-action from 1.27.1 to 1.27.2 commit 4111a37ca19c231fd9d6fe0fe7f911465400e685 Author: nscuro Date: Mon Oct 30 16:55:56 2023 +0100 Remove `mockserver-netty` dependency For some strange reason, removal of MockServer required addition of `javax.servlet-api`, even though it should come in via `alpine-parent` already. Signed-off-by: nscuro commit 529da2661cf601eb85afcaef7067f35cb7dc2e2e Merge: 182cad73 34ef4a27 Author: Niklas Date: Mon Oct 30 16:21:43 2023 +0100 Merge pull request #405 from DependencyTrack/nscuro-patch-1 Remove unused `frontend.version` property commit 182cad737745c68e33378ce5d1e08898aa0d3e28 Merge: 24f6d3a9 f38d11d0 Author: VithikaS <122881935+VithikaS@users.noreply.github.com> Date: Mon Oct 30 14:34:37 2023 +0000 Merge pull request #404 from DependencyTrack/port-pr-3129 Force downgrade of `logstash-logback-encoder` to `7.3` commit 24f6d3a91a3e2df0a9856490bc3d2d01fc32728b Merge: ef5086b3 e9f0f4f4 Author: VithikaS <122881935+VithikaS@users.noreply.github.com> Date: Mon Oct 30 14:34:17 2023 +0000 Merge pull request #403 from DependencyTrack/port-pr-3126 Fix impossible SQL query conditions causing DB indexes to be bypassed commit 32545115dffd5e3bd3deca362631f6d07715baca Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Oct 30 14:30:22 2023 +0000 Bump aquasecurity/trivy-action from 0.12.0 to 0.13.0 Bumps [aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action) from 0.12.0 to 0.13.0. - [Release notes](https://github.com/aquasecurity/trivy-action/releases) - [Commits](https://github.com/aquasecurity/trivy-action/compare/0.12.0...0.13.0) --- updated-dependencies: - dependency-name: aquasecurity/trivy-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] commit 0aee9746b9e73b5ea7bc5e73971d89e9757e54c9 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon Oct 30 14:30:18 2023 +0000 Bump bufbuild/buf-setup-action from 1.27.1 to 1.27.2 Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.27.1 to 1.27.2. - [Release notes](https://github.com/bufbuild/buf-setup-action/releases) - [Commits](https://github.com/bufbuild/buf-setup-action/compare/6bdfab1cc71322c663d891914e69da7a6c2c5f52...1158f4fa81bc02e1ff62abcca6d516c9e24c77da) --- updated-dependencies: - dependency-name: bufbuild/buf-setup-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] commit 34ef4a27966dff7b0e91ae077c08d87f68344f42 Author: Niklas Date: Mon Oct 30 15:18:32 2023 +0100 Remove unused `frontend.version` property Signed-off-by: Niklas commit ef5086b37ab20ba31cc6c173590f8487f32adcaf Merge: 2a035b0b 4bb51d54 Author: Niklas Date: Mon Oct 30 15:02:59 2023 +0100 Merge pull request #400 from DependencyTrack/dependabot/maven/org.apache.maven.plugins-maven-clean-plugin-3.3.2 Bump org.apache.maven.plugins:maven-clean-plugin from 3.3.1 to 3.3.2 commit 2a035b0b05de618434cf7eccc6b80ccbe2d598a0 Merge: 4e15bd68 f5174bee Author: Niklas Date: Mon Oct 30 15:02:41 2023 +0100 Merge pull request #402 from DependencyTrack/add-schema-upgrade Schema upgrade v5.2.0 commit f38d11d03da3e9c7d2d74f44d96232baae138b22 Author: nscuro Date: Mon Oct 30 14:10:06 2023 +0100 Force downgrade of `logstash-logback-encoder` to `7.3` Ported from https://github.com/DependencyTrack/dependency-track/pull/3129 Signed-off-by: nscuro commit e9f0f4f44a58a81bc81e7d340664226ebb0aa835 Author: nscuro Date: Mon Oct 30 14:06:42 2023 +0100 Fix impossible SQL query conditions causing DB indexes to be bypassed Ported from https://github.com/DependencyTrack/dependency-track/pull/3126 Signed-off-by: nscuro commit f5174bee65820e31168b33077ebf0c4d81cedd4e Author: vithikashukla Date: Mon Oct 30 12:49:49 2023 +0000 schema upgarde Signed-off-by: vithikashukla commit 4bb51d54e682eec86bf358714b59f8149a171c21 Author: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri Oct 27 14:44:39 2023 +0000 Bump org.apache.maven.plugins:maven-clean-plugin from 3.3.1 to 3.3.2 Bumps [org.apache.maven.plugins:maven-clean-plugin](https://github.com/apache/maven-clean-plugin) from 3.3.1 to 3.3.2. - [Release notes](https://github.com/apache/maven-clean-plugin/releases) - [Commits](https://github.com/apache/maven-clean-plugin/compare/maven-clean-plugin-3.3.1...maven-clean-plugin-3.3.2) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-clean-plugin dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * WIP * WIP 3 * Update ComponentQueryManager.java * component projection added for SQL * added test for ComponentQueryManager with postgres * add test and projection mapping * changed list to paginated result * Update ComponentQueryManager.java * fix mapping of postgres byte array * fix tests * Update ComponentQueryManager.java * Update ComponentQueryManager.java * addressed PR comments * Update ComponentResourcePostgresTest.java * fix transient object warnings * Update FindingResource.java --------- Signed-off-by: mehab Co-authored-by: mehab --- .../model/ComponentMetaInformation.java | 3 +- .../model/sqlmapping/ComponentProjection.java | 245 ++++++++++++++++++ .../persistence/ComponentQueryManager.java | 199 +++++++++++--- .../persistence/MetricsQueryManager.java | 14 + .../persistence/QueryManager.java | 12 +- .../resources/v1/FindingResource.java | 1 - .../ComponentQueryManangerPostgresTest.java | 211 +++++++++++++++ .../persistence/QueryManagerTest.java | 2 + .../v1/ComponentResourcePostgresTest.java | 229 ++++++++++++++++ .../resources/v1/ComponentResourceTest.java | 65 ----- 10 files changed, 882 insertions(+), 99 deletions(-) create mode 100644 src/main/java/org/dependencytrack/model/sqlmapping/ComponentProjection.java create mode 100644 src/test/java/org/dependencytrack/persistence/ComponentQueryManangerPostgresTest.java create mode 100644 src/test/java/org/dependencytrack/resources/v1/ComponentResourcePostgresTest.java diff --git a/src/main/java/org/dependencytrack/model/ComponentMetaInformation.java b/src/main/java/org/dependencytrack/model/ComponentMetaInformation.java index f78b4c736..b69f56907 100644 --- a/src/main/java/org/dependencytrack/model/ComponentMetaInformation.java +++ b/src/main/java/org/dependencytrack/model/ComponentMetaInformation.java @@ -3,5 +3,6 @@ import java.util.Date; public record ComponentMetaInformation(Date publishedDate, IntegrityMatchStatus integrityMatchStatus, - Date lastFetched) { + Date lastFetched, + String integrityRepoUrl) { } diff --git a/src/main/java/org/dependencytrack/model/sqlmapping/ComponentProjection.java b/src/main/java/org/dependencytrack/model/sqlmapping/ComponentProjection.java new file mode 100644 index 000000000..17cc0fb4e --- /dev/null +++ b/src/main/java/org/dependencytrack/model/sqlmapping/ComponentProjection.java @@ -0,0 +1,245 @@ +package org.dependencytrack.model.sqlmapping; + +import org.apache.commons.lang3.SerializationUtils; +import org.dependencytrack.model.Classifier; +import org.dependencytrack.model.Component; +import org.dependencytrack.model.ComponentMetaInformation; +import org.dependencytrack.model.IntegrityMatchStatus; +import org.dependencytrack.model.License; +import org.dependencytrack.model.Project; + +import java.util.Date; +import java.util.UUID; + +public class ComponentProjection { + + public long id; + + public String uuid; + + public String author; + + public String group; + + public String name; + + public String text; + + public String publisher; + + public String version; + + public String classifier; + + public String copyright; + + public String description; + + public String extension; + + public String filename; + + public byte[] externalReferences; + + public String directDependencies; + + public String cpe; + + public String purl; + + public String purlCoordinates; + + public String swidTagId; + + public Boolean internal; + + public Double lastInheritedRiskScore; + + public String md5; + + public String sha1; + + public String sha256; + + public String sha384; + + public String sha512; + + public String sha3_256; + + public String sha3_384; + + public String sha3_512; + + public String blake2b_256; + + public String blake2b_384; + + public String blake2b_512; + + public String blake3; + + public String licenseUrl; + + public String componentLicenseName; + + public String licenseExpression; + + public Date publishedAt; + + public Date lastFetch; + + public String integrityCheckStatus; + + public String integrityRepoUrl; + + public Long projectId; + + public String projectUuid; + + public String projectGroup; + + public String projectName; + + public String projectVersion; + + public String projectClassifier; + + public Boolean projectActive; + + public String projectAuthor; + + public String projectCpe; + + public String projectDescription; + + public String projectPurl; + + public String projectSwidTagId; + + public Date lastBomImport; + + public String lastBomImportFormat; + + public Double projectLastInheritedRiskScore; + + public String projectDirectDependencies; + + public byte[] projectExternalReferences; + + public String projectPublisher; + + public String licenseUuid; + + public String licenseId; + public String licenseName; + + public Boolean isOsiApproved; + + public Boolean isFsfLibre; + + public Boolean isCustomLicense; + + public Long totalCount; + + public static Component mapToComponent(ComponentProjection result) { + Component componentPersistent = new Component(); + componentPersistent.setAuthor(result.author); + componentPersistent.setBlake2b_256(result.blake2b_256); + componentPersistent.setBlake2b_384(result.blake2b_384); + componentPersistent.setBlake2b_512(result.blake2b_512); + componentPersistent.setBlake3(result.blake3); + if (result.classifier != null) { + componentPersistent.setClassifier(Classifier.valueOf(result.classifier)); + } + componentPersistent.setCopyright(result.copyright); + componentPersistent.setCpe(result.cpe); + componentPersistent.setDescription(result.description); + componentPersistent.setDirectDependencies(result.directDependencies); + componentPersistent.setExtension(result.extension); + componentPersistent.setGroup(result.group); + componentPersistent.setId(result.id); + if (result.internal != null) { + componentPersistent.setInternal(result.internal); + } + componentPersistent.setNotes(result.text); + componentPersistent.setSwidTagId(result.swidTagId); + componentPersistent.setLastInheritedRiskScore(result.lastInheritedRiskScore); + componentPersistent.setLicense(result.componentLicenseName); + componentPersistent.setLicenseUrl(result.licenseUrl); + componentPersistent.setLicenseExpression(result.licenseExpression); + componentPersistent.setName(result.name); + if (result.uuid != null) { + componentPersistent.setUuid(UUID.fromString(result.uuid)); + } + if (result.externalReferences != null) { + componentPersistent.setExternalReferences(SerializationUtils.deserialize(result.externalReferences)); + } + componentPersistent.setPurl(result.purl); + componentPersistent.setPurlCoordinates(result.purlCoordinates); + componentPersistent.setVersion(result.version); + componentPersistent.setMd5(result.md5); + componentPersistent.setSha1(result.sha1); + componentPersistent.setSha256(result.sha256); + componentPersistent.setSha384(result.sha384); + componentPersistent.setSha512(result.sha512); + componentPersistent.setSha3_256(result.sha3_256); + componentPersistent.setSha3_384(result.sha3_384); + componentPersistent.setSha3_512(result.sha3_512); + + var project = new Project(); + if (result.projectId != null) { + project.setId(result.projectId); + } + project.setAuthor(result.projectAuthor); + if (result.projectActive != null) { + project.setActive(result.projectActive); + } + project.setDescription(result.projectDescription); + project.setCpe(result.projectCpe); + project.setPurl(result.projectPurl); + project.setSwidTagId(result.projectSwidTagId); + project.setPublisher(result.projectPublisher); + if (result.projectExternalReferences != null) { + project.setExternalReferences(SerializationUtils.deserialize(result.projectExternalReferences)); + } + project.setLastInheritedRiskScore(result.projectLastInheritedRiskScore); + if (result.projectClassifier != null) { + project.setClassifier(Classifier.valueOf(result.projectClassifier)); + } + project.setDirectDependencies(result.projectDirectDependencies); + project.setLastBomImport(result.lastBomImport); + project.setLastBomImportFormat(result.lastBomImportFormat); + project.setName(result.projectName); + if (result.projectUuid != null) { + project.setUuid(UUID.fromString(result.projectUuid)); + } + project.setVersion(result.projectVersion); + componentPersistent.setProject(project); + + var license = new License(); + license.setName(result.licenseName); + if (result.licenseUuid != null) { + license.setUuid(UUID.fromString(result.licenseUuid)); + } + if (result.isCustomLicense != null) { + license.setCustomLicense(result.isCustomLicense); + } + if (result.isFsfLibre != null) { + license.setFsfLibre(result.isFsfLibre); + } + license.setLicenseId(result.licenseId); + if (result.isOsiApproved != null) { + license.setOsiApproved(result.isOsiApproved); + } + license.setName(result.licenseName); + componentPersistent.setResolvedLicense(license); + + var componentMetaInformation = new ComponentMetaInformation(result.publishedAt, + result.integrityCheckStatus != null ? IntegrityMatchStatus.valueOf(result.integrityCheckStatus) : null, + result.lastFetch, result.integrityRepoUrl); + componentPersistent.setComponentMetaInformation(componentMetaInformation); + + return componentPersistent; + } +} diff --git a/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java b/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java index 756594626..4f2dd795b 100644 --- a/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java @@ -21,6 +21,7 @@ import alpine.model.ApiKey; import alpine.model.Team; import alpine.model.UserPrincipal; +import alpine.persistence.OrderDirection; import alpine.persistence.PaginatedResult; import alpine.resources.AlpineRequest; import com.github.packageurl.MalformedPackageURLException; @@ -32,6 +33,7 @@ import org.dependencytrack.model.Project; import org.dependencytrack.model.RepositoryMetaComponent; import org.dependencytrack.model.RepositoryType; +import org.dependencytrack.model.sqlmapping.ComponentProjection; import org.dependencytrack.resources.v1.vo.DependencyGraphResponse; import org.dependencytrack.tasks.IntegrityMetaInitializerTask; @@ -50,6 +52,8 @@ import java.util.Set; import java.util.UUID; +import static org.dependencytrack.model.sqlmapping.ComponentProjection.mapToComponent; + final class ComponentQueryManager extends QueryManager implements IQueryManager { /** @@ -156,56 +160,191 @@ public PaginatedResult getComponents(final Project project, final boolean includ * @return a List of Dependency objects */ public PaginatedResult getComponents(final Project project, final boolean includeMetrics, final boolean onlyOutdated, final boolean onlyDirect) { - final PaginatedResult result; - String querySring = "SELECT FROM org.dependencytrack.model.Component WHERE project == :project "; - if (filter != null) { - querySring += " && (project == :project) && name.toLowerCase().matches(:name)"; - } + List componentsResult = new ArrayList<>(); + String queryString = """ + SELECT "A0"."ID" AS "id", + "A0"."NAME" AS "name", + "A0"."AUTHOR" AS "author", + "A0"."BLAKE2B_256" AS "blake2b_256", + "A0"."BLAKE2B_384" AS "blake2b_384", + "A0"."BLAKE2B_512" AS "blake2b_512", + "A0"."BLAKE3" AS "blake3", + "A0"."CLASSIFIER" AS "classifier", + "A0"."COPYRIGHT" AS "copyright", + "A0"."CPE" AS "cpe", + "A0"."PUBLISHER" AS "publisher", + "A0"."PURL" AS "purl", + "A0"."PURLCOORDINATES" AS "purlCoordinates", + "A0"."DESCRIPTION" AS "description", + "A0"."DIRECT_DEPENDENCIES" AS "directDependencies", + "A0"."EXTENSION" AS "extension", + "A0"."EXTERNAL_REFERENCES" AS "externalReferences", + "A0"."FILENAME" AS "filename", + "A0"."GROUP" AS "group", + "A0"."INTERNAL" AS "internal", + "A0"."LAST_RISKSCORE" AS "lastInheritedRiskScore", + "A0"."LICENSE" AS "componentLicenseName", + "A0"."LICENSE_EXPRESSION" AS "licenseExpression", + "A0"."LICENSE_URL" AS "licenseUrl", + "A0"."TEXT" AS "text", + "A0"."MD5" AS "md5", + "A0"."SHA1" AS "sha1", + "A0"."SHA_256" AS "sha256", + "A0"."SHA_384" AS "sha384", + "A0"."SHA_512" AS "sha512", + "A0"."SHA3_256" AS "sha3_256", + "A0"."SHA3_384" AS "sha3_384", + "A0"."SHA3_512" AS "sha3_512", + "A0"."SWIDTAGID" AS "swidTagId", + "A0"."UUID" AS "uuid", + "A0"."VERSION" AS "version", + "B0"."ACTIVE" AS "projectActive", + "B0"."AUTHOR" AS "projectAuthor", + "B0"."CLASSIFIER" AS "projectClassifier", + "B0"."CPE" AS "projectCpe", + "B0"."DESCRIPTION" AS "projectDescription", + "B0"."DIRECT_DEPENDENCIES" AS "projectDirectDependencies", + "B0"."EXTERNAL_REFERENCES" AS "projectExternalReferences", + "B0"."GROUP" AS "projectGroup", + "B0"."ID" AS "projectId", + "B0"."LAST_BOM_IMPORTED" AS "lastBomImport", + "B0"."LAST_BOM_IMPORTED_FORMAT" AS "lastBomImportFormat", + "B0"."LAST_RISKSCORE" AS "projectLastInheritedRiskScore", + "B0"."NAME" AS "projectName", + "B0"."PUBLISHER" AS "projectPublisher", + "B0"."PURL" AS "projectPurl", + "B0"."SWIDTAGID" AS "projectSwidTagId", + "B0"."UUID" AS "projectUuid", + "B0"."VERSION" AS "projectVersion", + "D0"."ISCUSTOMLICENSE" AS "isCustomLicense", + "D0"."FSFLIBRE" AS "isFsfLibre", + "D0"."LICENSEID" AS "licenseId", + "D0"."ISOSIAPPROVED" AS "isOsiApproved", + "D0"."UUID" AS "licenseUuid", + "D0"."NAME" AS "licenseName", + "I0"."LAST_FETCH" AS "lastFetch", + "I0"."PUBLISHED_AT" AS "publishedAt", + "IA"."INTEGRITY_CHECK_STATUS" AS "integrityCheckStatus", + "I0"."REPOSITORY_URL" AS "integrityRepoUrl", + COUNT(*) OVER() AS "totalCount" + FROM "COMPONENT" "A0" + INNER JOIN "PROJECT" "B0" ON "A0"."PROJECT_ID" = "B0"."ID" + LEFT JOIN "INTEGRITY_META_COMPONENT" "I0" ON "A0"."PURL" = "I0"."PURL" + LEFT JOIN "INTEGRITY_ANALYSIS" "IA" ON "A0"."ID" = "IA"."COMPONENT_ID" + LEFT OUTER JOIN "LICENSE" "D0" ON "A0"."LICENSE_ID" = "D0"."ID" + WHERE "A0"."PROJECT_ID" = ? + """; + if (onlyOutdated) { // Components are considered outdated when metadata does exists, but the version is different than latestVersion // Different should always mean version < latestVersion // Hack JDO using % instead of .* to get the SQL LIKE clause working: - querySring += - " && !(" + - " SELECT FROM org.dependencytrack.model.RepositoryMetaComponent m " + - " WHERE m.name == this.name " + - " && m.namespace == this.group " + - " && m.latestVersion != this.version " + - " && this.purl.matches('pkg:' + m.repositoryType.toString().toLowerCase() + '/%') " + - " ).isEmpty()"; + queryString += + """ + AND NOT (NOT EXISTS ( + SELECT "M"."ID" + FROM "REPOSITORY_META_COMPONENT" "M" WHERE "M"."NAME" = "A0"."NAME" + AND "M"."NAMESPACE" = "A0"."GROUP" + AND "M"."LATEST_VERSION" <> "A0"."VERSION" + AND "A0"."PURL" LIKE (('pkg:' || LOWER("M"."REPOSITORY_TYPE")) || '/%') ESCAPE E'\\\\')) + """; } if (onlyDirect) { - querySring += - " && this.project.directDependencies.matches('%\"uuid\":\"'+this.uuid+'\"%') "; // only direct dependencies + queryString += + """ + AND "B0"."DIRECT_DEPENDENCIES" LIKE (('%' || "A0"."UUID") || '%') ESCAPE E'\\\\' + """; } - final Query query = pm.newQuery(querySring); - query.getFetchPlan().setMaxFetchDepth(2); if (orderBy == null) { - query.setOrdering("name asc, version desc, id asc"); - } - if (filter != null) { - final String filterString = ".*" + filter.toLowerCase() + ".*"; - result = execute(query, project, filterString); + queryString += + """ + ORDER BY "name", + "version" DESC + """; } else { - result = execute(query, project); + if (orderBy.equalsIgnoreCase("componentMetaInformation.publishedDate")) { + queryString += + """ + ORDER BY "publishedAt" + """; + } + if (orderBy.equalsIgnoreCase("version")) { + queryString += + """ + ORDER BY "version" + """; + } + if (orderBy.equalsIgnoreCase("name")) { + queryString += + """ + ORDER BY "name" + """; + } + if (orderBy.equalsIgnoreCase("group")) { + queryString += + """ + ORDER BY "group" + """; + } + if (orderBy.equalsIgnoreCase("lastInheritedRiskScore")) { + queryString += + """ + ORDER BY "lastInheritedRiskScore" + """; + } + if (orderBy.equalsIgnoreCase("componentMetaInformation.integrityMatchStatus")) { + queryString += + """ + ORDER BY "integrityCheckStatus" + """; + } + if (queryString.contains("ORDER BY")) { + if (orderDirection == OrderDirection.ASCENDING) { + queryString += " ASC "; + } else if (orderDirection == OrderDirection.DESCENDING) { + queryString += " DESC "; + } + queryString += """ + , "id" + """; + } } - if (includeMetrics) { - // Populate each Component object in the paginated result with transitive related - // data to minimize the number of round trips a client needs to make, process, and render. - for (Component component : result.getList(Component.class)) { - component.setMetrics(getMostRecentDependencyMetrics(component)); + + if (pagination != null && pagination.isPaginated()) { + queryString += + """ + OFFSET %d + LIMIT %d; + """.formatted(pagination.getOffset(), pagination.getLimit()); + } + + Query query = pm.newQuery(Query.SQL, queryString); + query.setParameters(project.getId()); + List resultSet; + try { + resultSet = List.copyOf(query.executeResultList(ComponentProjection.class)); + } + finally { + query.closeAll(); + } + for (final var result : resultSet) { + final org.dependencytrack.model.Component component = mapToComponent(result); + if (includeMetrics) { +// Populate each Component object in the paginated result with transitive related +// data to minimize the number of round trips a client needs to make, process, and render. + component.setMetrics(getMostRecentDependencyMetricsById(component.getId())); final PackageURL purl = component.getPurl(); if (purl != null) { final RepositoryType type = RepositoryType.resolve(purl); if (RepositoryType.UNSUPPORTED != type) { final RepositoryMetaComponent repoMetaComponent = getRepositoryMetaComponent(type, purl.getNamespace(), purl.getName()); component.setRepositoryMeta(repoMetaComponent); - component.setComponentMetaInformation(getMetaInformation(component.getUuid())); } } } + componentsResult.add(component); } - return result; + return (new PaginatedResult()).objects(componentsResult).total(resultSet.isEmpty() ? 0 : resultSet.get(0).totalCount); } /** diff --git a/src/main/java/org/dependencytrack/persistence/MetricsQueryManager.java b/src/main/java/org/dependencytrack/persistence/MetricsQueryManager.java index 829d8b568..f04e7f3a3 100644 --- a/src/main/java/org/dependencytrack/persistence/MetricsQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/MetricsQueryManager.java @@ -150,6 +150,20 @@ public DependencyMetrics getMostRecentDependencyMetrics(Component component) { } + /** + * Retrieves the most recent DependencyMetrics by component ID. + * + * @param componentId the Component ID to retrieve metrics for + * @return a DependencyMetrics object + */ + public DependencyMetrics getMostRecentDependencyMetricsById(long componentId) { + final Query query = pm.newQuery(DependencyMetrics.class, "component.id == :componentId"); + query.setOrdering("lastOccurrence desc"); + query.setRange(0, 1); + return singleResult(query.execute(componentId)); + + } + /** * Retrieves DependencyMetrics in descending order starting with the most recent. * diff --git a/src/main/java/org/dependencytrack/persistence/QueryManager.java b/src/main/java/org/dependencytrack/persistence/QueryManager.java index 7a1007529..f2cf5250c 100644 --- a/src/main/java/org/dependencytrack/persistence/QueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/QueryManager.java @@ -1228,6 +1228,10 @@ public DependencyMetrics getMostRecentDependencyMetrics(Component component) { return getMetricsQueryManager().getMostRecentDependencyMetrics(component); } + public DependencyMetrics getMostRecentDependencyMetricsById(long component) { + return getMetricsQueryManager().getMostRecentDependencyMetricsById(component); + } + public PaginatedResult getDependencyMetrics(Component component) { return getMetricsQueryManager().getDependencyMetrics(component); } @@ -1882,7 +1886,7 @@ public ComponentMetaInformation getMetaInformation(UUID uuid) { Connection connection = null; PreparedStatement preparedStatement = null; String queryString = """ - SELECT "C"."ID", "C"."PURL", "IMC"."LAST_FETCH", "IMC"."PUBLISHED_AT", "IA"."INTEGRITY_CHECK_STATUS" FROM "COMPONENT" "C" + SELECT "C"."ID", "C"."PURL", "IMC"."LAST_FETCH", "IMC"."PUBLISHED_AT", "IA"."INTEGRITY_CHECK_STATUS", "IMC"."REPOSITORY_URL" FROM "COMPONENT" "C" JOIN "INTEGRITY_META_COMPONENT" "IMC" ON "C"."PURL" ="IMC"."PURL" LEFT JOIN "INTEGRITY_ANALYSIS" "IA" ON "IA"."COMPONENT_ID" ="C"."ID" WHERE "C"."UUID" = ? """; try { @@ -1895,6 +1899,7 @@ public ComponentMetaInformation getMetaInformation(UUID uuid) { Date publishedDate = null; Date lastFetch = null; IntegrityMatchStatus integrityMatchStatus = null; + String integrityRepoUrl = null; if (resultSet.getTimestamp("PUBLISHED_AT") != null) { publishedDate = Date.from(resultSet.getTimestamp("PUBLISHED_AT").toInstant()); } @@ -1904,7 +1909,10 @@ public ComponentMetaInformation getMetaInformation(UUID uuid) { if (resultSet.getString("INTEGRITY_CHECK_STATUS") != null) { integrityMatchStatus = IntegrityMatchStatus.valueOf(resultSet.getString("INTEGRITY_CHECK_STATUS")); } - return new ComponentMetaInformation(publishedDate, integrityMatchStatus, lastFetch); + if(resultSet.getString("REPOSITORY_URL") != null) { + integrityRepoUrl = String.valueOf(resultSet.getString("REPOSITORY_URL")); + } + return new ComponentMetaInformation(publishedDate, integrityMatchStatus, lastFetch, integrityRepoUrl); } } catch (Exception ex) { diff --git a/src/main/java/org/dependencytrack/resources/v1/FindingResource.java b/src/main/java/org/dependencytrack/resources/v1/FindingResource.java index 3e14e3007..220a67797 100644 --- a/src/main/java/org/dependencytrack/resources/v1/FindingResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/FindingResource.java @@ -88,7 +88,6 @@ public Response getFindingsByProject(@PathParam("uuid") String uuid, final Project project = qm.getObjectByUuid(Project.class, uuid); if (project != null) { if (qm.hasAccess(super.getPrincipal(), project)) { - //final long totalCount = qm.getVulnerabilityCount(project, suppressed); final List findings = qm.getFindings(project, suppressed); if (source != null) { final List filteredList = findings.stream().filter(finding -> source.name().equals(finding.getVulnerability().get("source"))).collect(Collectors.toList()); diff --git a/src/test/java/org/dependencytrack/persistence/ComponentQueryManangerPostgresTest.java b/src/test/java/org/dependencytrack/persistence/ComponentQueryManangerPostgresTest.java new file mode 100644 index 000000000..6df6ba80b --- /dev/null +++ b/src/test/java/org/dependencytrack/persistence/ComponentQueryManangerPostgresTest.java @@ -0,0 +1,211 @@ +package org.dependencytrack.persistence; + +import com.github.packageurl.MalformedPackageURLException; +import com.github.packageurl.PackageURL; +import org.dependencytrack.AbstractPostgresEnabledTest; +import org.dependencytrack.model.Classifier; +import org.dependencytrack.model.Component; +import org.dependencytrack.model.ExternalReference; +import org.dependencytrack.model.IntegrityAnalysis; +import org.dependencytrack.model.IntegrityMatchStatus; +import org.dependencytrack.model.IntegrityMetaComponent; +import org.dependencytrack.model.License; +import org.dependencytrack.model.Project; +import org.dependencytrack.model.RepositoryMetaComponent; +import org.dependencytrack.model.RepositoryType; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ComponentQueryManangerPostgresTest extends AbstractPostgresEnabledTest { + + @Test + public void testGetAllComponents() throws MalformedPackageURLException { + + final Project project = prepareProject(); + var components = qm.getComponents(project, false, false, false); + assertThat(components.getTotal()).isEqualTo(1000); + } + + @Test + public void testGetOutdatedComponents() throws MalformedPackageURLException { + + final Project project = prepareProject(); + var components = qm.getComponents(project, false, true, false); + assertThat(components.getTotal()).isEqualTo(200); + } + + @Test + public void testGetDirectComponents() throws MalformedPackageURLException { + + final Project project = prepareProject(); + var components = qm.getComponents(project, false, false, true); + assertThat(components.getTotal()).isEqualTo(100); + } + + @Test + public void testGetOutdatedDirectComponents() throws MalformedPackageURLException { + + final Project project = prepareProject(); + var components = qm.getComponents(project, true, true, true); + assertThat(components.getTotal()).isEqualTo(75); + } + + private Project prepareProject() throws MalformedPackageURLException { + final Project project = qm.createProject("Acme Application", null, null, null, null, null, true, false); + final List directDepencencies = new ArrayList<>(); + // Generate 1000 dependencies + for (int i = 0; i < 1000; i++) { + Component component = new Component(); + component.setProject(project); + component.setGroup("component-group"); + component.setName("component-name-" + i); + component.setVersion(String.valueOf(i) + ".0"); + if (i == 0) { + var er = new ExternalReference(); + er.setUrl("https://github.com/thinkcmf/thinkcmf/issues/736"); + component.setExternalReferences(List.of(er)); + } + component.setPurl(new PackageURL(RepositoryType.MAVEN.toString(), "component-group", "component-name-" + i, String.valueOf(i) + ".0", null, null)); + component = qm.createComponent(component, false); + // direct depencencies + if (i < 100) { + // 100 direct depencencies, 900 transitive depencencies + directDepencencies.add("{\"uuid\":\"" + component.getUuid() + "\"}"); + } + // Recent & Outdated + if ((i >= 25) && (i < 225)) { + // 100 outdated components, 75 of these are direct dependencies, 25 transitive + final var metaComponent = new RepositoryMetaComponent(); + metaComponent.setRepositoryType(RepositoryType.MAVEN); + metaComponent.setNamespace("component-group"); + metaComponent.setName("component-name-" + i); + metaComponent.setLatestVersion(String.valueOf(i + 1) + ".0"); + metaComponent.setLastCheck(new Date()); + qm.persist(metaComponent); + } else if (i < 500) { + // 300 recent components, 25 of these are direct dependencies + final var metaComponent = new RepositoryMetaComponent(); + metaComponent.setRepositoryType(RepositoryType.MAVEN); + metaComponent.setNamespace("component-group"); + metaComponent.setName("component-name-" + i); + metaComponent.setLatestVersion(String.valueOf(i) + ".0"); + metaComponent.setLastCheck(new Date()); + qm.persist(metaComponent); + } else { + // 500 components with no RepositoryMetaComponent containing version + // metadata, all transitive dependencies + } + } + project.setDirectDependencies("[" + String.join(",", directDepencencies.toArray(new String[0])) + "]"); + return project; + } + + /** + * (Regression-)Test for ensuring that all data is mapped in the project component + */ + @Test + public void testMappingComponentProjectionWithAllFields() { + final var project = new Project(); + project.setUuid(UUID.fromString("d7173786-60aa-4a4f-a950-c92fe6422307")); + project.setGroup("projectGroup"); + project.setName("projectName"); + project.setVersion("projectVersion"); + project.setClassifier(Classifier.APPLICATION); + project.setActive(true); + project.setCpe("projectCpe"); + project.setPurl("projectPurl"); + project.setSwidTagId("projectSwidTagId"); + project.setAuthor("projectAuthor"); + project.setDescription("projectDescription"); + project.setDirectDependencies("{7e5f6465-d2f2-424f-b1a4-68d186fa2b46}"); + project.setExternalReferences(List.of(new ExternalReference())); + project.setLastBomImport(new java.util.Date()); + project.setLastBomImportFormat("projectBomFormat"); + project.setLastInheritedRiskScore(7.7); + project.setPublisher("projectPublisher"); + qm.persist(project); + + final var license = new License(); + license.setUuid(UUID.fromString("dc9876c2-0adc-422b-9f71-3ca78285f138")); + license.setLicenseId("resolvedLicenseId"); + license.setName("resolvedLicenseName"); + license.setOsiApproved(true); + license.setFsfLibre(true); + license.setCustomLicense(true); + qm.persist(license); + + final var component = new Component(); + component.setProject(project); + component.setUuid(UUID.fromString("7e5f6465-d2f2-424f-b1a4-68d186fa2b46")); + component.setGroup("componentGroup"); + component.setName("componentName"); + component.setAuthor("componentAuthor"); + component.setVersion("1.0"); + component.setDescription("componentDescription"); + component.setClassifier(Classifier.LIBRARY); + component.setCopyright("componentCopyright"); + component.setCpe("componentCpe"); + component.setPurl("pkg:maven/a/b@1.0"); + component.setPublisher("componentPublisher"); + component.setPurlCoordinates("componentPurlCoordinates"); + component.setDirectDependencies("componentDirectDependencies"); + component.setExtension("componentExtension"); + component.setExternalReferences(List.of(new ExternalReference())); + component.setFilename("componentFilename"); + component.setLastInheritedRiskScore(5.5); + component.setSwidTagId("componentSwidTagId"); + component.setInternal(true); + component.setNotes("componentText"); + 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.setLicense("componentLicenseName"); + component.setLicenseExpression("componentLicenseExpression"); + component.setLicenseUrl("componentLicenseUrl"); + component.setResolvedLicense(license); + qm.persist(component); + + final var metaComponent = new IntegrityMetaComponent(); + metaComponent.setPurl("componentPurl"); + metaComponent.setPublishedAt(new java.util.Date(222)); + metaComponent.setRepositoryUrl("integrityRepoUrl"); + metaComponent.setLastFetch(new Date()); + qm.persist(metaComponent); + + final var integrityAnalysis = new IntegrityAnalysis(); + integrityAnalysis.setComponent(component); + integrityAnalysis.setMd5HashMatchStatus(IntegrityMatchStatus.HASH_MATCH_UNKNOWN); + integrityAnalysis.setSha1HashMatchStatus(IntegrityMatchStatus.HASH_MATCH_UNKNOWN); + integrityAnalysis.setSha256HashMatchStatus(IntegrityMatchStatus.HASH_MATCH_UNKNOWN); + integrityAnalysis.setSha512HashMatchStatus(IntegrityMatchStatus.HASH_MATCH_UNKNOWN); + integrityAnalysis.setIntegrityCheckStatus(IntegrityMatchStatus.HASH_MATCH_UNKNOWN); + integrityAnalysis.setUpdatedAt(new java.util.Date(222)); + qm.persist(integrityAnalysis); + + var repoMeta = new RepositoryMetaComponent(); + repoMeta.setName(component.getName()); + repoMeta.setLatestVersion("2.0"); + repoMeta.setNamespace(component.getGroup()); + repoMeta.setRepositoryType(RepositoryType.MAVEN); + repoMeta.setLastCheck(new java.util.Date(222)); + qm.persist(repoMeta); + + var components = qm.getComponents(project, false, true, false); + assertThat(components.getTotal()).isEqualTo(1); + } +} diff --git a/src/test/java/org/dependencytrack/persistence/QueryManagerTest.java b/src/test/java/org/dependencytrack/persistence/QueryManagerTest.java index 2a9bd649e..1f3ad18ef 100644 --- a/src/test/java/org/dependencytrack/persistence/QueryManagerTest.java +++ b/src/test/java/org/dependencytrack/persistence/QueryManagerTest.java @@ -47,12 +47,14 @@ public void testGetMetaInformation() { integrityMetaComponent.setPublishedAt(published); integrityMetaComponent.setLastFetch(published); integrityMetaComponent.setStatus(FetchStatus.PROCESSED); + integrityMetaComponent.setRepositoryUrl("repo.url.com"); qm.createIntegrityMetaComponent(integrityMetaComponent); component = qm.createComponent(component, false); ComponentMetaInformation componentMetaInformation = qm.getMetaInformation(component.getUuid()); assertEquals(HASH_MATCH_PASSED, componentMetaInformation.integrityMatchStatus()); assertEquals(integrityMetaComponent.getPublishedAt(), componentMetaInformation.publishedDate()); assertEquals(integrityMetaComponent.getLastFetch(), componentMetaInformation.lastFetched()); + assertEquals(integrityMetaComponent.getRepositoryUrl(), componentMetaInformation.integrityRepoUrl()); } @Test diff --git a/src/test/java/org/dependencytrack/resources/v1/ComponentResourcePostgresTest.java b/src/test/java/org/dependencytrack/resources/v1/ComponentResourcePostgresTest.java new file mode 100644 index 000000000..9dbadf4ce --- /dev/null +++ b/src/test/java/org/dependencytrack/resources/v1/ComponentResourcePostgresTest.java @@ -0,0 +1,229 @@ +package org.dependencytrack.resources.v1; + +import alpine.Config; +import alpine.server.filters.ApiFilter; +import alpine.server.persistence.PersistenceManagerFactory; +import com.github.packageurl.MalformedPackageURLException; +import com.github.packageurl.PackageURL; +import org.apache.http.HttpStatus; +import org.apache.kafka.clients.producer.MockProducer; +import org.datanucleus.api.jdo.JDOPersistenceManagerFactory; +import org.dependencytrack.ResourceTest; +import org.dependencytrack.TestUtil; +import org.dependencytrack.event.kafka.KafkaProducerInitializer; +import org.dependencytrack.model.Component; +import org.dependencytrack.model.Project; +import org.dependencytrack.model.RepositoryMetaComponent; +import org.dependencytrack.model.RepositoryType; +import org.dependencytrack.persistence.QueryManager; +import org.dependencytrack.persistence.migration.MigrationInitializer; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.servlet.ServletContainer; +import org.glassfish.jersey.test.DeploymentContext; +import org.glassfish.jersey.test.ServletDeploymentContext; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.EnvironmentVariables; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.postgresql.ds.PGSimpleDataSource; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.utility.DockerImageName; + +import javax.jdo.JDOHelper; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonReader; +import javax.ws.rs.core.Response; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ComponentResourcePostgresTest extends ResourceTest { + + @Override + protected DeploymentContext configureDeployment() { + return ServletDeploymentContext.forServlet(new ServletContainer( + new ResourceConfig(ComponentResource.class) + .register(ApiFilter.class))) + .build(); + } + + @Rule + public EnvironmentVariables environmentVariables = new EnvironmentVariables(); + + protected PostgreSQLContainer postgresContainer; + protected MockProducer kafkaMockProducer; + protected QueryManager qm; + + @BeforeClass + public static void init() { + Config.enableUnitTests(); + } + + @BeforeEach + public void setUp() throws Exception { + super.setUp(); + postgresContainer = new PostgreSQLContainer<>(DockerImageName.parse("postgres:11-alpine")) + .withUsername("dtrack") + .withPassword("dtrack") + .withDatabaseName("dtrack"); + postgresContainer.start(); + + final var dataSource = new PGSimpleDataSource(); + dataSource.setUrl(postgresContainer.getJdbcUrl()); + dataSource.setUser(postgresContainer.getUsername()); + dataSource.setPassword(postgresContainer.getPassword()); + MigrationInitializer.runMigration(dataSource, /* silent */ true); + + final var dnProps = TestUtil.getDatanucleusProperties(postgresContainer.getJdbcUrl(), + postgresContainer.getDriverClassName(), + postgresContainer.getUsername(), + postgresContainer.getPassword()); + + final var pmf = (JDOPersistenceManagerFactory) JDOHelper.getPersistenceManagerFactory(dnProps, "Alpine"); + PersistenceManagerFactory.tearDown(); + PersistenceManagerFactory.setJdoPersistenceManagerFactory(pmf); + + qm = new QueryManager(); + + environmentVariables.set("TASK_PORTFOLIO_REPOMETAANALYSIS_LOCKATLEASTFORINMILLIS", "2000"); + this.kafkaMockProducer = (MockProducer) KafkaProducerInitializer.getProducer(); + } + + @AfterEach + public void tearDown() throws Exception { + super.tearDown(); + if (!qm.getPersistenceManager().isClosed() + && qm.getPersistenceManager().currentTransaction().isActive()) { + qm.getPersistenceManager().currentTransaction().rollback(); + } + + PersistenceManagerFactory.tearDown(); + if (postgresContainer != null) { + postgresContainer.stop(); + } + KafkaProducerInitializer.tearDown(); + } + + @Test + public void getAllComponentsTest() throws MalformedPackageURLException { + final Project project = prepareProject(); + + final Response response = target(V1_COMPONENT + "/project/" + project.getUuid()) + .request() + .header(X_API_KEY, apiKey) + .get(Response.class); + assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); + assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("1000"); // 1000 dependencies + + final JsonArray json = parseJsonArray(response); + assertThat(json).hasSize(100); // Default page size is 100 + } + + @Test + public void getOutdatedComponentsTest() throws MalformedPackageURLException { + final Project project = prepareProject(); + + final Response response = target(V1_COMPONENT + "/project/" + project.getUuid()) + .queryParam("onlyOutdated", true) + .queryParam("onlyDirect", false) + .request() + .header(X_API_KEY, apiKey) + .get(Response.class); + assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); + assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("200"); // 200 outdated dependencies, direct and transitive + + final JsonArray json = parseJsonArray(response); + assertThat(json).hasSize(100); // Default page size is 100 + } + + @Test + public void getOutdatedDirectComponentsTest() throws MalformedPackageURLException { + final Project project = prepareProject(); + + final Response response = target(V1_COMPONENT + "/project/" + project.getUuid()) + .queryParam("onlyOutdated", true) + .queryParam("onlyDirect", true) + .request() + .header(X_API_KEY, apiKey) + .get(Response.class); + assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); + assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("75"); // 75 outdated direct dependencies + + final JsonArray json = parseJsonArray(response); + assertThat(json).hasSize(75); + } + + @Test + public void getAllDirectComponentsTest() throws MalformedPackageURLException { + final Project project = prepareProject(); + + final Response response = target(V1_COMPONENT + "/project/" + project.getUuid()) + .queryParam("onlyDirect", true) + .request() + .header(X_API_KEY, apiKey) + .get(Response.class); + assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); + assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("100"); // 100 direct dependencies + + final JsonArray json = parseJsonArray(response); + assertThat(json).hasSize(100); + } + + private Project prepareProject() throws MalformedPackageURLException { + final Project project = qm.createProject("Acme Application", null, null, null, null, null, true, false); + final List directDepencencies = new ArrayList<>(); + // Generate 1000 dependencies + for (int i = 0; i < 1000; i++) { + Component component = new Component(); + component.setProject(project); + component.setGroup("component-group"); + component.setName("component-name-" + i); + component.setVersion(String.valueOf(i) + ".0"); + component.setPurl(new PackageURL(RepositoryType.MAVEN.toString(), "component-group", "component-name-" + i, String.valueOf(i) + ".0", null, null)); + component = qm.createComponent(component, false); + // direct depencencies + if (i < 100) { + // 100 direct depencencies, 900 transitive depencencies + directDepencencies.add("{\"uuid\":\"" + component.getUuid() + "\"}"); + } + // Recent & Outdated + if ((i >= 25) && (i < 225)) { + // 100 outdated components, 75 of these are direct dependencies, 25 transitive + final var metaComponent = new RepositoryMetaComponent(); + metaComponent.setRepositoryType(RepositoryType.MAVEN); + metaComponent.setNamespace("component-group"); + metaComponent.setName("component-name-" + i); + metaComponent.setLatestVersion(String.valueOf(i + 1) + ".0"); + metaComponent.setLastCheck(new Date()); + qm.persist(metaComponent); + } else if (i < 500) { + // 300 recent components, 25 of these are direct dependencies + final var metaComponent = new RepositoryMetaComponent(); + metaComponent.setRepositoryType(RepositoryType.MAVEN); + metaComponent.setNamespace("component-group"); + metaComponent.setName("component-name-" + i); + metaComponent.setLatestVersion(String.valueOf(i) + ".0"); + metaComponent.setLastCheck(new Date()); + qm.persist(metaComponent); + } else { + // 500 components with no RepositoryMetaComponent containing version + // metadata, all transitive dependencies + } + } + project.setDirectDependencies("[" + String.join(",", directDepencencies.toArray(new String[0])) + "]"); + return project; + } + + protected JsonArray parseJsonArray(Response response) { + StringReader stringReader = new StringReader(response.readEntity(String.class)); + try (JsonReader jsonReader = Json.createReader(stringReader)) { + return jsonReader.readArray(); + } + } +} diff --git a/src/test/java/org/dependencytrack/resources/v1/ComponentResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ComponentResourceTest.java index 803704e3b..95b687da5 100644 --- a/src/test/java/org/dependencytrack/resources/v1/ComponentResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/ComponentResourceTest.java @@ -133,71 +133,6 @@ private Project prepareProject() throws MalformedPackageURLException { return project; } - @Test - public void getOutdatedComponentsTest() throws MalformedPackageURLException { - final Project project = prepareProject(); - - final Response response = target(V1_COMPONENT + "/project/" + project.getUuid()) - .queryParam("onlyOutdated", true) - .queryParam("onlyDirect", false) - .request() - .header(X_API_KEY, apiKey) - .get(Response.class); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); - assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("200"); // 200 outdated dependencies, direct and transitive - - final JsonArray json = parseJsonArray(response); - assertThat(json).hasSize(100); // Default page size is 100 - } - - @Test - public void getOutdatedDirectComponentsTest() throws MalformedPackageURLException { - final Project project = prepareProject(); - - final Response response = target(V1_COMPONENT + "/project/" + project.getUuid()) - .queryParam("onlyOutdated", true) - .queryParam("onlyDirect", true) - .request() - .header(X_API_KEY, apiKey) - .get(Response.class); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); - assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("75"); // 75 outdated direct dependencies - - final JsonArray json = parseJsonArray(response); - assertThat(json).hasSize(75); - } - - @Test - public void getAllComponentsTest() throws MalformedPackageURLException { - final Project project = prepareProject(); - - final Response response = target(V1_COMPONENT + "/project/" + project.getUuid()) - .request() - .header(X_API_KEY, apiKey) - .get(Response.class); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); - assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("1000"); // 1000 dependencies - - final JsonArray json = parseJsonArray(response); - assertThat(json).hasSize(100); // Default page size is 100 - } - - @Test - public void getAllDirectComponentsTest() throws MalformedPackageURLException { - final Project project = prepareProject(); - - final Response response = target(V1_COMPONENT + "/project/" + project.getUuid()) - .queryParam("onlyDirect", true) - .request() - .header(X_API_KEY, apiKey) - .get(Response.class); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); - assertThat(response.getHeaderString(TOTAL_COUNT_HEADER)).isEqualTo("100"); // 100 direct dependencies - - final JsonArray json = parseJsonArray(response); - assertThat(json).hasSize(100); - } - @Test public void getComponentByUuidTest() { Project project = qm.createProject("Acme Application", null, null, null, null, null, true, false);