Skip to content

Commit f2ac75e

Browse files
committed
refactor: add optional "parent" param to resources
Signed-off-by: Jonathan Howard <jonathan.w.howard@lmco.com>
1 parent a1f8e27 commit f2ac75e

File tree

6 files changed

+122
-73
lines changed

6 files changed

+122
-73
lines changed

src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,23 @@ public Project getProject(final String name, final String version, final UUID pa
303303
*/
304304
@Override
305305
public Project getLatestProjectVersion(final String name) {
306+
return getLatestProjectVersion(name, null);
307+
}
308+
309+
/**
310+
* Returns the latest version of a project by its name.
311+
*
312+
* @param name the name of the Project (required)
313+
* @param parentUuid UUID of the parent {@link Project}
314+
* @return a Project object representing the latest version, or null if not found
315+
*/
316+
@Override
317+
public Project getLatestProjectVersion(final String name, final UUID parentUuid) {
306318
final Query<Project> query = pm.newQuery(Project.class);
307319

308320
final var filterBuilder = new ProjectQueryFilterBuilder()
309321
.withName(name)
322+
.withParent(parentUuid)
310323
.onlyLatestVersion();
311324

312325
final String queryFilter = filterBuilder.buildFilter();
@@ -1348,7 +1361,7 @@ private List<ProjectVersion> getProjectVersions(Project project) {
13481361
final ProjectQueryFilterBuilder filterBuilder = new ProjectQueryFilterBuilder()
13491362
.withName(project.getName())
13501363
.withParent(project.getParent() != null ? project.getParent().getUuid() : null);
1351-
1364+
13521365
final Query<Project> query = pm.newQuery(Project.class)
13531366
.filter(filterBuilder.buildFilter())
13541367
.setNamedParameters(filterBuilder.getParams())

src/main/java/org/dependencytrack/persistence/QueryManager.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,10 @@ public Project getLatestProjectVersion(final String name) {
549549
return getProjectQueryManager().getLatestProjectVersion(name);
550550
}
551551

552+
public Project getLatestProjectVersion(final String name, final UUID parentUuid) {
553+
return getProjectQueryManager().getLatestProjectVersion(name, parentUuid);
554+
}
555+
552556
public PaginatedResult getProjectsWithoutDescendantsOf(final boolean excludeInactive, final Project project) {
553557
return getProjectQueryManager().getProjectsWithoutDescendantsOf(excludeInactive, project);
554558
}

src/main/java/org/dependencytrack/resources/v1/BadgeResource.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,11 @@
5252
import jakarta.ws.rs.Path;
5353
import jakarta.ws.rs.PathParam;
5454
import jakarta.ws.rs.Produces;
55+
import jakarta.ws.rs.QueryParam;
5556
import jakarta.ws.rs.core.Response;
5657
import javax.naming.AuthenticationException;
5758
import java.security.Principal;
59+
import java.util.UUID;
5860

5961
import static org.dependencytrack.model.ConfigPropertyConstants.GENERAL_BADGE_ENABLED;
6062

@@ -231,7 +233,9 @@ public Response getProjectVulnerabilitiesBadge(
231233
@Parameter(description = "The name of the project to query on", required = true)
232234
@PathParam("name") String name,
233235
@Parameter(description = "The version of the project to query on", required = true)
234-
@PathParam("version") String version) {
236+
@PathParam("version") String version,
237+
@Parameter(description = "The UUID of the parent project to query on", required = false)
238+
@QueryParam("parent") @ValidUuid String parentUuid) {
235239
try (QueryManager qm = new QueryManager()) {
236240
final boolean shouldBypassAuth = qm.isEnabled(GENERAL_BADGE_ENABLED);
237241
if (!shouldBypassAuth && !passesAuthentication()) {
@@ -240,7 +244,8 @@ public Response getProjectVulnerabilitiesBadge(
240244
if (!shouldBypassAuth && !passesAuthorization(qm)) {
241245
return Response.status(Response.Status.FORBIDDEN).build();
242246
}
243-
final Project project = qm.getProject(name, version);
247+
final UUID uuid = parentUuid != null ? UUID.fromString(parentUuid) : null;
248+
final Project project = qm.getProject(name, version, uuid);
244249
if (project != null) {
245250
if (!shouldBypassAuth) {
246251
requireAccess(qm, project);
@@ -325,7 +330,9 @@ public Response getProjectPolicyViolationsBadge(
325330
@Parameter(description = "The name of the project to query on", required = true)
326331
@PathParam("name") String name,
327332
@Parameter(description = "The version of the project to query on", required = true)
328-
@PathParam("version") String version) {
333+
@PathParam("version") String version,
334+
@Parameter(description = "The UUID of the parent project to query on", required = false)
335+
@QueryParam("parent") @ValidUuid String parentUuid) {
329336
try (QueryManager qm = new QueryManager()) {
330337
final boolean shouldBypassAuth = qm.isEnabled(GENERAL_BADGE_ENABLED);
331338
if (!shouldBypassAuth && !passesAuthentication()) {
@@ -334,7 +341,8 @@ public Response getProjectPolicyViolationsBadge(
334341
if (!shouldBypassAuth && !passesAuthorization(qm)) {
335342
return Response.status(Response.Status.FORBIDDEN).build();
336343
}
337-
final Project project = qm.getProject(name, version);
344+
final UUID uuid = parentUuid != null ? UUID.fromString(parentUuid) : null;
345+
final Project project = qm.getProject(name, version, uuid);
338346
if (project != null) {
339347
if (!shouldBypassAuth) {
340348
requireAccess(qm, project);

src/main/java/org/dependencytrack/resources/v1/BomResource.java

Lines changed: 76 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@
9191
import java.util.Arrays;
9292
import java.util.Base64;
9393
import java.util.List;
94+
import java.util.Objects;
9495
import java.util.Set;
96+
import java.util.UUID;
97+
import java.util.stream.Stream;
9598

9699
import static java.util.function.Predicate.not;
97100
import static org.dependencytrack.model.ConfigPropertyConstants.BOM_VALIDATION_MODE;
@@ -308,46 +311,48 @@ public Response uploadBom(@Parameter(required = true) BomSubmitRequest request)
308311
validator.validateProperty(request, "bom")
309312
);
310313
try (QueryManager qm = new QueryManager()) {
311-
Project project = qm.getProject(request.getProjectName(), request.getProjectVersion());
314+
Project parent = null;
315+
UUID parentUuid = null;
316+
if (request.getParentUUID() != null) {
317+
failOnValidationError(validator.validateProperty(request, "parentUUID"));
318+
parent = qm.getProject(request.getParentUUID());
319+
parentUuid = parent.getUuid();
320+
}
321+
322+
Project project = qm.getProject(request.getProjectName(), request.getProjectVersion(), parentUuid);
312323
if (project == null && request.isAutoCreate()) {
313-
if (hasPermission(Permissions.Constants.PORTFOLIO_MANAGEMENT) || hasPermission(Permissions.Constants.PORTFOLIO_MANAGEMENT_CREATE) || hasPermission(Permissions.Constants.PROJECT_CREATION_UPLOAD)) {
314-
Project parent = null;
315-
if (request.getParentUUID() != null || request.getParentName() != null) {
316-
if (request.getParentUUID() != null) {
317-
failOnValidationError(validator.validateProperty(request, "parentUUID"));
318-
parent = qm.getObjectByUuid(Project.class, request.getParentUUID());
319-
} else {
320-
failOnValidationError(
321-
validator.validateProperty(request, "parentName"),
322-
validator.validateProperty(request, "parentVersion")
323-
);
324-
final String trimmedParentName = StringUtils.trimToNull(request.getParentName());
325-
final String trimmedParentVersion = StringUtils.trimToNull(request.getParentVersion());
326-
parent = qm.getProject(trimmedParentName, trimmedParentVersion);
327-
}
328-
329-
if (parent == null) { // if parent project is specified but not found
330-
return Response.status(Response.Status.NOT_FOUND).entity("The parent project could not be found.").build();
331-
}
332-
requireAccess(qm, parent, "Access to the specified parent project is forbidden");
333-
}
334-
final String trimmedProjectName = StringUtils.trimToNull(request.getProjectName());
335-
if (request.isLatestProjectVersion()) {
336-
final Project oldLatest = qm.getLatestProjectVersion(trimmedProjectName);
337-
if(oldLatest != null) {
338-
requireAccess(qm, oldLatest, "Access to the previous latest project version is forbidden");
339-
}
340-
}
341-
project = qm.createProject(trimmedProjectName, null,
342-
StringUtils.trimToNull(request.getProjectVersion()), request.getProjectTags(), parent,
343-
null, null, request.isLatestProjectVersion(), true);
344-
Principal principal = getPrincipal();
345-
qm.updateNewProjectACL(project, principal);
346-
} else {
324+
if (Stream.of(Permissions.Constants.PORTFOLIO_MANAGEMENT, Permissions.Constants.PORTFOLIO_MANAGEMENT_CREATE, Permissions.Constants.PROJECT_CREATION_UPLOAD).noneMatch(this::hasPermission)) {
347325
return Response.status(Response.Status.UNAUTHORIZED).entity("The principal does not have permission to create project.").build();
348326
}
327+
328+
if (parent == null) {
329+
failOnValidationError(
330+
validator.validateProperty(request, "parentName"),
331+
validator.validateProperty(request, "parentVersion"));
332+
final String trimmedParentName = StringUtils.trimToNull(request.getParentName());
333+
final String trimmedParentVersion = StringUtils.trimToNull(request.getParentVersion());
334+
parent = qm.getProject(trimmedParentName, trimmedParentVersion, parentUuid);
335+
}
336+
337+
Objects.requireNonNull(parent);
338+
requireAccess(qm, parent, "Access to the specified parent project is forbidden");
339+
340+
final String trimmedProjectName = StringUtils.trimToNull(request.getProjectName());
341+
if (request.isLatestProjectVersion()) {
342+
final Project oldLatest = qm.getLatestProjectVersion(trimmedProjectName, parentUuid);
343+
if(oldLatest != null) {
344+
requireAccess(qm, oldLatest, "Access to the previous latest project version is forbidden");
345+
}
346+
}
347+
project = qm.createProject(trimmedProjectName, null,
348+
StringUtils.trimToNull(request.getProjectVersion()), request.getProjectTags(), parent,
349+
null, null, request.isLatestProjectVersion(), true);
350+
Principal principal = getPrincipal();
351+
qm.updateNewProjectACL(project, principal);
349352
}
350353
return process(qm, project, request.getBom());
354+
} catch (NullPointerException e) {
355+
return Response.status(Response.Status.NOT_FOUND).entity("The parent project could not be found.").build();
351356
}
352357
}
353358
}
@@ -419,42 +424,47 @@ public Response uploadBom(
419424
try (QueryManager qm = new QueryManager()) {
420425
final String trimmedProjectName = StringUtils.trimToNull(projectName);
421426
final String trimmedProjectVersion = StringUtils.trimToNull(projectVersion);
422-
Project project = qm.getProject(trimmedProjectName, trimmedProjectVersion);
427+
Project parent = null;
428+
UUID uuid = null;
429+
if (parentUUID != null) {
430+
parent = qm.getProject(parentUUID);
431+
uuid = parent.getUuid();
432+
}
433+
Project project = qm.getProject(trimmedProjectName, trimmedProjectVersion, uuid);
423434
if (project == null && autoCreate) {
424-
if (hasPermission(Permissions.Constants.PORTFOLIO_MANAGEMENT) || hasPermission(Permissions.Constants.PORTFOLIO_MANAGEMENT_CREATE) || hasPermission(Permissions.Constants.PROJECT_CREATION_UPLOAD)) {
425-
Project parent = null;
426-
if (parentUUID != null || parentName != null) {
427-
if (parentUUID != null) {
428-
429-
parent = qm.getObjectByUuid(Project.class, parentUUID);
430-
} else {
431-
final String trimmedParentName = StringUtils.trimToNull(parentName);
432-
final String trimmedParentVersion = StringUtils.trimToNull(parentVersion);
433-
parent = qm.getProject(trimmedParentName, trimmedParentVersion);
434-
}
435-
436-
if (parent == null) { // if parent project is specified but not found
437-
return Response.status(Response.Status.NOT_FOUND).entity("The parent project could not be found.").build();
438-
}
439-
requireAccess(qm, parent, "Access to the specified parent project is forbidden");
440-
}
441-
if (isLatest) {
442-
final Project oldLatest = qm.getLatestProjectVersion(trimmedProjectName);
443-
if(oldLatest != null) {
444-
requireAccess(qm, oldLatest, "Access to the previous latest project version is forbidden");
445-
}
435+
if (Stream.of(Permissions.Constants.PORTFOLIO_MANAGEMENT,
436+
Permissions.Constants.PORTFOLIO_MANAGEMENT_CREATE,
437+
Permissions.Constants.PROJECT_CREATION_UPLOAD).noneMatch(this::hasPermission)) {
438+
return Response.status(Response.Status.UNAUTHORIZED)
439+
.entity("The principal does not have permission to create project.")
440+
.build();
441+
}
442+
443+
if (parent == null) {
444+
final String trimmedParentName = StringUtils.trimToNull(parentName);
445+
final String trimmedParentVersion = StringUtils.trimToNull(parentVersion);
446+
parent = qm.getProject(trimmedParentName, trimmedParentVersion, uuid);
447+
}
448+
449+
Objects.requireNonNull(parent);
450+
requireAccess(qm, parent, "Access to the specified parent project is forbidden");
451+
452+
if (isLatest) {
453+
final Project oldLatest = qm.getLatestProjectVersion(trimmedProjectName, uuid);
454+
if(oldLatest != null) {
455+
requireAccess(qm, oldLatest, "Access to the previous latest project version is forbidden");
446456
}
447-
final List<org.dependencytrack.model.Tag> tags = (projectTags != null && !projectTags.isBlank())
448-
? Arrays.stream(projectTags.split(",")).map(String::trim).filter(not(String::isEmpty)).map(org.dependencytrack.model.Tag::new).toList()
449-
: null;
450-
project = qm.createProject(trimmedProjectName, null, trimmedProjectVersion, tags, parent, null, null, isLatest, true);
451-
Principal principal = getPrincipal();
452-
qm.updateNewProjectACL(project, principal);
453-
} else {
454-
return Response.status(Response.Status.UNAUTHORIZED).entity("The principal does not have permission to create project.").build();
455457
}
458+
final List<org.dependencytrack.model.Tag> tags = (projectTags != null && !projectTags.isBlank())
459+
? Arrays.stream(projectTags.split(",")).map(String::trim).filter(not(String::isEmpty)).map(org.dependencytrack.model.Tag::new).toList()
460+
: null;
461+
project = qm.createProject(trimmedProjectName, null, trimmedProjectVersion, tags, parent, null, null, isLatest, true);
462+
Principal principal = getPrincipal();
463+
qm.updateNewProjectACL(project, principal);
456464
}
457465
return process(qm, project, artifactParts);
466+
} catch (NullPointerException e) {
467+
return Response.status(Response.Status.NOT_FOUND).entity("The parent project could not be found.").build();
458468
}
459469
}
460470
}

src/main/java/org/dependencytrack/resources/v1/VexResource.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import java.util.Base64;
6666
import java.util.Collections;
6767
import java.util.List;
68+
import java.util.UUID;
6869

6970
/**
7071
* JAX-RS resources for processing VEX documents.
@@ -191,10 +192,12 @@ public Response uploadVex(VexSubmitRequest request) {
191192
failOnValidationError(
192193
validator.validateProperty(request, "projectName"),
193194
validator.validateProperty(request, "projectVersion"),
195+
validator.validateProperty(request, "parent"),
194196
validator.validateProperty(request, "vex")
195197
);
196198
try (QueryManager qm = new QueryManager()) {
197-
Project project = qm.getProject(request.getProjectName(), request.getProjectVersion());
199+
UUID parentUuid = request.getParent() != null ? UUID.fromString(request.getParent()) : null;
200+
Project project = qm.getProject(request.getProjectName(), request.getProjectVersion(), parentUuid);
198201
return process(qm, project, request.getVex());
199202
}
200203
}
@@ -242,6 +245,7 @@ public Response uploadVex(VexSubmitRequest request) {
242245
public Response uploadVex(@FormDataParam("project") String projectUuid,
243246
@FormDataParam("projectName") String projectName,
244247
@FormDataParam("projectVersion") String projectVersion,
248+
@Parameter(allowEmptyValue = true, required = false) @FormDataParam("parent") String parentUuid,
245249
@Parameter(schema = @Schema(type = "string")) @FormDataParam("vex") final List<FormDataBodyPart> artifactParts) {
246250
if (projectUuid != null) {
247251
try (QueryManager qm = new QueryManager()) {
@@ -252,7 +256,8 @@ public Response uploadVex(@FormDataParam("project") String projectUuid,
252256
try (QueryManager qm = new QueryManager()) {
253257
final String trimmedProjectName = StringUtils.trimToNull(projectName);
254258
final String trimmedProjectVersion = StringUtils.trimToNull(projectVersion);
255-
Project project = qm.getProject(trimmedProjectName, trimmedProjectVersion);
259+
final UUID uuid = parentUuid != null ? UUID.fromString(parentUuid) : null;
260+
Project project = qm.getProject(trimmedProjectName, trimmedProjectVersion, uuid);
256261
return process(qm, project, artifactParts);
257262
}
258263
}

0 commit comments

Comments
 (0)