Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing resource to get all components #3720

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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}
* <br>
* Mutable and not threadsafe!
*/
class ComponentQueryFilterBuilder {

private final Map<String, Object> params;
private final List<String> filterCriteria;

ComponentQueryFilterBuilder() {
this.params = new HashMap<>();
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)");
return this;
}

ComponentQueryFilterBuilder withClassifier(Classifier classifier) {
params.put("classifier", classifier);
filterCriteria.add("(classifier == :classifier)");
return this;
}

String buildFilter() {
return String.join(" && ", this.filterCriteria);
}

Map<String, Object> getParams() {
return params;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object> 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,30 @@
@Path("/v1/component")
@Api(value = "component", authorizations = @Authorization(value = "X-Api-Key"))
public class ComponentResource extends AlpineResource {


@GET
@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 = "<p>Requires permission <strong>VIEW_PORTFOLIO</strong></p>"
)
@PaginatedApi
@ApiResponses(value = {
@ApiResponse(code = 401, message = "Unauthorized"),
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
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();
Copy link
Contributor

@valentijnscholten valentijnscholten May 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add ACL checks / filtering, similar to

public PaginatedResult getProjects(final String name, final boolean excludeInactive, final boolean onlyRoot, final Team notAssignedToTeam) {
final Query<Project> query = pm.newQuery(Project.class);
if (orderBy == null) {
query.setOrdering("version desc");
}
final var filterBuilder = new ProjectQueryFilterBuilder()
.excludeInactive(excludeInactive)
.withName(name);
if (onlyRoot) {
filterBuilder.excludeChildProjects();
query.getFetchPlan().addGroup(Project.FetchGroup.ALL.name());
}
if(notAssignedToTeam != null) {
filterBuilder.notWithTeam(notAssignedToTeam);
}
final String queryFilter = filterBuilder.buildFilter();
final Map<String, Object> params = filterBuilder.getParams();
preprocessACLs(query, queryFilter, params, false);
return execute(query, params);
}

return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build()
}
}

@GET
@Path("/project/{uuid}")
@Produces(MediaType.APPLICATION_JSON)
Expand Down