diff --git a/api/src/main/openapi/resources/projects/schemas/list-project-components-response-item.yaml b/api/src/main/openapi/resources/projects/schemas/list-project-components-response-item.yaml index 37e51505fc..fda8fe9873 100644 --- a/api/src/main/openapi/resources/projects/schemas/list-project-components-response-item.yaml +++ b/api/src/main/openapi/resources/projects/schemas/list-project-components-response-item.yaml @@ -41,6 +41,8 @@ properties: maxLength: 255 internal: type: boolean + direct: + type: boolean copyright: type: string maxLength: 255 diff --git a/apiserver/src/main/java/org/dependencytrack/model/Component.java b/apiserver/src/main/java/org/dependencytrack/model/Component.java index 69710679f7..867638a82c 100644 --- a/apiserver/src/main/java/org/dependencytrack/model/Component.java +++ b/apiserver/src/main/java/org/dependencytrack/model/Component.java @@ -426,6 +426,11 @@ public enum FetchGroup { @NotNull private UUID uuid; + @Persistent + @Column(name = "DIRECT", allowsNull = "true") + @JsonProperty("isDirect") + private Boolean direct; + private transient String bomRef; private transient List licenseCandidates; private transient DependencyMetrics metrics; @@ -955,6 +960,17 @@ public void setOccurrenceCount(final Long occurrenceCount) { this.occurrenceCount = occurrenceCount; } + public boolean isDirect() { + if (direct == null) { + return false; + } + return direct; + } + + public void setDirect(boolean direct) { + this.direct = direct; + } + @Override public String toString() { if (getPurl() != null) { diff --git a/apiserver/src/main/java/org/dependencytrack/model/mapping/PolicyProtoMapper.java b/apiserver/src/main/java/org/dependencytrack/model/mapping/PolicyProtoMapper.java index 0d149d29e3..4ad9386b16 100644 --- a/apiserver/src/main/java/org/dependencytrack/model/mapping/PolicyProtoMapper.java +++ b/apiserver/src/main/java/org/dependencytrack/model/mapping/PolicyProtoMapper.java @@ -61,6 +61,7 @@ public static org.dependencytrack.proto.policy.v1.Component mapToProto(final Com maybeSet(component::getPurl, purl -> protoBuilder.setPurl(purl.canonicalize())); maybeSet(component::getSwidTagId, protoBuilder::setSwidTagId); maybeSet(component::isInternal, protoBuilder::setIsInternal); + maybeSet(component::isDirect, protoBuilder::setIsDirect); maybeSet(component::getMd5, protoBuilder::setMd5); maybeSet(component::getSha1, protoBuilder::setSha1); maybeSet(component::getSha256, protoBuilder::setSha256); diff --git a/apiserver/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java b/apiserver/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java index cf9c478936..136459d527 100644 --- a/apiserver/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java +++ b/apiserver/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java @@ -211,7 +211,7 @@ AND EXISTS ( if (onlyDirect) { queryString += """ - AND "B0"."DIRECT_DEPENDENCIES" @> JSONB_BUILD_ARRAY(JSONB_BUILD_OBJECT('uuid', "A0"."UUID")) + AND "A0"."DIRECT" = TRUE """; } if (orderBy == null) { diff --git a/apiserver/src/main/java/org/dependencytrack/persistence/jdbi/ComponentDao.java b/apiserver/src/main/java/org/dependencytrack/persistence/jdbi/ComponentDao.java index 9c8e4ce3fe..4c69ac2632 100644 --- a/apiserver/src/main/java/org/dependencytrack/persistence/jdbi/ComponentDao.java +++ b/apiserver/src/main/java/org/dependencytrack/persistence/jdbi/ComponentDao.java @@ -122,12 +122,8 @@ default Page listProjectComponents(ListProjectComponentsQuery query) } if (Boolean.TRUE.equals(query.onlyDirect())) { whereConditions.add(/* language=SQL */ """ - EXISTS ( - SELECT 1 - FROM "PROJECT" - WHERE "PROJECT"."ID" = "C"."PROJECT_ID" - AND "PROJECT"."DIRECT_DEPENDENCIES" @> JSONB_BUILD_ARRAY(JSONB_BUILD_OBJECT('uuid', "C"."UUID")) - )"""); + AND "C"."DIRECT" IS TRUE + """); } if (query.searchText() != null && !query.searchText().isBlank()) { whereConditions.add(/* language=SQL */ """ @@ -235,6 +231,7 @@ default Page listProjectComponents(ListProjectComponentsQuery query) , "C"."PURL" , "C"."GROUP" , "C"."INTERNAL" + , "C"."DIRECT" , "C"."LAST_RISKSCORE" , "C"."LICENSE" AS "componentLicenseName" , "C"."LICENSE_EXPRESSION" AS "licenseExpression" @@ -630,4 +627,14 @@ public ListedComponent map(final ResultSet rs, final StatementContext ctx) throw } } + @SqlUpdate(""" + UPDATE "COMPONENT" + SET "DIRECT" = ("UUID"::TEXT IN ( + SELECT JSONB_ARRAY_ELEMENTS( + COALESCE("DIRECT_DEPENDENCIES", '[]'::jsonb)) ->> 'uuid' + FROM "PROJECT" WHERE "ID" = :projectId + )) + WHERE "PROJECT_ID" = :projectId + """) + void setDirect(@Bind Long projectId); } diff --git a/apiserver/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyComponentRowMapper.java b/apiserver/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyComponentRowMapper.java index b6748f28b8..64af3343a4 100644 --- a/apiserver/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyComponentRowMapper.java +++ b/apiserver/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyComponentRowMapper.java @@ -42,6 +42,7 @@ public Component map(final ResultSet rs, final StatementContext ctx) throws SQLE maybeSet(rs, "purl", ResultSet::getString, builder::setPurl); maybeSet(rs, "swid_tag_id", ResultSet::getString, builder::setSwidTagId); maybeSet(rs, "is_internal", ResultSet::getBoolean, builder::setIsInternal); + maybeSet(rs, "is_direct", ResultSet::getBoolean, builder::setIsDirect); maybeSet(rs, "md5", ResultSet::getString, builder::setMd5); maybeSet(rs, "sha1", ResultSet::getString, builder::setSha1); maybeSet(rs, "sha256", ResultSet::getString, builder::setSha256); diff --git a/apiserver/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyDao.java b/apiserver/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyDao.java index 7b446e3b57..d510fa17c9 100644 --- a/apiserver/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyDao.java +++ b/apiserver/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyDao.java @@ -361,14 +361,9 @@ AND EXISTS ( public boolean isDirectDependency(Component component) { return jdbiHandle .createQuery(""" - SELECT EXISTS ( - SELECT 1 - FROM "COMPONENT" AS c - INNER JOIN "PROJECT" AS p - ON p."ID" = c."PROJECT_ID" - AND p."DIRECT_DEPENDENCIES" @> JSONB_BUILD_ARRAY(JSONB_BUILD_OBJECT('uuid', :uuid)) - WHERE c."UUID" = CAST(:uuid AS UUID) - ) + SELECT COALESCE(c."DIRECT", FALSE) + FROM "COMPONENT" c + WHERE c."UUID" = CAST(:uuid AS UUID) """) .bind("uuid", component.getUuid()) .mapTo(Boolean.class) diff --git a/apiserver/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyFieldMappingRegistry.java b/apiserver/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyFieldMappingRegistry.java index 2aecc9d36b..830a80efb5 100644 --- a/apiserver/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyFieldMappingRegistry.java +++ b/apiserver/src/main/java/org/dependencytrack/policy/cel/persistence/CelPolicyFieldMappingRegistry.java @@ -39,6 +39,7 @@ public record FieldMapping(String protoFieldName, String sqlExpression) { new FieldMapping("purl", "c.\"PURL\""), new FieldMapping("swid_tag_id", "c.\"SWIDTAGID\""), new FieldMapping("is_internal", "c.\"INTERNAL\""), + new FieldMapping("is_direct", "c.\"DIRECT\""), new FieldMapping("md5", "c.\"MD5\""), new FieldMapping("sha1", "c.\"SHA1\""), new FieldMapping("sha256", "c.\"SHA_256\""), diff --git a/apiserver/src/main/java/org/dependencytrack/resources/v2/ProjectsResource.java b/apiserver/src/main/java/org/dependencytrack/resources/v2/ProjectsResource.java index e3948b3ee5..08b046393e 100644 --- a/apiserver/src/main/java/org/dependencytrack/resources/v2/ProjectsResource.java +++ b/apiserver/src/main/java/org/dependencytrack/resources/v2/ProjectsResource.java @@ -188,6 +188,7 @@ public Response listProjectComponents( .cpe(componentRow.getCpe()) .group(componentRow.getGroup()) .internal(componentRow.isInternal()) + .direct(componentRow.isDirect()) .lastInheritedRiskScore(componentRow.getLastInheritedRiskScore()) .license(componentRow.getLicense()) .licenseExpression(componentRow.getLicenseExpression()) diff --git a/apiserver/src/main/java/org/dependencytrack/tasks/ImportBomActivity.java b/apiserver/src/main/java/org/dependencytrack/tasks/ImportBomActivity.java index 1c1ca0865d..3bd4569047 100644 --- a/apiserver/src/main/java/org/dependencytrack/tasks/ImportBomActivity.java +++ b/apiserver/src/main/java/org/dependencytrack/tasks/ImportBomActivity.java @@ -44,11 +44,13 @@ import org.dependencytrack.notification.JdoNotificationEmitter; import org.dependencytrack.notification.NotificationModelConverter; import org.dependencytrack.persistence.QueryManager; +import org.dependencytrack.persistence.jdbi.ComponentDao; import org.dependencytrack.pkgmetadata.ResolvePackageMetadataWorkflow; import org.dependencytrack.proto.internal.workflow.v1.AnalyzeProjectWorkflowArg; import org.dependencytrack.proto.internal.workflow.v1.ImportBomArg; import org.dependencytrack.proto.internal.workflow.v1.VulnAnalysisWorkflowContext; import org.dependencytrack.util.InternalComponentIdentifier; +import org.jdbi.v3.core.Handle; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.slf4j.Logger; @@ -102,6 +104,7 @@ import static org.dependencytrack.parser.cyclonedx.util.ModelConverter.convertToProject; import static org.dependencytrack.parser.cyclonedx.util.ModelConverter.convertToProjectMetadata; import static org.dependencytrack.parser.cyclonedx.util.ModelConverter.flatten; +import static org.dependencytrack.persistence.jdbi.JdbiFactory.createLocalJdbi; import static org.dependencytrack.proto.internal.workflow.v1.AnalysisTrigger.ANALYSIS_TRIGGER_BOM_UPLOAD; import static org.dependencytrack.util.PersistenceUtil.applyIfChanged; import static org.dependencytrack.util.PersistenceUtil.assertPersistent; @@ -698,6 +701,10 @@ private void processDependencyGraph( } qm.getPersistenceManager().flush(); + + try (final Handle jdbiHandle = createLocalJdbi(qm).open()) { + jdbiHandle.attach(ComponentDao.class).setDirect(project.getId()); + } } private static void recordBomImport(final ProcessingContext ctx, final QueryManager qm, final Project project) { diff --git a/apiserver/src/test/java/org/dependencytrack/persistence/ComponentQueryManangerPostgresTest.java b/apiserver/src/test/java/org/dependencytrack/persistence/ComponentQueryManangerPostgresTest.java index 8caa0d7e46..79f9854368 100644 --- a/apiserver/src/test/java/org/dependencytrack/persistence/ComponentQueryManangerPostgresTest.java +++ b/apiserver/src/test/java/org/dependencytrack/persistence/ComponentQueryManangerPostgresTest.java @@ -62,7 +62,7 @@ public void testGetOutdatedComponents() throws Exception { } @Test - public void testGetDirectComponents() throws Exception { + public void testIsDirectComponents() throws Exception { final Project project = prepareProject(); var components = qm.getComponents(project, false, false, true); @@ -94,7 +94,6 @@ public void getUngroupedOutdatedDirectComponentsTest() throws Exception { private Project prepareProject() throws Exception { final Project project = qm.createProject("Acme Application", null, null, null, null, null, null, false); - final List directDepencencies = new ArrayList<>(); final List metadataList = new ArrayList<>(); final List artifactMetadataList = new ArrayList<>(); // Generate 1000 dependencies @@ -114,7 +113,7 @@ private Project prepareProject() throws Exception { // direct depencencies if (i < 100) { // 100 direct depencencies, 900 transitive depencencies - directDepencencies.add("{\"uuid\":\"" + component.getUuid() + "\"}"); + component.setDirect(true); } // Recent & Outdated if ((i >= 25) && (i < 225)) { @@ -151,7 +150,6 @@ private Project prepareProject() throws Exception { new PackageMetadataDao(handle).upsertAll(metadataList); new PackageArtifactMetadataDao(handle).upsertAll(artifactMetadataList); }); - project.setDirectDependencies("[" + String.join(",", directDepencencies.toArray(new String[0])) + "]"); return project; } @@ -270,7 +268,6 @@ public void testMappingComponentProjectionWithAllFields() throws Exception { */ private Project prepareProjectUngroupedComponents() throws Exception { final Project project = qm.createProject("Ungrouped Application", null, null, null, null, null, null, false); - final List directDepencencies = new ArrayList<>(); final List metadataList = new ArrayList<>(); final List artifactMetadataList = new ArrayList<>(); // Generate 10 dependencies @@ -284,7 +281,7 @@ private Project prepareProjectUngroupedComponents() throws Exception { // direct depencencies if (i < 4) { // 4 direct depencencies, 6 transitive depencencies - directDepencencies.add("{\"uuid\":\"" + component.getUuid() + "\"}"); + component.setDirect(true); } // Recent & Outdated if ((i < 7)) { @@ -313,7 +310,6 @@ private Project prepareProjectUngroupedComponents() throws Exception { new PackageMetadataDao(handle).upsertAll(metadataList); new PackageArtifactMetadataDao(handle).upsertAll(artifactMetadataList); }); - project.setDirectDependencies("[" + String.join(",", directDepencencies.toArray(new String[0])) + "]"); return project; } } diff --git a/apiserver/src/test/java/org/dependencytrack/persistence/jdbi/ComponentDaoTest.java b/apiserver/src/test/java/org/dependencytrack/persistence/jdbi/ComponentDaoTest.java index bd4dad748b..32025b5a07 100644 --- a/apiserver/src/test/java/org/dependencytrack/persistence/jdbi/ComponentDaoTest.java +++ b/apiserver/src/test/java/org/dependencytrack/persistence/jdbi/ComponentDaoTest.java @@ -172,4 +172,28 @@ public void testGetComponentId() { qm.persist(component); assertThat(componentDao.getComponentId(component.getUuid())).isEqualTo(component.getId()); } + + @Test + public void testSettingIsDirectFlag() { + final var project = qm.createProject("acme-app", "Description 1", "1.0.0", null, null, null, null, false); + final var componentA = new Component(); + componentA.setName("acme-lib-a"); + componentA.setVersion("1.0.0"); + componentA.setProject(project); + qm.persist(componentA); + final var componentB = new Component(); + componentB.setName("acme-lib-b"); + componentB.setVersion("2.0.0"); + componentB.setProject(project); + qm.persist(componentB); + project.setDirectDependencies("[" + String.join(",", "{\"uuid\":\"" + componentB.getUuid() + "\"}]")); + + componentDao.setDirect(project.getId()); + + qm.getPersistenceManager().refresh(componentA); + qm.getPersistenceManager().refresh(componentB); + + assertThat(componentA.isDirect()).isFalse(); + assertThat(componentB.isDirect()).isTrue(); + } } \ No newline at end of file diff --git a/apiserver/src/test/java/org/dependencytrack/policy/cel/CelPolicyEngineTest.java b/apiserver/src/test/java/org/dependencytrack/policy/cel/CelPolicyEngineTest.java index 5b3b488ee7..9eed2769e5 100644 --- a/apiserver/src/test/java/org/dependencytrack/policy/cel/CelPolicyEngineTest.java +++ b/apiserver/src/test/java/org/dependencytrack/policy/cel/CelPolicyEngineTest.java @@ -500,9 +500,8 @@ void testEvaluateProjectWithPolicyOperatorForVersionDistance() throws Exception component.setName("bar"); component.setPurl("pkg:maven/foo/bar@1.0.0"); component.setVersion("1.2.3"); + component.setDirect(true); qm.persist(component); - - project.setDirectDependencies("[{\"uuid\":\"" + component.getUuid() + "\"}]"); qm.persist(project); new CelPolicyEngine().evaluateProject(project.getUuid()); @@ -1277,11 +1276,13 @@ void testEvaluateProjectWithFuncComponentIsDependencyOfExclusiveComponentWithMul final var componentA = new Component(); componentA.setProject(project); componentA.setName("acme-lib-a"); + componentA.setDirect(true); qm.persist(componentA); final var componentB = new Component(); componentB.setProject(project); componentB.setName("acme-lib-b"); + componentB.setDirect(true); qm.persist(componentB); final var componentC = new Component(); @@ -1297,10 +1298,6 @@ void testEvaluateProjectWithFuncComponentIsDependencyOfExclusiveComponentWithMul // /-> A -------\ // * > C // \-> B -> D --/ - project.setDirectDependencies("[%s, %s]".formatted( - new ComponentIdentity(componentA).toJSON(), - new ComponentIdentity(componentB).toJSON()) - ); componentA.setDirectDependencies("[%s]".formatted(new ComponentIdentity(componentC).toJSON())); componentB.setDirectDependencies("[%s]".formatted(new ComponentIdentity(componentD).toJSON())); componentD.setDirectDependencies("[%s]".formatted(new ComponentIdentity(componentC).toJSON())); @@ -1710,20 +1707,18 @@ void testEvaluateProjectWithFuncComponentIsDependencyOfExclusiveComponentWithMul final var componentA = new Component(); componentA.setProject(project); componentA.setName("acme-lib-a"); + componentA.setDirect(true); qm.persist(componentA); final var componentB = new Component(); componentB.setProject(project); componentB.setName("acme-lib-b"); + componentB.setDirect(true); qm.persist(componentB); // /-> A -> B // * ^ // \-------/ - project.setDirectDependencies("[%s, %s]".formatted( - new ComponentIdentity(componentA).toJSON(), - new ComponentIdentity(componentB).toJSON() - )); componentA.setDirectDependencies("[%s]".formatted(new ComponentIdentity(componentB).toJSON())); qm.persist(project); qm.persist(componentA); diff --git a/apiserver/src/test/java/org/dependencytrack/policy/cel/compat/VersionDistanceCelPolicyEvaluatorTest.java b/apiserver/src/test/java/org/dependencytrack/policy/cel/compat/VersionDistanceCelPolicyEvaluatorTest.java index a138ed0580..1c653b6754 100644 --- a/apiserver/src/test/java/org/dependencytrack/policy/cel/compat/VersionDistanceCelPolicyEvaluatorTest.java +++ b/apiserver/src/test/java/org/dependencytrack/policy/cel/compat/VersionDistanceCelPolicyEvaluatorTest.java @@ -156,9 +156,8 @@ public void evaluateTest(final String version, String latestVersion, Operator op component.setName("bar"); component.setPurl(componentPurl); component.setVersion(version); + component.setDirect(true); qm.persist(component); - - project.setDirectDependencies("[{\"uuid\":\"" + component.getUuid() + "\"}]"); qm.persist(project); new CelPolicyEngine().evaluateProject(project.getUuid()); @@ -172,8 +171,8 @@ public void evaluateTest(final String version, String latestVersion, Operator op } // https://github.com/DependencyTrack/dependency-track/issues/3295 - project.setDirectDependencies(null); - qm.persist(project); + component.setDirect(false); + qm.persist(component); new CelPolicyEngine().evaluateProject(project.getUuid()); assertThat(qm.getAllPolicyViolations(component)).isEmpty(); } diff --git a/apiserver/src/test/java/org/dependencytrack/policy/cel/persistence/CelPolicyDaoTest.java b/apiserver/src/test/java/org/dependencytrack/policy/cel/persistence/CelPolicyDaoTest.java index 4996398dd7..b377eb60b9 100644 --- a/apiserver/src/test/java/org/dependencytrack/policy/cel/persistence/CelPolicyDaoTest.java +++ b/apiserver/src/test/java/org/dependencytrack/policy/cel/persistence/CelPolicyDaoTest.java @@ -235,6 +235,7 @@ public void testLoadRequiredFieldsForComponent() throws Exception { "purl": "pkg:maven/componentGroup/componentName@componentVersion", "swidTagId": "componentSwidTagId", "isInternal": true, + "isDirect": false, "md5": "componentmd5", "sha1": "componentsha1", "sha256": "componentsha256", diff --git a/apiserver/src/test/java/org/dependencytrack/resources/v1/ComponentResourceTest.java b/apiserver/src/test/java/org/dependencytrack/resources/v1/ComponentResourceTest.java index 65b0d6a5e0..762036ca63 100644 --- a/apiserver/src/test/java/org/dependencytrack/resources/v1/ComponentResourceTest.java +++ b/apiserver/src/test/java/org/dependencytrack/resources/v1/ComponentResourceTest.java @@ -1347,6 +1347,7 @@ public void getAllComponentsTest() throws Exception { }, "expandDependencyGraph": false, "isInternal": false, + "isDirect": false, "occurrenceCount": 0 } """); @@ -1510,6 +1511,7 @@ private Project prepareProject() throws Exception { component.setPurl(new PackageURL(RepositoryType.MAVEN.toString(), "component-group", "component-name-" + i, i + ".0", null, null)); component = qm.createComponent(component, false); if (i < 10) { + component.setDirect(true); directDependencies.add("{\"uuid\":\"" + component.getUuid() + "\"}"); } if ((i >= 5) && (i < 15)) { diff --git a/apiserver/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java b/apiserver/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java index 82c8298560..13d9718532 100644 --- a/apiserver/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java +++ b/apiserver/src/test/java/org/dependencytrack/resources/v1/VulnerabilityResourceTest.java @@ -274,6 +274,7 @@ public void getVulnerabilitiesByProjectTest() { "uuid": "${json-unit.any-string}", "expandDependencyGraph": false, "isInternal": false, + "isDirect": false, "externalReferences":[] } ], @@ -303,6 +304,7 @@ public void getVulnerabilitiesByProjectTest() { "uuid": "${json-unit.any-string}", "expandDependencyGraph": false, "isInternal": false, + "isDirect": false, "externalReferences":[] } ], @@ -336,6 +338,7 @@ public void getVulnerabilitiesByProjectTest() { "uuid": "${json-unit.any-string}", "expandDependencyGraph": false, "isInternal": false, + "isDirect": false, "externalReferences":[] } ], @@ -359,6 +362,7 @@ public void getVulnerabilitiesByProjectTest() { "uuid": "${json-unit.any-string}", "expandDependencyGraph": false, "isInternal": false, + "isDirect": false, "externalReferences":[] } ], @@ -447,6 +451,7 @@ public void getVulnerabilitiesByProjectIncludeProjectSuppressedTest() { "uuid": "${json-unit.any-string}", "expandDependencyGraph": false, "isInternal": false, + "isDirect": false, "externalReferences":[] } ], @@ -470,6 +475,7 @@ public void getVulnerabilitiesByProjectIncludeProjectSuppressedTest() { "uuid": "${json-unit.any-string}", "expandDependencyGraph": false, "isInternal": false, + "isDirect": false, "externalReferences":[] } ], diff --git a/apiserver/src/test/java/org/dependencytrack/resources/v2/ComponentsResourceTest.java b/apiserver/src/test/java/org/dependencytrack/resources/v2/ComponentsResourceTest.java index 59e7035f77..d3fd8e9093 100644 --- a/apiserver/src/test/java/org/dependencytrack/resources/v2/ComponentsResourceTest.java +++ b/apiserver/src/test/java/org/dependencytrack/resources/v2/ComponentsResourceTest.java @@ -105,7 +105,8 @@ public void createComponentTest() { "uuid" : "${json-unit.any-string}", "expandDependencyGraph" : false, "occurrenceCount" : 0, - "isInternal" : false + "isInternal" : false, + "isDirect" : false } ] } """); diff --git a/apiserver/src/test/java/org/dependencytrack/resources/v2/ProjectsResourceTest.java b/apiserver/src/test/java/org/dependencytrack/resources/v2/ProjectsResourceTest.java index 2f54e52b61..d501e716b8 100644 --- a/apiserver/src/test/java/org/dependencytrack/resources/v2/ProjectsResourceTest.java +++ b/apiserver/src/test/java/org/dependencytrack/resources/v2/ProjectsResourceTest.java @@ -77,6 +77,7 @@ public void listProjectComponents() { "group" : "component-group", "purl" : "pkg:maven/foo/bar@1.0", "internal" : false, + "direct": false, "hashes": { "md5": "hash-md5" }, @@ -87,6 +88,7 @@ public void listProjectComponents() { "group" : "component-group", "purl" : "pkg:maven/foo/bar@2.0", "internal" : false, + "direct": false, "scope": "REQUIRED", "resolved_license" : { "name" : "MIT License", @@ -124,6 +126,7 @@ public void listProjectComponents() { "group" : "component-group", "purl" : "pkg:maven/foo/bar@3.0", "internal" : false, + "direct": true, "uuid" : "${json-unit.any-string}" } ], @@ -718,6 +721,7 @@ private Project prepareProject() { component.setGroup("component-group"); component.setName("component-name"); component.setVersion("3.0"); + component.setDirect(true); component.setPurl("pkg:maven/foo/bar@3.0"); qm.createComponent(component, false); diff --git a/migration/src/main/resources/org/dependencytrack/migration/V202605291156__add_direct_column_to_component.sql b/migration/src/main/resources/org/dependencytrack/migration/V202605291156__add_direct_column_to_component.sql new file mode 100644 index 0000000000..e918875bde --- /dev/null +++ b/migration/src/main/resources/org/dependencytrack/migration/V202605291156__add_direct_column_to_component.sql @@ -0,0 +1,5 @@ +ALTER TABLE "COMPONENT" + ADD COLUMN "DIRECT" BOOLEAN; + +CREATE INDEX "COMPONENT_DIRECT_IDX" + ON "COMPONENT" ("DIRECT"); \ No newline at end of file diff --git a/proto/src/main/proto/org/dependencytrack/policy/v1/policy.proto b/proto/src/main/proto/org/dependencytrack/policy/v1/policy.proto index 62ad840988..ce12bea075 100644 --- a/proto/src/main/proto/org/dependencytrack/policy/v1/policy.proto +++ b/proto/src/main/proto/org/dependencytrack/policy/v1/policy.proto @@ -69,6 +69,8 @@ message Component { // When the component current version last modified. optional google.protobuf.Timestamp published_at = 53; optional string latest_version = 54; + // Whether the component is direct dependency of the project. + optional bool is_direct = 55; message Property { string group = 1;