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()