From d271a94e8b40c0c15ac8b1eb0ee90682d48d7570 Mon Sep 17 00:00:00 2001
From: starfishfive <161029169+starfishfive@users.noreply.github.com>
Date: Thu, 16 May 2024 12:15:20 +0200
Subject: [PATCH 1/4] Update ComponentResource.java
Signed-off-by: starfishfive <161029169+starfishfive@users.noreply.github.com>
---
.../resources/v1/ComponentResource.java | 24 ++++++++++++++++++-
1 file changed, 23 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/dependencytrack/resources/v1/ComponentResource.java b/src/main/java/org/dependencytrack/resources/v1/ComponentResource.java
index 23e90d1252..9199051440 100644
--- a/src/main/java/org/dependencytrack/resources/v1/ComponentResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/ComponentResource.java
@@ -73,7 +73,29 @@
@Path("/v1/component")
@Api(value = "component", authorizations = @Authorization(value = "X-Api-Key"))
public class ComponentResource extends AlpineResource {
-
+
+ @GET
+ @Path("/all")
+ @Produces(MediaType.APPLICATION_JSON)
+ @ApiOperation(
+ value = "Returns a list of all components",
+ response = Component.class,
+ responseContainer = "List",
+ responseHeaders = @ResponseHeader(name = TOTAL_COUNT_HEADER, response = Long.class, description = "The total number of components"),
+ notes = "
Requires permission VIEW_PORTFOLIO
"
+ )
+ @PaginatedApi
+ @ApiResponses(value = {
+ @ApiResponse(code = 401, message = "Unauthorized"),
+ })
+ @PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
+ public Response getComponents(
+ try (QueryManager qm = new QueryManager(getAlpineRequest())) {
+ final PaginatedResult result = qm.getComponents();
+ return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build()
+ }
+ }
+
@GET
@Path("/project/{uuid}")
@Produces(MediaType.APPLICATION_JSON)
From 35315f913b8d7d3e987709f053255ff3817312a3 Mon Sep 17 00:00:00 2001
From: starfishfive <161029169+starfishfive@users.noreply.github.com>
Date: Fri, 17 May 2024 16:56:12 +0200
Subject: [PATCH 2/4] Create ComponentQueryFilterBuilder.java
Signed-off-by: starfishfive <161029169+starfishfive@users.noreply.github.com>
---
.../ComponentQueryFilterBuilder.java | 63 +++++++++++++++++++
1 file changed, 63 insertions(+)
create mode 100644 src/main/java/org/dependencytrack/persistence/ComponentQueryFilterBuilder.java
diff --git a/src/main/java/org/dependencytrack/persistence/ComponentQueryFilterBuilder.java b/src/main/java/org/dependencytrack/persistence/ComponentQueryFilterBuilder.java
new file mode 100644
index 0000000000..349c10b624
--- /dev/null
+++ b/src/main/java/org/dependencytrack/persistence/ComponentQueryFilterBuilder.java
@@ -0,0 +1,63 @@
+/*
+ * This file is part of Dependency-Track.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) OWASP Foundation. All Rights Reserved.
+ */
+package org.dependencytrack.persistence;
+
+import org.dependencytrack.model.Classifier;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Builder for filters meant to be used with {@link javax.jdo.Query#setFilter} and the query's
+ * parameters that can be passed to {@link alpine.persistence.AbstractAlpineQueryManager#execute}
+ *
+ * Mutable and not threadsafe!
+ */
+class ComponentQueryFilterBuilder {
+
+ private final Map params;
+ private final List filterCriteria;
+
+ ComponentQueryFilterBuilder() {
+ this.params = new HashMap<>();
+ this.filterCriteria = new ArrayList<>();
+ }
+
+ ComponentQueryFilterBuilder withAuthor(string author) {
+ params.put("author", author);
+ filterCriteria.add("(author == :author)");
+ return this;
+ }
+
+ ComponentQueryFilterBuilder withClassifier(Classifier classifier) {
+ params.put("classifier", classifier);
+ filterCriteria.add("(classifier == :classifier)");
+ return this;
+ }
+
+ String buildFilter() {
+ return String.join(" && ", this.filterCriteria);
+ }
+
+ Map getParams() {
+ return params;
+ }
+}
From 7d7724155e64204c32f4726b65f3997e12e88834 Mon Sep 17 00:00:00 2001
From: starfishfive <161029169+starfishfive@users.noreply.github.com>
Date: Fri, 17 May 2024 17:09:08 +0200
Subject: [PATCH 3/4] Fix missing close/start tag
Signed-off-by: starfishfive <161029169+starfishfive@users.noreply.github.com>
---
.../org/dependencytrack/resources/v1/ComponentResource.java | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/main/java/org/dependencytrack/resources/v1/ComponentResource.java b/src/main/java/org/dependencytrack/resources/v1/ComponentResource.java
index 9199051440..f6c617500e 100644
--- a/src/main/java/org/dependencytrack/resources/v1/ComponentResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/ComponentResource.java
@@ -75,7 +75,6 @@
public class ComponentResource extends AlpineResource {
@GET
- @Path("/all")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Returns a list of all components",
@@ -89,7 +88,7 @@ public class ComponentResource extends AlpineResource {
@ApiResponse(code = 401, message = "Unauthorized"),
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
- public Response getComponents(
+ public Response getComponents() {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final PaginatedResult result = qm.getComponents();
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build()
From 61bb89c41ad178e8406018a11e6f49fe3d69b318 Mon Sep 17 00:00:00 2001
From: starfishfive <161029169+starfishfive@users.noreply.github.com>
Date: Fri, 17 May 2024 15:49:00 +0000
Subject: [PATCH 4/4] Add ACL and filter
Signed-off-by: starfishfive <161029169+starfishfive@users.noreply.github.com>
---
.../persistence/ComponentQueryFilterBuilder.java | 6 ++++++
.../persistence/ComponentQueryManager.java | 16 +++++++++++-----
.../resources/v1/ComponentResource.java | 4 +++-
3 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/src/main/java/org/dependencytrack/persistence/ComponentQueryFilterBuilder.java b/src/main/java/org/dependencytrack/persistence/ComponentQueryFilterBuilder.java
index 349c10b624..0850e381b5 100644
--- a/src/main/java/org/dependencytrack/persistence/ComponentQueryFilterBuilder.java
+++ b/src/main/java/org/dependencytrack/persistence/ComponentQueryFilterBuilder.java
@@ -41,6 +41,12 @@ class ComponentQueryFilterBuilder {
this.filterCriteria = new ArrayList<>();
}
+ ComponentQueryFilterBuilder withFuzzyName(String name) {
+ params.put("name", name);
+ filterCriteria.add("(name.toLowerCase().matches(:name))");
+ return this;
+ }
+
ComponentQueryFilterBuilder withAuthor(string author) {
params.put("author", author);
filterCriteria.add("(author == :author)");
diff --git a/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java b/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java
index 358595e1b6..22b6aced9f 100644
--- a/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java
+++ b/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java
@@ -89,13 +89,19 @@ public PaginatedResult getComponents(final boolean includeMetrics) {
if (orderBy == null) {
query.setOrdering("name asc, version desc");
}
+
+ final var filterBuilder = new ProjectQueryFilterBuilder();
+
if (filter != null) {
- query.setFilter("name.toLowerCase().matches(:name)");
final String filterString = ".*" + filter.toLowerCase() + ".*";
- result = execute(query, filterString);
- } else {
- result = execute(query);
- }
+ filterBuilder = filterBuilder.withFuzzyName(filterString);
+ }
+
+ final String queryFilter = filterBuilder.buildFilter();
+ final Map params = filterBuilder.getParams();
+
+ preprocessACLs(query, queryFilter, params, false);
+ result = execute(query, params);
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.
diff --git a/src/main/java/org/dependencytrack/resources/v1/ComponentResource.java b/src/main/java/org/dependencytrack/resources/v1/ComponentResource.java
index f6c617500e..1c71985cdf 100644
--- a/src/main/java/org/dependencytrack/resources/v1/ComponentResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/ComponentResource.java
@@ -88,7 +88,9 @@ public class ComponentResource extends AlpineResource {
@ApiResponse(code = 401, message = "Unauthorized"),
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
- public Response getComponents() {
+ public Response getComponents(
+ @ApiParam(value = "The optional author of the component to query on", required = false)
+ @QueryParam("author") String author) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final PaginatedResult result = qm.getComponents();
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build()