Skip to content

Commit

Permalink
Fix directDependencies missing from /api/v1/project/{uuid} respon…
Browse files Browse the repository at this point in the history
…se when not already cached

Since DependencyTrack#4049, the endpoint returns only properties that are part of the `ALL` fetch group. `Project#directDependencies` was missing from this fetch group, and thus was not returned by the API, **unless the project was previously created or updated**.

The API ended up returning the `directDependencies` field after modification of the project, which happened because `directDependencies` was in the L2 cache at that point.

Besides fixing the issue at hand, this change also extends the existing endpoint test to ensure that **all** fields are returned as expected.

Signed-off-by: nscuro <nscuro@protonmail.com>
  • Loading branch information
nscuro committed Aug 14, 2024
1 parent e999f8c commit fc4498a
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 12 deletions.
8 changes: 7 additions & 1 deletion src/main/java/org/dependencytrack/model/Project.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@
@PersistenceCapable
@FetchGroups({
@FetchGroup(name = "ALL", members = {
@Persistent(name = "name"),
@Persistent(name = "author"),
@Persistent(name = "publisher"),
@Persistent(name = "manufacturer"),
@Persistent(name = "supplier"),
@Persistent(name = "group"),
@Persistent(name = "name"),
Expand All @@ -85,12 +85,18 @@
@Persistent(name = "cpe"),
@Persistent(name = "purl"),
@Persistent(name = "swidTagId"),
@Persistent(name = "directDependencies"),
@Persistent(name = "uuid"),
@Persistent(name = "parent"),
@Persistent(name = "children"),
@Persistent(name = "properties"),
@Persistent(name = "tags"),
@Persistent(name = "lastBomImport"),
@Persistent(name = "lastBomImportFormat"),
@Persistent(name = "lastInheritedRiskScore"),
@Persistent(name = "active"),
@Persistent(name = "accessTeams"),
@Persistent(name = "externalReferences"),
@Persistent(name = "metadata")
}),
@FetchGroup(name = "METADATA", members = {
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/dependencytrack/model/ProjectMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

import javax.jdo.annotations.Column;
import javax.jdo.annotations.Convert;
import javax.jdo.annotations.FetchGroup;
import javax.jdo.annotations.FetchGroups;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
Expand All @@ -41,6 +43,12 @@
*
* @since 4.10.0
*/
@FetchGroups(value = {
@FetchGroup(name = "ALL", members = {
@Persistent(name = "supplier"),
@Persistent(name = "authors")
})
})
@PersistenceCapable(table = "PROJECT_METADATA")
@JsonInclude(Include.NON_NULL)
public class ProjectMetadata {
Expand Down
217 changes: 206 additions & 11 deletions src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
import org.dependencytrack.model.AnalysisJustification;
import org.dependencytrack.model.AnalysisResponse;
import org.dependencytrack.model.AnalysisState;
import org.dependencytrack.model.Classifier;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.ExternalReference;
import org.dependencytrack.model.OrganizationalContact;
import org.dependencytrack.model.OrganizationalEntity;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.ProjectMetadata;
import org.dependencytrack.model.ProjectMetrics;
import org.dependencytrack.model.ProjectProperty;
import org.dependencytrack.model.ServiceComponent;
import org.dependencytrack.model.Tag;
Expand All @@ -62,6 +64,7 @@
import javax.ws.rs.core.Response;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -255,19 +258,211 @@ public void getProjectsDescOrderedRequestTest() {

@Test
public void getProjectByUuidTest() {
Project project = qm.createProject("ABC", null, "1.0", null, null, null, true, false);
Response response = jersey.target(V1_PROJECT + "/" + project.getUuid())
final var parentProject = new Project();
parentProject.setName("parent");
parentProject.setVersion("1.2.3");
qm.persist(parentProject);

final var manufacturer = new OrganizationalEntity();
manufacturer.setName("manufacturer");

final var supplier = new OrganizationalEntity();
supplier.setName("supplier");

final var property = new ProjectProperty();
property.setGroupName("groupName");
property.setPropertyName("propertyName");
property.setPropertyValue("propertyValue");
property.setPropertyType(PropertyType.STRING);

final var externalRef = new ExternalReference();
externalRef.setUrl("https://example.com");
externalRef.setType(Type.WEBSITE);

final var project = new Project();
project.setAuthor("author");
project.setPublisher("publisher");
project.setManufacturer(manufacturer);
project.setSupplier(supplier);
project.setGroup("group");
project.setName("name");
project.setVersion("1.0.0");
project.setClassifier(Classifier.LIBRARY);
project.setDescription("description");
project.setDirectDependencies("[{\"uuid\":\"c162be63-35f0-4059-b28b-327e6a01390a\"}]");
project.setCpe("cpe:2.3:*:vendor:product:1.0.0:update:edition:lang:swEdition:targetSw:targetHw:other");
project.setPurl("pkg:maven/namespace/name@1.0.0");
project.setSwidTagId("swidTagId");
project.setParent(parentProject);
project.setProperties(List.of(property));
project.setLastBomImport(new Date(1643767322000L));
project.setLastBomImportFormat("lastBomImportFormat");
project.setLastInheritedRiskScore(66.6);
project.setActive(false);
project.setExternalReferences(List.of(externalRef));
qm.persist(project);

qm.bind(project, List.of(qm.createTag("tag-1")));

final var metadataAuthor = new OrganizationalContact();
metadataAuthor.setName("metadataAuthor");
final var metadataSupplier = new OrganizationalEntity();
metadataSupplier.setName("metadataSupplier");
final var metadata = new ProjectMetadata();
metadata.setAuthors(List.of(metadataAuthor));
metadata.setSupplier(metadataSupplier);
metadata.setProject(project);
qm.persist(metadata);

final var metrics = new ProjectMetrics();
metrics.setProject(project);
metrics.setCritical(6);
metrics.setHigh(6);
metrics.setMedium(6);
metrics.setLow(6);
metrics.setUnassigned(6);
metrics.setVulnerabilities(6);
metrics.setVulnerableComponents(6);
metrics.setComponents(6);
metrics.setFindingsTotal(6);
metrics.setFindingsAudited(6);
metrics.setFindingsUnaudited(6);
metrics.setInheritedRiskScore(66.6);
metrics.setSuppressed(6);
metrics.setPolicyViolationsTotal(6);
metrics.setPolicyViolationsFail(6);
metrics.setPolicyViolationsInfo(6);
metrics.setPolicyViolationsWarn(6);
metrics.setPolicyViolationsAudited(6);
metrics.setPolicyViolationsUnaudited(6);
metrics.setPolicyViolationsLicenseAudited(6);
metrics.setPolicyViolationsLicenseTotal(6);
metrics.setPolicyViolationsLicenseUnaudited(6);
metrics.setPolicyViolationsOperationalAudited(6);
metrics.setPolicyViolationsOperationalTotal(6);
metrics.setPolicyViolationsOperationalUnaudited(6);
metrics.setPolicyViolationsSecurityAudited(6);
metrics.setPolicyViolationsSecurityTotal(6);
metrics.setPolicyViolationsSecurityUnaudited(6);
metrics.setFirstOccurrence(new Date(1677812583000L));
metrics.setLastOccurrence(new Date(1677812583000L));
qm.persist(metrics);

final UUID parentProjectUuid = parentProject.getUuid();
final UUID projectUuid = project.getUuid();

// Nuke L1 and L2 cache and close PM to ensure all changes are flushed.
qm.getPersistenceManager().getPersistenceManagerFactory().getDataStoreCache().evictAll();
qm.getPersistenceManager().evictAll();
qm.close();

final Response response = jersey.target(V1_PROJECT + "/" + projectUuid)
.request()
.header(X_API_KEY, apiKey)
.get(Response.class);
Assert.assertEquals(200, response.getStatus(), 0);
Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER));
JsonObject json = parseJsonObject(response);
Assert.assertNotNull(json);
Assert.assertEquals("ABC", json.getString("name"));
Assert.assertEquals(1, json.getJsonArray("versions").size());
Assert.assertEquals(project.getUuid().toString(), json.getJsonArray("versions").getJsonObject(0).getJsonString("uuid").getString());
Assert.assertEquals("1.0", json.getJsonArray("versions").getJsonObject(0).getJsonString("version").getString());
.get();
assertThat(response.getStatus()).isEqualTo(200);
assertThatJson(getPlainTextBody(response))
.withMatcher("parentProjectUuid", equalTo(parentProjectUuid.toString()))
.withMatcher("projectUuid", equalTo(projectUuid.toString()))
.isEqualTo(/* language=JSON */ """
{
"active": false,
"author": "author",
"children": [],
"classifier": "LIBRARY",
"cpe": "cpe:2.3:*:vendor:product:1.0.0:update:edition:lang:swEdition:targetSw:targetHw:other",
"description": "description",
"directDependencies": "[{\\"uuid\\":\\"c162be63-35f0-4059-b28b-327e6a01390a\\"}]",
"externalReferences": [
{
"type": "website",
"url": "https://example.com"
}
],
"group": "group",
"lastBomImport": 1643767322000,
"lastBomImportFormat": "lastBomImportFormat",
"lastInheritedRiskScore": 66.6,
"manufacturer": {
"name": "manufacturer"
},
"metadata": {
"supplier": {
"name": "metadataSupplier"
},
"authors": [
{
"name":"metadataAuthor"
}
]
},
"metrics":{
"critical": 6,
"high": 6,
"medium": 6,
"low": 6,
"unassigned": 6,
"vulnerabilities": 6,
"vulnerableComponents":6,
"components": 6,
"suppressed": 6,
"findingsTotal": 6,
"findingsAudited": 6,
"findingsUnaudited": 6,
"inheritedRiskScore": 66.6,
"policyViolationsFail": 6,
"policyViolationsWarn": 6,
"policyViolationsInfo": 6,
"policyViolationsTotal": 6,
"policyViolationsAudited": 6,
"policyViolationsUnaudited": 6,
"policyViolationsSecurityTotal": 6,
"policyViolationsSecurityAudited": 6,
"policyViolationsSecurityUnaudited": 6,
"policyViolationsLicenseTotal": 6,
"policyViolationsLicenseAudited": 6,
"policyViolationsLicenseUnaudited": 6,
"policyViolationsOperationalTotal": 6,
"policyViolationsOperationalAudited": 6,
"policyViolationsOperationalUnaudited": 6,
"firstOccurrence": 1677812583000,
"lastOccurrence": 1677812583000
},
"name": "name",
"parent": {
"name": "parent",
"uuid": "${json-unit.matches:parentProjectUuid}",
"version": "1.2.3"
},
"properties": [
{
"groupName": "groupName",
"propertyName": "propertyName",
"propertyType": "STRING",
"propertyValue": "propertyValue"
}
],
"publisher": "publisher",
"purl": "pkg:maven/namespace/name@1.0.0",
"supplier": {
"name": "supplier"
},
"swidTagId": "swidTagId",
"tags": [
{
"name": "tag-1"
}
],
"uuid": "${json-unit.matches:projectUuid}",
"version": "1.0.0",
"versions": [
{
"uuid": "${json-unit.matches:projectUuid}",
"version": "1.0.0"
}
]
}
""");
}

@Test
Expand Down

0 comments on commit fc4498a

Please sign in to comment.