2021-03-29 13:55:25 -05:00
|
|
|
/*
|
|
|
|
* 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) Steve Springett. All Rights Reserved.
|
|
|
|
*/
|
|
|
|
package org.dependencytrack.persistence;
|
|
|
|
|
2022-04-12 13:33:01 +02:00
|
|
|
import alpine.common.logging.Logger;
|
2021-03-29 13:55:25 -05:00
|
|
|
import alpine.event.framework.Event;
|
2021-06-27 00:12:43 -05:00
|
|
|
import alpine.model.ApiKey;
|
|
|
|
import alpine.model.Permission;
|
2021-04-01 23:01:28 -05:00
|
|
|
import alpine.model.Team;
|
2021-06-27 00:12:43 -05:00
|
|
|
import alpine.model.UserPrincipal;
|
2022-12-14 12:41:09 +01:00
|
|
|
import alpine.notification.Notification;
|
|
|
|
import alpine.notification.NotificationLevel;
|
2021-03-29 13:55:25 -05:00
|
|
|
import alpine.persistence.PaginatedResult;
|
|
|
|
import alpine.resources.AlpineRequest;
|
|
|
|
import com.github.packageurl.PackageURL;
|
2022-11-05 16:09:33 +01:00
|
|
|
import org.apache.commons.collections4.CollectionUtils;
|
2021-03-29 13:55:25 -05:00
|
|
|
import org.apache.commons.lang3.StringUtils;
|
2021-06-27 00:12:43 -05:00
|
|
|
import org.dependencytrack.auth.Permissions;
|
2021-03-29 13:55:25 -05:00
|
|
|
import org.dependencytrack.event.IndexEvent;
|
|
|
|
import org.dependencytrack.model.Analysis;
|
|
|
|
import org.dependencytrack.model.AnalysisComment;
|
2022-05-08 02:52:49 -05:00
|
|
|
import org.dependencytrack.model.Classifier;
|
2021-03-29 13:55:25 -05:00
|
|
|
import org.dependencytrack.model.Component;
|
2021-06-27 00:12:43 -05:00
|
|
|
import org.dependencytrack.model.ConfigPropertyConstants;
|
2021-03-29 13:55:25 -05:00
|
|
|
import org.dependencytrack.model.FindingAttribution;
|
|
|
|
import org.dependencytrack.model.Project;
|
|
|
|
import org.dependencytrack.model.ProjectProperty;
|
2023-03-12 09:22:42 +01:00
|
|
|
import org.dependencytrack.model.ProjectVersion;
|
2021-03-29 13:55:25 -05:00
|
|
|
import org.dependencytrack.model.ServiceComponent;
|
|
|
|
import org.dependencytrack.model.Tag;
|
|
|
|
import org.dependencytrack.model.Vulnerability;
|
2022-12-14 12:41:09 +01:00
|
|
|
import org.dependencytrack.notification.NotificationConstants;
|
|
|
|
import org.dependencytrack.notification.NotificationGroup;
|
|
|
|
import org.dependencytrack.notification.NotificationScope;
|
|
|
|
import org.dependencytrack.util.NotificationUtil;
|
2023-11-08 15:47:25 +01:00
|
|
|
|
2021-03-29 13:55:25 -05:00
|
|
|
import javax.jdo.FetchPlan;
|
|
|
|
import javax.jdo.PersistenceManager;
|
|
|
|
import javax.jdo.Query;
|
2021-06-27 00:12:43 -05:00
|
|
|
import java.security.Principal;
|
2021-04-01 23:01:28 -05:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Date;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.UUID;
|
2021-03-29 13:55:25 -05:00
|
|
|
|
|
|
|
final class ProjectQueryManager extends QueryManager implements IQueryManager {
|
|
|
|
|
2022-04-12 13:33:01 +02:00
|
|
|
private static final Logger LOGGER = Logger.getLogger(ProjectQueryManager.class);
|
|
|
|
|
2021-03-29 13:55:25 -05:00
|
|
|
/**
|
|
|
|
* Constructs a new QueryManager.
|
|
|
|
* @param pm a PersistenceManager object
|
|
|
|
*/
|
|
|
|
ProjectQueryManager(final PersistenceManager pm) {
|
|
|
|
super(pm);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs a new QueryManager.
|
|
|
|
* @param pm a PersistenceManager object
|
|
|
|
* @param request an AlpineRequest object
|
|
|
|
*/
|
|
|
|
ProjectQueryManager(final PersistenceManager pm, final AlpineRequest request) {
|
|
|
|
super(pm, request);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of all projects.
|
|
|
|
* @return a List of Projects
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2022-09-30 16:24:59 +02:00
|
|
|
public PaginatedResult getProjects(final boolean includeMetrics, final boolean excludeInactive, final boolean onlyRoot) {
|
2021-03-29 13:55:25 -05:00
|
|
|
final PaginatedResult result;
|
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
|
|
|
if (orderBy == null) {
|
|
|
|
query.setOrdering("name asc, version desc");
|
|
|
|
}
|
2022-07-15 22:54:11 +02:00
|
|
|
|
2022-07-15 23:48:12 +02:00
|
|
|
var filterBuilder = new ProjectQueryFilterBuilder()
|
2022-10-04 09:59:42 +02:00
|
|
|
.excludeInactive(excludeInactive);
|
2022-09-30 16:24:59 +02:00
|
|
|
|
|
|
|
if (onlyRoot){
|
Refactoring in persistence (#2121)
* Refactor getTags for greater readability
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Rename method and clean up formatting
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add docs to ProjectQueryFilterBuilder
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add tests for ProjectQueryFilterBuilder
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Simplify method
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Simplify getComponents method
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Move copying from another alias into VulnerabilityAlias class
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add missing newlines at eof
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add back condition removed in refactoring
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
2022-11-15 11:34:27 +01:00
|
|
|
filterBuilder.excludeChildProjects();
|
2022-10-18 09:41:11 +02:00
|
|
|
query.getFetchPlan().addGroup(Project.FetchGroup.ALL.name());
|
2022-09-30 16:24:59 +02:00
|
|
|
}
|
2022-07-15 22:54:11 +02:00
|
|
|
|
2021-03-29 13:55:25 -05:00
|
|
|
if (filter != null) {
|
|
|
|
final String filterString = ".*" + filter.toLowerCase() + ".*";
|
|
|
|
final Tag tag = getTagByName(filter.trim());
|
2022-07-15 22:54:11 +02:00
|
|
|
|
2022-07-16 00:28:09 +02:00
|
|
|
if (tag != null) {
|
|
|
|
filterBuilder = filterBuilder.withFuzzyNameOrExactTag(filterString, tag);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
filterBuilder = filterBuilder.withFuzzyName(filterString);
|
|
|
|
}
|
2022-07-15 23:48:12 +02:00
|
|
|
}
|
2022-07-15 22:54:11 +02:00
|
|
|
|
2022-07-15 23:48:12 +02:00
|
|
|
final String queryFilter = filterBuilder.buildFilter();
|
|
|
|
final Map<String, Object> params = filterBuilder.getParams();
|
2022-07-15 22:54:11 +02:00
|
|
|
|
2022-07-15 22:51:33 +02:00
|
|
|
preprocessACLs(query, queryFilter, params, false);
|
|
|
|
result = execute(query, params);
|
2021-03-29 13:55:25 -05:00
|
|
|
if (includeMetrics) {
|
|
|
|
// Populate each Project object in the paginated result with transitive related
|
|
|
|
// data to minimize the number of round trips a client needs to make, process, and render.
|
|
|
|
for (Project project : result.getList(Project.class)) {
|
|
|
|
project.setMetrics(getMostRecentProjectMetrics(project));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of all projects.
|
|
|
|
* @return a List of Projects
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public PaginatedResult getProjects(final boolean includeMetrics) {
|
2022-09-30 16:24:59 +02:00
|
|
|
return getProjects(includeMetrics, false, false);
|
2021-03-29 13:55:25 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of all projects.
|
|
|
|
* @return a List of Projects
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public PaginatedResult getProjects() {
|
|
|
|
return getProjects(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of all projects.
|
|
|
|
* This method if designed NOT to provide paginated results.
|
|
|
|
* @return a List of Projects
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public List<Project> getAllProjects() {
|
|
|
|
return getAllProjects(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of all projects.
|
|
|
|
* This method if designed NOT to provide paginated results.
|
|
|
|
* @return a List of Projects
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public List<Project> getAllProjects(boolean excludeInactive) {
|
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
|
|
|
if (excludeInactive) {
|
2021-04-01 23:01:28 -05:00
|
|
|
query.setFilter("active == true || active == null");
|
2021-03-29 13:55:25 -05:00
|
|
|
}
|
2021-11-26 22:37:32 -06:00
|
|
|
query.setOrdering("id asc");
|
2021-08-20 23:01:08 -05:00
|
|
|
return query.executeList();
|
2021-03-29 13:55:25 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-07-15 23:05:46 +02:00
|
|
|
* Returns a list of projects by their name.
|
2021-03-29 13:55:25 -05:00
|
|
|
* @param name the name of the Projects (required)
|
|
|
|
* @return a List of Project objects
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2022-09-30 16:24:59 +02:00
|
|
|
public PaginatedResult getProjects(final String name, final boolean excludeInactive, final boolean onlyRoot) {
|
2021-03-29 13:55:25 -05:00
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
|
|
|
if (orderBy == null) {
|
|
|
|
query.setOrdering("version desc");
|
|
|
|
}
|
2022-07-15 23:38:32 +02:00
|
|
|
|
|
|
|
final var filterBuilder = new ProjectQueryFilterBuilder()
|
|
|
|
.excludeInactive(excludeInactive)
|
2022-10-04 09:59:42 +02:00
|
|
|
.withName(name);
|
2022-09-30 16:24:59 +02:00
|
|
|
|
Refactoring in persistence (#2121)
* Refactor getTags for greater readability
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Rename method and clean up formatting
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add docs to ProjectQueryFilterBuilder
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add tests for ProjectQueryFilterBuilder
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Simplify method
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Simplify getComponents method
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Move copying from another alias into VulnerabilityAlias class
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add missing newlines at eof
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add back condition removed in refactoring
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
2022-11-15 11:34:27 +01:00
|
|
|
if (onlyRoot) {
|
|
|
|
filterBuilder.excludeChildProjects();
|
2022-10-18 09:41:11 +02:00
|
|
|
query.getFetchPlan().addGroup(Project.FetchGroup.ALL.name());
|
2022-09-30 16:24:59 +02:00
|
|
|
}
|
2022-07-15 23:38:32 +02:00
|
|
|
|
|
|
|
final String queryFilter = filterBuilder.buildFilter();
|
|
|
|
final Map<String, Object> params = filterBuilder.getParams();
|
|
|
|
|
2021-06-27 00:12:43 -05:00
|
|
|
preprocessACLs(query, queryFilter, params, false);
|
|
|
|
return execute(query, params);
|
2021-03-29 13:55:25 -05:00
|
|
|
}
|
|
|
|
|
2023-03-12 09:22:42 +01:00
|
|
|
/**
|
|
|
|
* Returns a project by its uuid.
|
|
|
|
* @param uuid the uuid of the Project (required)
|
|
|
|
* @return a Project object, or null if not found
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2023-03-12 09:22:42 +01:00
|
|
|
public Project getProject(final String uuid) {
|
|
|
|
final Project project = getObjectByUuid(Project.class, uuid, Project.FetchGroup.ALL.name());
|
|
|
|
if (project != null) {
|
|
|
|
// set Metrics to minimize the number of round trips a client needs to make
|
|
|
|
project.setMetrics(getMostRecentProjectMetrics(project));
|
|
|
|
// set ProjectVersions to minimize the number of round trips a client needs to make
|
|
|
|
project.setVersions(getProjectVersions(project));
|
|
|
|
}
|
|
|
|
return project;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-29 13:55:25 -05:00
|
|
|
/**
|
2022-07-15 23:05:46 +02:00
|
|
|
* Returns a project by its name and version.
|
2021-03-29 13:55:25 -05:00
|
|
|
* @param name the name of the Project (required)
|
|
|
|
* @param version the version of the Project (or null)
|
|
|
|
* @return a Project object, or null if not found
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public Project getProject(final String name, final String version) {
|
2021-06-27 00:12:43 -05:00
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
2022-07-15 23:38:32 +02:00
|
|
|
|
|
|
|
final var filterBuilder = new ProjectQueryFilterBuilder()
|
|
|
|
.withName(name)
|
|
|
|
.withVersion(version);
|
|
|
|
|
|
|
|
final String queryFilter = filterBuilder.buildFilter();
|
|
|
|
final Map<String, Object> params = filterBuilder.getParams();
|
|
|
|
|
2021-06-27 00:12:43 -05:00
|
|
|
preprocessACLs(query, queryFilter, params, false);
|
2021-07-16 15:13:51 -05:00
|
|
|
query.setFilter(queryFilter);
|
2022-07-23 20:29:51 +02:00
|
|
|
query.setRange(0, 1);
|
2023-03-12 09:22:42 +01:00
|
|
|
final Project project = singleResult(query.executeWithMap(params));
|
|
|
|
if (project != null) {
|
|
|
|
// set Metrics to prevent extra round trip
|
|
|
|
project.setMetrics(getMostRecentProjectMetrics(project));
|
|
|
|
// set ProjectVersions to prevent extra round trip
|
|
|
|
project.setVersions(getProjectVersions(project));
|
|
|
|
}
|
|
|
|
return project;
|
2021-06-27 00:12:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of projects that are accessible by the specified team.
|
|
|
|
* @param team the team the has access to Projects
|
|
|
|
* @return a List of Project objects
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2022-09-30 16:24:59 +02:00
|
|
|
public PaginatedResult getProjects(final Team team, final boolean excludeInactive, final boolean bypass, final boolean onlyRoot) {
|
2021-06-27 00:12:43 -05:00
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
|
|
|
if (orderBy == null) {
|
|
|
|
query.setOrdering("name asc, version desc, id asc");
|
|
|
|
}
|
2022-07-15 23:38:32 +02:00
|
|
|
|
|
|
|
final var filterBuilder = new ProjectQueryFilterBuilder()
|
|
|
|
.excludeInactive(excludeInactive)
|
2022-10-04 09:59:42 +02:00
|
|
|
.withTeam(team);
|
2022-09-30 16:24:59 +02:00
|
|
|
|
|
|
|
if (onlyRoot){
|
Refactoring in persistence (#2121)
* Refactor getTags for greater readability
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Rename method and clean up formatting
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add docs to ProjectQueryFilterBuilder
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add tests for ProjectQueryFilterBuilder
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Simplify method
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Simplify getComponents method
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Move copying from another alias into VulnerabilityAlias class
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add missing newlines at eof
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add back condition removed in refactoring
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
2022-11-15 11:34:27 +01:00
|
|
|
filterBuilder.excludeChildProjects();
|
2022-10-18 09:41:11 +02:00
|
|
|
query.getFetchPlan().addGroup(Project.FetchGroup.ALL.name());
|
2022-09-30 16:24:59 +02:00
|
|
|
}
|
2022-07-15 23:38:32 +02:00
|
|
|
|
|
|
|
final String queryFilter = filterBuilder.buildFilter();
|
|
|
|
final Map<String, Object> params = filterBuilder.getParams();
|
|
|
|
|
2021-06-27 00:12:43 -05:00
|
|
|
preprocessACLs(query, queryFilter, params, bypass);
|
|
|
|
return execute(query, params);
|
2021-03-29 13:55:25 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a paginated result of projects by tag.
|
|
|
|
* @param tag the tag associated with the Project
|
|
|
|
* @return a List of Projects that contain the tag
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2022-09-30 16:24:59 +02:00
|
|
|
public PaginatedResult getProjects(final Tag tag, final boolean includeMetrics, final boolean excludeInactive, final boolean onlyRoot) {
|
2021-03-29 13:55:25 -05:00
|
|
|
final PaginatedResult result;
|
2021-06-27 00:12:43 -05:00
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
2021-03-29 13:55:25 -05:00
|
|
|
if (orderBy == null) {
|
|
|
|
query.setOrdering("name asc");
|
|
|
|
}
|
2022-07-15 23:38:32 +02:00
|
|
|
|
2022-07-16 00:28:09 +02:00
|
|
|
var filterBuilder = new ProjectQueryFilterBuilder()
|
2022-07-15 23:38:32 +02:00
|
|
|
.excludeInactive(excludeInactive)
|
2022-10-04 09:59:42 +02:00
|
|
|
.withTag(tag);
|
2022-09-30 16:24:59 +02:00
|
|
|
|
|
|
|
if (onlyRoot){
|
Refactoring in persistence (#2121)
* Refactor getTags for greater readability
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Rename method and clean up formatting
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add docs to ProjectQueryFilterBuilder
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add tests for ProjectQueryFilterBuilder
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Simplify method
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Simplify getComponents method
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Move copying from another alias into VulnerabilityAlias class
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add missing newlines at eof
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add back condition removed in refactoring
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
2022-11-15 11:34:27 +01:00
|
|
|
filterBuilder.excludeChildProjects();
|
2022-10-18 09:41:11 +02:00
|
|
|
query.getFetchPlan().addGroup(Project.FetchGroup.ALL.name());
|
2022-09-30 16:24:59 +02:00
|
|
|
}
|
2022-07-15 23:38:32 +02:00
|
|
|
|
2022-07-16 00:28:09 +02:00
|
|
|
if (filter != null) {
|
|
|
|
final String filterString = ".*" + filter.toLowerCase() + ".*";
|
|
|
|
filterBuilder = filterBuilder.withFuzzyName(filterString);
|
|
|
|
}
|
|
|
|
|
2022-07-15 23:38:32 +02:00
|
|
|
final String queryFilter = filterBuilder.buildFilter();
|
|
|
|
final Map<String, Object> params = filterBuilder.getParams();
|
|
|
|
|
2021-06-27 00:12:43 -05:00
|
|
|
preprocessACLs(query, queryFilter, params, false);
|
|
|
|
result = execute(query, params);
|
2021-03-29 13:55:25 -05:00
|
|
|
if (includeMetrics) {
|
|
|
|
// Populate each Project object in the paginated result with transitive related
|
|
|
|
// data to minimize the number of round trips a client needs to make, process, and render.
|
|
|
|
for (Project project : result.getList(Project.class)) {
|
|
|
|
project.setMetrics(getMostRecentProjectMetrics(project));
|
2022-05-08 02:52:49 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a paginated result of projects by classifier.
|
|
|
|
* @param classifier the classifier of the Project
|
|
|
|
* @return a List of Projects of the specified classifier
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2022-09-30 16:24:59 +02:00
|
|
|
public PaginatedResult getProjects(final Classifier classifier, final boolean includeMetrics, final boolean excludeInactive, final boolean onlyRoot) {
|
2022-05-08 02:52:49 -05:00
|
|
|
final PaginatedResult result;
|
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
|
|
|
if (orderBy == null) {
|
|
|
|
query.setOrdering("name asc");
|
|
|
|
}
|
2022-07-15 23:38:32 +02:00
|
|
|
|
|
|
|
final var filterBuilder = new ProjectQueryFilterBuilder()
|
|
|
|
.excludeInactive(excludeInactive)
|
2022-10-04 09:59:42 +02:00
|
|
|
.withClassifier(classifier);
|
2022-09-30 16:24:59 +02:00
|
|
|
|
|
|
|
if (onlyRoot){
|
Refactoring in persistence (#2121)
* Refactor getTags for greater readability
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Rename method and clean up formatting
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add docs to ProjectQueryFilterBuilder
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add tests for ProjectQueryFilterBuilder
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Simplify method
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Simplify getComponents method
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Move copying from another alias into VulnerabilityAlias class
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add missing newlines at eof
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
* Add back condition removed in refactoring
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
Signed-off-by: Pinto Spindler, Stephan <25225092+s-spindler@users.noreply.github.com>
2022-11-15 11:34:27 +01:00
|
|
|
filterBuilder.excludeChildProjects();
|
2022-10-18 09:41:11 +02:00
|
|
|
query.getFetchPlan().addGroup(Project.FetchGroup.ALL.name());
|
2022-09-30 16:24:59 +02:00
|
|
|
}
|
2022-07-15 23:38:32 +02:00
|
|
|
|
|
|
|
final String queryFilter = filterBuilder.buildFilter();
|
|
|
|
final Map<String, Object> params = filterBuilder.getParams();
|
|
|
|
|
2022-05-08 02:52:49 -05:00
|
|
|
preprocessACLs(query, queryFilter, params, false);
|
|
|
|
result = execute(query, params);
|
|
|
|
if (includeMetrics) {
|
|
|
|
// Populate each Project object in the paginated result with transitive related
|
|
|
|
// data to minimize the number of round trips a client needs to make, process, and render.
|
|
|
|
for (Project project : result.getList(Project.class)) {
|
|
|
|
project.setMetrics(getMostRecentProjectMetrics(project));
|
2021-03-29 13:55:25 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a paginated result of projects by tag.
|
|
|
|
* @param tag the tag associated with the Project
|
|
|
|
* @return a List of Projects that contain the tag
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public PaginatedResult getProjects(final Tag tag) {
|
2022-09-30 16:24:59 +02:00
|
|
|
return getProjects(tag, false, false, false);
|
2021-03-29 13:55:25 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of Tag objects what have been resolved. It resolved
|
|
|
|
* tags by querying the database to retrieve the tag. If the tag does
|
|
|
|
* not exist, the tag will be created and returned with other resolved
|
|
|
|
* tags.
|
|
|
|
* @param tags a List of Tags to resolve
|
|
|
|
* @return List of resolved Tags
|
|
|
|
*/
|
|
|
|
private synchronized List<Tag> resolveTags(final List<Tag> tags) {
|
|
|
|
if (tags == null) {
|
|
|
|
return new ArrayList<>();
|
|
|
|
}
|
|
|
|
final List<Tag> resolvedTags = new ArrayList<>();
|
|
|
|
final List<String> unresolvedTags = new ArrayList<>();
|
|
|
|
for (final Tag tag: tags) {
|
|
|
|
final String trimmedTag = StringUtils.trimToNull(tag.getName());
|
|
|
|
if (trimmedTag != null) {
|
|
|
|
final Tag resolvedTag = getTagByName(trimmedTag);
|
|
|
|
if (resolvedTag != null) {
|
|
|
|
resolvedTags.add(resolvedTag);
|
|
|
|
} else {
|
|
|
|
unresolvedTags.add(trimmedTag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
resolvedTags.addAll(createTags(unresolvedTags));
|
|
|
|
return resolvedTags;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a list of Tag objects by name.
|
|
|
|
* @param name the name of the Tag
|
|
|
|
* @return a Tag object
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public Tag getTagByName(final String name) {
|
2022-12-14 16:16:29 +01:00
|
|
|
final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name));
|
2021-03-29 13:55:25 -05:00
|
|
|
final Query<Tag> query = pm.newQuery(Tag.class, "name == :name");
|
2022-07-23 20:29:51 +02:00
|
|
|
query.setRange(0, 1);
|
2022-12-14 16:16:29 +01:00
|
|
|
return singleResult(query.execute(loweredTrimmedTag));
|
2021-03-29 13:55:25 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new Tag object with the specified name.
|
|
|
|
* @param name the name of the Tag to create
|
|
|
|
* @return the created Tag object
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public Tag createTag(final String name) {
|
2022-12-14 16:16:29 +01:00
|
|
|
final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name));
|
|
|
|
final Tag resolvedTag = getTagByName(loweredTrimmedTag);
|
2021-03-29 13:55:25 -05:00
|
|
|
if (resolvedTag != null) {
|
|
|
|
return resolvedTag;
|
|
|
|
}
|
|
|
|
final Tag tag = new Tag();
|
2022-12-14 16:16:29 +01:00
|
|
|
tag.setName(loweredTrimmedTag);
|
2021-03-29 13:55:25 -05:00
|
|
|
return persist(tag);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates one or more Tag objects from the specified name(s).
|
|
|
|
* @param names the name(s) of the Tag(s) to create
|
|
|
|
* @return the created Tag object(s)
|
|
|
|
*/
|
|
|
|
private List<Tag> createTags(final List<String> names) {
|
|
|
|
final List<Tag> newTags = new ArrayList<>();
|
|
|
|
for (final String name: names) {
|
2022-12-14 16:16:29 +01:00
|
|
|
final String loweredTrimmedTag = StringUtils.lowerCase(StringUtils.trimToNull(name));
|
|
|
|
if (getTagByName(loweredTrimmedTag) == null) {
|
2021-03-29 13:55:25 -05:00
|
|
|
final Tag tag = new Tag();
|
2022-12-14 16:16:29 +01:00
|
|
|
tag.setName(loweredTrimmedTag);
|
2021-03-29 13:55:25 -05:00
|
|
|
newTags.add(tag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new ArrayList<>(persist(newTags));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new Project.
|
|
|
|
* @param name the name of the project to create
|
|
|
|
* @param description a description of the project
|
|
|
|
* @param version the project version
|
|
|
|
* @param tags a List of Tags - these will be resolved if necessary
|
|
|
|
* @param parent an optional parent Project
|
|
|
|
* @param purl an optional Package URL
|
|
|
|
* @param active specified if the project is active
|
|
|
|
* @param commitIndex specifies if the search index should be committed (an expensive operation)
|
|
|
|
* @return the created Project
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public Project createProject(String name, String description, String version, List<Tag> tags, Project parent, PackageURL purl, boolean active, boolean commitIndex) {
|
|
|
|
final Project project = new Project();
|
|
|
|
project.setName(name);
|
|
|
|
project.setDescription(description);
|
|
|
|
project.setVersion(version);
|
2022-09-30 16:24:59 +02:00
|
|
|
if (parent != null ) {
|
|
|
|
if (!Boolean.TRUE.equals(parent.isActive())){
|
|
|
|
throw new IllegalArgumentException("An inactive Parent cannot be selected as parent");
|
|
|
|
}
|
2022-10-04 09:59:42 +02:00
|
|
|
project.setParent(parent);
|
2021-03-29 13:55:25 -05:00
|
|
|
}
|
|
|
|
project.setPurl(purl);
|
|
|
|
project.setActive(active);
|
|
|
|
final Project result = persist(project);
|
|
|
|
|
|
|
|
final List<Tag> resolvedTags = resolveTags(tags);
|
|
|
|
bind(project, resolvedTags);
|
|
|
|
|
2023-10-21 23:36:58 +02:00
|
|
|
Event.dispatch(new IndexEvent(IndexEvent.Action.CREATE, result));
|
2022-12-14 12:41:09 +01:00
|
|
|
Notification.dispatch(new Notification()
|
|
|
|
.scope(NotificationScope.PORTFOLIO)
|
|
|
|
.group(NotificationGroup.PROJECT_CREATED)
|
|
|
|
.title(NotificationConstants.Title.PROJECT_CREATED)
|
|
|
|
.level(NotificationLevel.INFORMATIONAL)
|
|
|
|
.content(result.getName() + " was created")
|
|
|
|
.subject(NotificationUtil.toJson(pm.detachCopy(result)))
|
|
|
|
);
|
2021-03-29 13:55:25 -05:00
|
|
|
commitSearchIndex(commitIndex, Project.class);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a new Project.
|
|
|
|
* @param project the project to create
|
|
|
|
* @param tags a List of Tags - these will be resolved if necessary
|
|
|
|
* @param commitIndex specifies if the search index should be committed (an expensive operation)
|
|
|
|
* @return the created Project
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public Project createProject(final Project project, List<Tag> tags, boolean commitIndex) {
|
2022-12-02 18:21:49 +01:00
|
|
|
if (project.getParent() != null && !Boolean.TRUE.equals(project.getParent().isActive())){
|
|
|
|
throw new IllegalArgumentException("An inactive Parent cannot be selected as parent");
|
|
|
|
}
|
2021-03-29 13:55:25 -05:00
|
|
|
final Project result = persist(project);
|
|
|
|
final List<Tag> resolvedTags = resolveTags(tags);
|
|
|
|
bind(project, resolvedTags);
|
|
|
|
|
2023-10-21 23:36:58 +02:00
|
|
|
Event.dispatch(new IndexEvent(IndexEvent.Action.CREATE, result));
|
2021-03-29 13:55:25 -05:00
|
|
|
commitSearchIndex(commitIndex, Project.class);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates an existing Project.
|
|
|
|
* @param uuid the uuid of the project to update
|
|
|
|
* @param name the name of the project
|
|
|
|
* @param description a description of the project
|
|
|
|
* @param version the project version
|
|
|
|
* @param tags a List of Tags - these will be resolved if necessary
|
|
|
|
* @param purl an optional Package URL
|
|
|
|
* @param active specified if the project is active
|
|
|
|
* @param commitIndex specifies if the search index should be committed (an expensive operation)
|
|
|
|
* @return the updated Project
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public Project updateProject(UUID uuid, String name, String description, String version, List<Tag> tags, PackageURL purl, boolean active, boolean commitIndex) {
|
|
|
|
final Project project = getObjectByUuid(Project.class, uuid);
|
|
|
|
project.setName(name);
|
|
|
|
project.setDescription(description);
|
|
|
|
project.setVersion(version);
|
|
|
|
project.setPurl(purl);
|
2022-09-29 16:38:07 +02:00
|
|
|
|
2022-12-21 10:55:06 +01:00
|
|
|
if (!active && Boolean.TRUE.equals(project.isActive()) && hasActiveChild(project)){
|
2022-09-30 16:24:59 +02:00
|
|
|
throw new IllegalArgumentException("Project cannot be set to inactive, if active children are present.");
|
2022-09-29 16:38:07 +02:00
|
|
|
}
|
2022-10-04 09:59:42 +02:00
|
|
|
project.setActive(active);
|
2021-03-29 13:55:25 -05:00
|
|
|
|
|
|
|
final List<Tag> resolvedTags = resolveTags(tags);
|
|
|
|
bind(project, resolvedTags);
|
|
|
|
|
|
|
|
final Project result = persist(project);
|
2023-10-21 23:36:58 +02:00
|
|
|
Event.dispatch(new IndexEvent(IndexEvent.Action.UPDATE, result));
|
2021-03-29 13:55:25 -05:00
|
|
|
commitSearchIndex(commitIndex, Project.class);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates an existing Project.
|
|
|
|
* @param transientProject the project to update
|
|
|
|
* @param commitIndex specifies if the search index should be committed (an expensive operation)
|
|
|
|
* @return the updated Project
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public Project updateProject(Project transientProject, boolean commitIndex) {
|
|
|
|
final Project project = getObjectByUuid(Project.class, transientProject.getUuid());
|
|
|
|
project.setAuthor(transientProject.getAuthor());
|
|
|
|
project.setPublisher(transientProject.getPublisher());
|
|
|
|
project.setGroup(transientProject.getGroup());
|
|
|
|
project.setName(transientProject.getName());
|
|
|
|
project.setDescription(transientProject.getDescription());
|
|
|
|
project.setVersion(transientProject.getVersion());
|
|
|
|
project.setClassifier(transientProject.getClassifier());
|
|
|
|
project.setCpe(transientProject.getCpe());
|
|
|
|
project.setPurl(transientProject.getPurl());
|
|
|
|
project.setSwidTagId(transientProject.getSwidTagId());
|
2023-04-20 12:51:12 +02:00
|
|
|
project.setExternalReferences(transientProject.getExternalReferences());
|
2021-03-29 13:55:25 -05:00
|
|
|
|
2022-12-21 10:55:06 +01:00
|
|
|
if (Boolean.TRUE.equals(project.isActive()) && !Boolean.TRUE.equals(transientProject.isActive()) && hasActiveChild(project)){
|
2022-09-30 16:24:59 +02:00
|
|
|
throw new IllegalArgumentException("Project cannot be set to inactive if active children are present.");
|
2022-09-29 16:38:07 +02:00
|
|
|
}
|
2022-10-04 09:59:42 +02:00
|
|
|
project.setActive(transientProject.isActive());
|
2022-09-19 16:55:06 +02:00
|
|
|
|
|
|
|
if (transientProject.getParent() != null && transientProject.getParent().getUuid() != null) {
|
2022-09-30 16:24:59 +02:00
|
|
|
if (project.getUuid().equals(transientProject.getParent().getUuid())){
|
|
|
|
throw new IllegalArgumentException("A project cannot select itself as a parent");
|
|
|
|
}
|
2022-09-19 16:55:06 +02:00
|
|
|
Project parent = getObjectByUuid(Project.class, transientProject.getParent().getUuid());
|
2022-09-30 16:24:59 +02:00
|
|
|
if (!Boolean.TRUE.equals(parent.isActive())){
|
|
|
|
throw new IllegalArgumentException("An inactive project cannot be selected as a parent");
|
|
|
|
} else if (isChildOf(parent, transientProject.getUuid())){
|
|
|
|
throw new IllegalArgumentException("The new parent project cannot be a child of the current project.");
|
2022-09-19 16:55:06 +02:00
|
|
|
} else {
|
|
|
|
project.setParent(parent);
|
|
|
|
}
|
2022-10-04 09:59:42 +02:00
|
|
|
project.setParent(parent);
|
2022-09-19 16:55:06 +02:00
|
|
|
}else {
|
|
|
|
project.setParent(null);
|
|
|
|
}
|
|
|
|
|
2021-03-29 13:55:25 -05:00
|
|
|
final List<Tag> resolvedTags = resolveTags(transientProject.getTags());
|
|
|
|
bind(project, resolvedTags);
|
|
|
|
|
|
|
|
final Project result = persist(project);
|
2023-10-21 23:36:58 +02:00
|
|
|
Event.dispatch(new IndexEvent(IndexEvent.Action.UPDATE, result));
|
2021-03-29 13:55:25 -05:00
|
|
|
commitSearchIndex(commitIndex, Project.class);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public Project clone(UUID from, String newVersion, boolean includeTags, boolean includeProperties,
|
2022-11-05 16:09:33 +01:00
|
|
|
boolean includeComponents, boolean includeServices, boolean includeAuditHistory,
|
|
|
|
boolean includeACL) {
|
2021-03-29 13:55:25 -05:00
|
|
|
final Project source = getObjectByUuid(Project.class, from, Project.FetchGroup.ALL.name());
|
|
|
|
if (source == null) {
|
2023-08-20 20:37:52 +02:00
|
|
|
LOGGER.warn("Project with UUID %s was supposed to be cloned, but it does not exist anymore".formatted(from));
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (doesProjectExist(source.getName(), newVersion)) {
|
|
|
|
// Project cloning is an asynchronous process. When receiving the clone request, we already perform
|
|
|
|
// this check. It is possible though that a project with the new version is created synchronously
|
|
|
|
// between the clone event being dispatched, and it being processed.
|
|
|
|
LOGGER.warn("Project %s was supposed to be cloned to version %s, but that version already exists"
|
|
|
|
.formatted(source, newVersion));
|
2021-03-29 13:55:25 -05:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
Project project = new Project();
|
|
|
|
project.setAuthor(source.getAuthor());
|
2023-11-08 15:47:25 +01:00
|
|
|
project.setSupplier(source.getSupplier());
|
2021-03-29 13:55:25 -05:00
|
|
|
project.setPublisher(source.getPublisher());
|
|
|
|
project.setGroup(source.getGroup());
|
|
|
|
project.setName(source.getName());
|
|
|
|
project.setDescription(source.getDescription());
|
|
|
|
project.setVersion(newVersion);
|
|
|
|
project.setClassifier(source.getClassifier());
|
|
|
|
project.setActive(source.isActive());
|
|
|
|
project.setCpe(source.getCpe());
|
|
|
|
project.setPurl(source.getPurl());
|
|
|
|
project.setSwidTagId(source.getSwidTagId());
|
|
|
|
if (includeComponents && includeServices) {
|
|
|
|
project.setDirectDependencies(source.getDirectDependencies());
|
|
|
|
}
|
|
|
|
project.setParent(source.getParent());
|
|
|
|
project = persist(project);
|
|
|
|
|
|
|
|
if (includeTags) {
|
|
|
|
for (final Tag tag: source.getTags()) {
|
|
|
|
tag.getProjects().add(project);
|
|
|
|
persist(tag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (includeProperties && source.getProperties() != null) {
|
|
|
|
for (final ProjectProperty sourceProperty: source.getProperties()) {
|
|
|
|
final ProjectProperty property = new ProjectProperty();
|
|
|
|
property.setProject(project);
|
|
|
|
property.setPropertyType(sourceProperty.getPropertyType());
|
|
|
|
property.setGroupName(sourceProperty.getGroupName());
|
|
|
|
property.setPropertyName(sourceProperty.getPropertyName());
|
|
|
|
property.setPropertyValue(sourceProperty.getPropertyValue());
|
|
|
|
property.setDescription(sourceProperty.getDescription());
|
|
|
|
persist(property);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final Map<Long, Component> clonedComponents = new HashMap<>();
|
|
|
|
if (includeComponents) {
|
|
|
|
final List<Component> sourceComponents = getAllComponents(source);
|
|
|
|
if (sourceComponents != null) {
|
|
|
|
for (final Component sourceComponent: sourceComponents) {
|
|
|
|
final Component clonedComponent = cloneComponent(sourceComponent, project, false);
|
|
|
|
// Add vulnerabilties and finding attribution from the source component to the cloned component
|
|
|
|
for (Vulnerability vuln: sourceComponent.getVulnerabilities()) {
|
|
|
|
final FindingAttribution sourceAttribution = this.getFindingAttribution(vuln, sourceComponent);
|
|
|
|
this.addVulnerability(vuln, clonedComponent, sourceAttribution.getAnalyzerIdentity(), sourceAttribution.getAlternateIdentifier(), sourceAttribution.getReferenceUrl());
|
|
|
|
}
|
|
|
|
clonedComponents.put(sourceComponent.getId(), clonedComponent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-20 20:37:52 +02:00
|
|
|
if (includeServices) {
|
|
|
|
final List<ServiceComponent> sourceServices = getAllServiceComponents(source);
|
|
|
|
if (sourceServices != null) {
|
|
|
|
for (final ServiceComponent sourceService : sourceServices) {
|
|
|
|
cloneServiceComponent(sourceService, project, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-29 13:55:25 -05:00
|
|
|
if (includeAuditHistory && includeComponents) {
|
|
|
|
final List<Analysis> analyses = super.getAnalyses(source);
|
|
|
|
if (analyses != null) {
|
|
|
|
for (final Analysis sourceAnalysis: analyses) {
|
|
|
|
Analysis analysis = new Analysis();
|
|
|
|
analysis.setAnalysisState(sourceAnalysis.getAnalysisState());
|
|
|
|
final Component clonedComponent = clonedComponents.get(sourceAnalysis.getComponent().getId());
|
|
|
|
if (clonedComponent == null) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
analysis.setComponent(clonedComponent);
|
|
|
|
analysis.setVulnerability(sourceAnalysis.getVulnerability());
|
|
|
|
analysis.setSuppressed(sourceAnalysis.isSuppressed());
|
2022-02-12 15:53:08 -06:00
|
|
|
analysis.setAnalysisResponse(sourceAnalysis.getAnalysisResponse());
|
|
|
|
analysis.setAnalysisJustification(sourceAnalysis.getAnalysisJustification());
|
|
|
|
analysis.setAnalysisState(sourceAnalysis.getAnalysisState());
|
|
|
|
analysis.setAnalysisDetails(sourceAnalysis.getAnalysisDetails());
|
2021-03-29 13:55:25 -05:00
|
|
|
analysis = persist(analysis);
|
|
|
|
if (sourceAnalysis.getAnalysisComments() != null) {
|
|
|
|
for (final AnalysisComment sourceComment: sourceAnalysis.getAnalysisComments()) {
|
|
|
|
final AnalysisComment analysisComment = new AnalysisComment();
|
|
|
|
analysisComment.setAnalysis(analysis);
|
|
|
|
analysisComment.setTimestamp(sourceComment.getTimestamp());
|
|
|
|
analysisComment.setComment(sourceComment.getComment());
|
|
|
|
analysisComment.setCommenter(sourceComment.getCommenter());
|
|
|
|
persist(analysisComment);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-05 16:09:33 +01:00
|
|
|
if (includeACL) {
|
|
|
|
List<Team> accessTeams = source.getAccessTeams();
|
2023-03-02 15:02:40 +01:00
|
|
|
if (!CollectionUtils.isEmpty(accessTeams)) {
|
2022-11-06 20:43:03 +01:00
|
|
|
project.setAccessTeams(new ArrayList<>(accessTeams));
|
2022-11-05 16:09:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-29 13:55:25 -05:00
|
|
|
project = getObjectById(Project.class, project.getId());
|
2023-10-21 23:36:58 +02:00
|
|
|
Event.dispatch(new IndexEvent(IndexEvent.Action.CREATE, project));
|
2021-03-29 13:55:25 -05:00
|
|
|
commitSearchIndex(true, Project.class);
|
|
|
|
return project;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deletes a Project and all objects dependant on the project.
|
|
|
|
* @param project the Project to delete
|
2022-05-11 21:17:15 +02:00
|
|
|
* @param commitIndex specifies if the search index should be committed (an expensive operation)
|
2021-03-29 13:55:25 -05:00
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2022-05-11 21:17:15 +02:00
|
|
|
public void recursivelyDelete(final Project project, final boolean commitIndex) {
|
2021-03-29 13:55:25 -05:00
|
|
|
if (project.getChildren() != null) {
|
|
|
|
for (final Project child: project.getChildren()) {
|
2022-05-11 21:17:15 +02:00
|
|
|
recursivelyDelete(child, false);
|
2021-03-29 13:55:25 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
pm.getFetchPlan().setDetachmentOptions(FetchPlan.DETACH_LOAD_FIELDS);
|
|
|
|
final Project result = pm.getObjectById(Project.class, project.getId());
|
2023-10-21 23:36:58 +02:00
|
|
|
Event.dispatch(new IndexEvent(IndexEvent.Action.DELETE, result));
|
2022-05-11 21:17:15 +02:00
|
|
|
commitSearchIndex(commitIndex, Project.class);
|
2021-03-29 13:55:25 -05:00
|
|
|
|
|
|
|
deleteAnalysisTrail(project);
|
|
|
|
deleteViolationAnalysisTrail(project);
|
|
|
|
deleteMetrics(project);
|
2021-06-08 22:48:05 -05:00
|
|
|
deleteFindingAttributions(project);
|
|
|
|
deletePolicyViolations(project);
|
|
|
|
deleteComponents(project);
|
|
|
|
|
2021-03-29 13:55:25 -05:00
|
|
|
for (final ServiceComponent s: getAllServiceComponents(project)) {
|
|
|
|
recursivelyDelete(s, false);
|
|
|
|
}
|
|
|
|
deleteBoms(project);
|
2022-05-08 00:31:27 -05:00
|
|
|
deleteVexs(project);
|
2021-03-29 13:55:25 -05:00
|
|
|
removeProjectFromNotificationRules(project);
|
2022-08-03 21:57:46 +02:00
|
|
|
removeProjectFromPolicies(project);
|
2023-11-08 15:47:25 +01:00
|
|
|
delete(project.getMetadata());
|
2021-03-29 13:55:25 -05:00
|
|
|
delete(project.getProperties());
|
|
|
|
delete(getAllBoms(project));
|
|
|
|
delete(project.getChildren());
|
|
|
|
delete(project);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a key/value pair (ProjectProperty) for the specified Project.
|
|
|
|
* @param project the Project to create the property for
|
|
|
|
* @param groupName the group name of the property
|
|
|
|
* @param propertyName the name of the property
|
|
|
|
* @param propertyValue the value of the property
|
|
|
|
* @param propertyType the type of property
|
|
|
|
* @param description a description of the property
|
|
|
|
* @return the created ProjectProperty object
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public ProjectProperty createProjectProperty(final Project project, final String groupName, final String propertyName,
|
|
|
|
final String propertyValue, final ProjectProperty.PropertyType propertyType,
|
|
|
|
final String description) {
|
|
|
|
final ProjectProperty property = new ProjectProperty();
|
|
|
|
property.setProject(project);
|
|
|
|
property.setGroupName(groupName);
|
|
|
|
property.setPropertyName(propertyName);
|
|
|
|
property.setPropertyValue(propertyValue);
|
|
|
|
property.setPropertyType(propertyType);
|
|
|
|
property.setDescription(description);
|
|
|
|
return persist(property);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a ProjectProperty with the specified groupName and propertyName.
|
|
|
|
* @param project the project the property belongs to
|
|
|
|
* @param groupName the group name of the config property
|
|
|
|
* @param propertyName the name of the property
|
|
|
|
* @return a ProjectProperty object
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public ProjectProperty getProjectProperty(final Project project, final String groupName, final String propertyName) {
|
|
|
|
final Query<ProjectProperty> query = this.pm.newQuery(ProjectProperty.class, "project == :project && groupName == :groupName && propertyName == :propertyName");
|
2022-07-23 20:29:51 +02:00
|
|
|
query.setRange(0, 1);
|
2021-03-29 13:55:25 -05:00
|
|
|
return singleResult(query.execute(project, groupName, propertyName));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a List of ProjectProperty's for the specified project.
|
|
|
|
* @param project the project the property belongs to
|
|
|
|
* @return a List ProjectProperty objects
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
public List<ProjectProperty> getProjectProperties(final Project project) {
|
|
|
|
final Query<ProjectProperty> query = this.pm.newQuery(ProjectProperty.class, "project == :project");
|
|
|
|
query.setOrdering("groupName asc, propertyName asc");
|
|
|
|
return (List<ProjectProperty>)query.execute(project);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Binds the two objects together in a corresponding join table.
|
|
|
|
* @param project a Project object
|
|
|
|
* @param tags a List of Tag objects
|
|
|
|
*/
|
|
|
|
@SuppressWarnings("unchecked")
|
2022-01-13 19:27:34 +01:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public void bind(Project project, List<Tag> tags) {
|
|
|
|
final Query<Tag> query = pm.newQuery(Tag.class, "projects.contains(:project)");
|
|
|
|
final List<Tag> currentProjectTags = (List<Tag>)query.execute(project);
|
|
|
|
pm.currentTransaction().begin();
|
|
|
|
for (final Tag tag: currentProjectTags) {
|
|
|
|
if (!tags.contains(tag)) {
|
|
|
|
tag.getProjects().remove(project);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
project.setTags(tags);
|
|
|
|
for (final Tag tag: tags) {
|
2022-01-13 19:27:34 +01:00
|
|
|
final List<Project> projects = tag.getProjects();
|
|
|
|
if (!projects.contains(project)) {
|
|
|
|
projects.add(project);
|
|
|
|
}
|
2021-03-29 13:55:25 -05:00
|
|
|
}
|
|
|
|
pm.currentTransaction().commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the last time a bom was imported.
|
|
|
|
* @param date the date of the last bom import
|
|
|
|
* @param bomFormat the format and version of the bom format
|
|
|
|
* @return the updated Project
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-03-29 13:55:25 -05:00
|
|
|
public Project updateLastBomImport(Project p, Date date, String bomFormat) {
|
|
|
|
final Project project = getObjectById(Project.class, p.getId());
|
|
|
|
project.setLastBomImport(date);
|
|
|
|
project.setLastBomImportFormat(bomFormat);
|
|
|
|
return persist(project);
|
|
|
|
}
|
2021-04-01 23:01:28 -05:00
|
|
|
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-06-27 00:12:43 -05:00
|
|
|
public boolean hasAccess(final Principal principal, final Project project) {
|
|
|
|
if (isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED)) {
|
2023-06-27 23:02:45 +02:00
|
|
|
if (principal instanceof final UserPrincipal userPrincipal) {
|
2021-07-16 15:13:51 -05:00
|
|
|
if (super.hasAccessManagementPermission(userPrincipal)) {
|
|
|
|
return true;
|
|
|
|
}
|
2022-04-10 17:30:45 -05:00
|
|
|
if (userPrincipal.getTeams() != null) {
|
|
|
|
for (final Team userInTeam : userPrincipal.getTeams()) {
|
|
|
|
for (final Team accessTeam : project.getAccessTeams()) {
|
|
|
|
if (userInTeam.getId() == accessTeam.getId()) {
|
|
|
|
return true;
|
|
|
|
}
|
2021-06-27 00:12:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-06-27 23:02:45 +02:00
|
|
|
} else if (principal instanceof final ApiKey apiKey ){
|
2021-07-16 15:13:51 -05:00
|
|
|
if (super.hasAccessManagementPermission(apiKey)) {
|
|
|
|
return true;
|
|
|
|
}
|
2022-04-10 17:30:45 -05:00
|
|
|
if (apiKey.getTeams() != null) {
|
|
|
|
for (final Team userInTeam : apiKey.getTeams()) {
|
|
|
|
for (final Team accessTeam : project.getAccessTeams()) {
|
|
|
|
if (userInTeam.getId() == accessTeam.getId()) {
|
|
|
|
return true;
|
|
|
|
}
|
2021-06-27 00:12:43 -05:00
|
|
|
}
|
|
|
|
}
|
2021-04-01 23:01:28 -05:00
|
|
|
}
|
2021-07-16 15:13:51 -05:00
|
|
|
} else if (principal == null) {
|
|
|
|
// This is a system request being made (e.g. MetricsUpdateTask, etc) where there isn't a principal
|
|
|
|
return true;
|
2021-04-01 23:01:28 -05:00
|
|
|
}
|
2021-06-27 00:12:43 -05:00
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-16 15:13:51 -05:00
|
|
|
/**
|
|
|
|
* A similar method exists in ComponentQueryManager
|
|
|
|
*/
|
2021-06-27 00:12:43 -05:00
|
|
|
private void preprocessACLs(final Query<Project> query, final String inputFilter, final Map<String, Object> params, final boolean bypass) {
|
|
|
|
if (super.principal != null && isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && !bypass) {
|
|
|
|
final List<Team> teams;
|
2023-06-27 23:02:45 +02:00
|
|
|
if (super.principal instanceof final UserPrincipal userPrincipal) {
|
2021-06-27 00:12:43 -05:00
|
|
|
teams = userPrincipal.getTeams();
|
2021-07-16 15:13:51 -05:00
|
|
|
if (super.hasAccessManagementPermission(userPrincipal)) {
|
2021-06-27 00:12:43 -05:00
|
|
|
query.setFilter(inputFilter);
|
|
|
|
return;
|
|
|
|
}
|
2021-04-01 23:01:28 -05:00
|
|
|
} else {
|
2021-06-27 00:12:43 -05:00
|
|
|
final ApiKey apiKey = ((ApiKey) super.principal);
|
|
|
|
teams = apiKey.getTeams();
|
2021-07-16 15:13:51 -05:00
|
|
|
if (super.hasAccessManagementPermission(apiKey)) {
|
2021-06-27 00:12:43 -05:00
|
|
|
query.setFilter(inputFilter);
|
|
|
|
return;
|
|
|
|
}
|
2021-04-01 23:01:28 -05:00
|
|
|
}
|
2021-06-27 00:12:43 -05:00
|
|
|
if (teams != null && teams.size() > 0) {
|
|
|
|
final StringBuilder sb = new StringBuilder();
|
|
|
|
for (int i = 0, teamsSize = teams.size(); i < teamsSize; i++) {
|
2021-08-06 23:28:06 +02:00
|
|
|
final Team team = super.getObjectById(Team.class, teams.get(i).getId());
|
2021-06-27 00:12:43 -05:00
|
|
|
sb.append(" accessTeams.contains(:team").append(i).append(") ");
|
|
|
|
params.put("team" + i, team);
|
2021-08-06 23:28:06 +02:00
|
|
|
if (i < teamsSize-1) {
|
2021-06-27 00:12:43 -05:00
|
|
|
sb.append(" || ");
|
|
|
|
}
|
|
|
|
}
|
2023-03-09 11:46:41 +01:00
|
|
|
if (inputFilter != null && !inputFilter.isBlank()) {
|
2021-06-27 00:12:43 -05:00
|
|
|
query.setFilter(inputFilter + " && (" + sb.toString() + ")");
|
|
|
|
} else {
|
|
|
|
query.setFilter(sb.toString());
|
|
|
|
}
|
|
|
|
}
|
2021-08-01 02:02:30 -05:00
|
|
|
} else if (StringUtils.trimToNull(inputFilter) != null) {
|
2021-06-27 00:12:43 -05:00
|
|
|
query.setFilter(inputFilter);
|
2021-04-01 23:01:28 -05:00
|
|
|
}
|
2021-06-27 00:12:43 -05:00
|
|
|
}
|
|
|
|
|
2022-04-12 13:33:01 +02:00
|
|
|
/**
|
|
|
|
* Updates a Project ACL to add the principals Team to the AccessTeams
|
|
|
|
* This only happens if Portfolio Access Control is enabled and the @param principal is an ApyKey
|
|
|
|
* For a UserPrincipal we don't know which Team(s) to add to the ACL,
|
|
|
|
* See https://github.com/DependencyTrack/dependency-track/issues/1435
|
|
|
|
* @param project
|
|
|
|
* @param principal
|
|
|
|
* @return True if ACL was updated
|
|
|
|
*/
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2022-04-12 13:33:01 +02:00
|
|
|
public boolean updateNewProjectACL(Project project, Principal principal) {
|
2023-06-27 23:02:45 +02:00
|
|
|
if (isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && principal instanceof ApiKey apiKey) {
|
2022-04-12 13:33:01 +02:00
|
|
|
final var apiTeam = apiKey.getTeams().stream().findFirst();
|
|
|
|
if (apiTeam.isPresent()) {
|
|
|
|
LOGGER.debug("adding Team to ACL of newly created project");
|
|
|
|
final Team team = getObjectByUuid(Team.class, apiTeam.get().getUuid());
|
|
|
|
project.addAccessTeam(team);
|
|
|
|
persist(project);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
LOGGER.warn("API Key without a Team, unable to assign team ACL to project.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-06-27 00:12:43 -05:00
|
|
|
public boolean hasAccessManagementPermission(final UserPrincipal userPrincipal) {
|
|
|
|
for (Permission permission: getEffectivePermissions(userPrincipal)) {
|
|
|
|
if (Permissions.ACCESS_MANAGEMENT.name().equals(permission.getName())) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2021-04-01 23:01:28 -05:00
|
|
|
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2021-06-27 00:12:43 -05:00
|
|
|
public boolean hasAccessManagementPermission(final ApiKey apiKey) {
|
|
|
|
return hasPermission(apiKey, Permissions.ACCESS_MANAGEMENT.name());
|
2021-04-01 23:01:28 -05:00
|
|
|
}
|
2022-07-15 23:38:32 +02:00
|
|
|
|
2022-09-19 16:55:06 +02:00
|
|
|
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2022-09-19 16:55:06 +02:00
|
|
|
public PaginatedResult getChildrenProjects(final UUID uuid, final boolean includeMetrics, final boolean excludeInactive) {
|
|
|
|
final PaginatedResult result;
|
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
|
|
|
if (orderBy == null) {
|
|
|
|
query.setOrdering("name asc, version desc");
|
|
|
|
}
|
|
|
|
|
|
|
|
var filterBuilder = new ProjectQueryFilterBuilder()
|
|
|
|
.excludeInactive(excludeInactive)
|
|
|
|
.withParent(uuid);
|
|
|
|
|
|
|
|
if (filter != null) {
|
|
|
|
final String filterString = ".*" + filter.toLowerCase() + ".*";
|
|
|
|
final Tag tag = getTagByName(filter.trim());
|
|
|
|
|
|
|
|
if (tag != null) {
|
|
|
|
filterBuilder = filterBuilder.withFuzzyNameOrExactTag(filterString, tag);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
filterBuilder = filterBuilder.withFuzzyName(filterString);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final String queryFilter = filterBuilder.buildFilter();
|
|
|
|
final Map<String, Object> params = filterBuilder.getParams();
|
|
|
|
|
|
|
|
preprocessACLs(query, queryFilter, params, false);
|
2022-10-18 09:41:11 +02:00
|
|
|
query.getFetchPlan().addGroup(Project.FetchGroup.ALL.name());
|
2022-09-19 16:55:06 +02:00
|
|
|
result = execute(query, params);
|
|
|
|
if (includeMetrics) {
|
|
|
|
// Populate each Project object in the paginated result with transitive related
|
|
|
|
// data to minimize the number of round trips a client needs to make, process, and render.
|
|
|
|
for (Project project : result.getList(Project.class)) {
|
|
|
|
project.setMetrics(getMostRecentProjectMetrics(project));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2022-12-02 18:21:49 +01:00
|
|
|
public PaginatedResult getChildrenProjects(final Classifier classifier, final UUID uuid, final boolean includeMetrics, final boolean excludeInactive) {
|
|
|
|
final PaginatedResult result;
|
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
|
|
|
if (orderBy == null) {
|
|
|
|
query.setOrdering("name asc");
|
|
|
|
}
|
|
|
|
|
|
|
|
final var filterBuilder = new ProjectQueryFilterBuilder()
|
|
|
|
.excludeInactive(excludeInactive)
|
|
|
|
.withParent(uuid)
|
|
|
|
.withClassifier(classifier);
|
|
|
|
|
|
|
|
final String queryFilter = filterBuilder.buildFilter();
|
|
|
|
final Map<String, Object> params = filterBuilder.getParams();
|
|
|
|
|
|
|
|
preprocessACLs(query, queryFilter, params, false);
|
|
|
|
query.getFetchPlan().addGroup(Project.FetchGroup.ALL.name());
|
|
|
|
result = execute(query, params);
|
|
|
|
if (includeMetrics) {
|
|
|
|
// Populate each Project object in the paginated result with transitive related
|
|
|
|
// data to minimize the number of round trips a client needs to make, process, and render.
|
|
|
|
for (Project project : result.getList(Project.class)) {
|
|
|
|
project.setMetrics(getMostRecentProjectMetrics(project));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2022-12-02 18:21:49 +01:00
|
|
|
public PaginatedResult getChildrenProjects(final Tag tag, final UUID uuid, final boolean includeMetrics, final boolean excludeInactive) {
|
|
|
|
final PaginatedResult result;
|
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
|
|
|
if (orderBy == null) {
|
|
|
|
query.setOrdering("name asc");
|
|
|
|
}
|
|
|
|
|
|
|
|
var filterBuilder = new ProjectQueryFilterBuilder()
|
|
|
|
.excludeInactive(excludeInactive)
|
|
|
|
.withParent(uuid)
|
|
|
|
.withTag(tag);
|
|
|
|
|
|
|
|
if (filter != null) {
|
|
|
|
final String filterString = ".*" + filter.toLowerCase() + ".*";
|
|
|
|
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 Project object in the paginated result with transitive related
|
|
|
|
// data to minimize the number of round trips a client needs to make, process, and render.
|
|
|
|
for (Project project : result.getList(Project.class)) {
|
|
|
|
project.setMetrics(getMostRecentProjectMetrics(project));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2022-12-02 18:21:49 +01:00
|
|
|
public PaginatedResult getProjectsWithoutDescendantsOf(final boolean exludeInactive, final Project project){
|
2022-09-19 16:55:06 +02:00
|
|
|
final PaginatedResult result;
|
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
|
|
|
if (orderBy == null) {
|
|
|
|
query.setOrdering("name asc, version desc");
|
|
|
|
}
|
|
|
|
|
2022-12-02 18:21:49 +01:00
|
|
|
var filterBuilder = new ProjectQueryFilterBuilder()
|
|
|
|
.excludeInactive(exludeInactive);
|
|
|
|
|
|
|
|
if (filter != null) {
|
|
|
|
final String filterString = ".*" + filter.toLowerCase() + ".*";
|
|
|
|
final Tag tag = getTagByName(filter.trim());
|
|
|
|
|
|
|
|
if (tag != null) {
|
|
|
|
filterBuilder = filterBuilder.withFuzzyNameOrExactTag(filterString, tag);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
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);
|
|
|
|
|
|
|
|
result.setObjects(result.getList(Project.class).stream().filter(p -> !isChildOf(p, project.getUuid()) && !p.getUuid().equals(project.getUuid())).toList());
|
|
|
|
result.setTotal(result.getObjects().size());
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-06-27 23:02:45 +02:00
|
|
|
@Override
|
2022-12-02 18:21:49 +01:00
|
|
|
public PaginatedResult getProjectsWithoutDescendantsOf(final String name, final boolean excludeInactive, Project project){
|
|
|
|
final PaginatedResult result;
|
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
|
|
|
if (orderBy == null) {
|
|
|
|
query.setOrdering("name asc, version desc");
|
|
|
|
}
|
|
|
|
|
|
|
|
var filterBuilder = new ProjectQueryFilterBuilder()
|
|
|
|
.excludeInactive(excludeInactive)
|
|
|
|
.withName(name);
|
2022-09-19 16:55:06 +02:00
|
|
|
|
|
|
|
if (filter != null) {
|
|
|
|
final String filterString = ".*" + filter.toLowerCase() + ".*";
|
|
|
|
final Tag tag = getTagByName(filter.trim());
|
|
|
|
|
|
|
|
if (tag != null) {
|
|
|
|
filterBuilder = filterBuilder.withFuzzyNameOrExactTag(filterString, tag);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
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);
|
2022-09-30 16:24:59 +02:00
|
|
|
|
2022-12-02 18:21:49 +01:00
|
|
|
result.setObjects(result.getList(Project.class).stream().filter(p -> !isChildOf(p, project.getUuid()) && !p.getUuid().equals(project.getUuid())).toList());
|
2022-09-30 16:24:59 +02:00
|
|
|
result.setTotal(result.getObjects().size());
|
|
|
|
|
2022-09-19 16:55:06 +02:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-08-20 20:37:52 +02:00
|
|
|
/**
|
|
|
|
* Check whether a {@link Project} with a given {@code name} and {@code version} exists.
|
|
|
|
*
|
|
|
|
* @param name Name of the {@link Project} to check for
|
|
|
|
* @param version Version of the {@link Project} to check for
|
|
|
|
* @return {@code true} when a matching {@link Project} exists, otherwise {@code false}
|
|
|
|
* @since 4.9.0
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public boolean doesProjectExist(final String name, final String version) {
|
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
2023-10-02 23:09:22 +02:00
|
|
|
if (version != null) {
|
2023-08-20 20:37:52 +02:00
|
|
|
query.setFilter("name == :name && version == :version");
|
|
|
|
query.setNamedParameters(Map.of(
|
|
|
|
"name", name,
|
|
|
|
"version", version
|
|
|
|
));
|
2023-10-02 23:09:22 +02:00
|
|
|
} else {
|
|
|
|
// Version is optional for projects, but using null
|
|
|
|
// for parameter values bypasses the query compilation cache.
|
|
|
|
// https://github.com/DependencyTrack/dependency-track/issues/2540
|
|
|
|
query.setFilter("name == :name && version == null");
|
|
|
|
query.setNamedParameters(Map.of(
|
|
|
|
"name", name
|
|
|
|
));
|
|
|
|
}
|
|
|
|
query.setResult("count(this)");
|
|
|
|
try {
|
2023-08-20 20:37:52 +02:00
|
|
|
return query.executeResultUnique(Long.class) > 0;
|
|
|
|
} finally {
|
|
|
|
query.closeAll();
|
|
|
|
}
|
|
|
|
}
|
2022-09-30 16:24:59 +02:00
|
|
|
|
|
|
|
private static boolean isChildOf(Project project, UUID uuid) {
|
2022-09-19 16:55:06 +02:00
|
|
|
boolean isChild = false;
|
|
|
|
if (project.getParent() != null){
|
|
|
|
if (project.getParent().getUuid().equals(uuid)){
|
|
|
|
return true;
|
|
|
|
} else {
|
2022-09-30 16:24:59 +02:00
|
|
|
isChild = isChildOf(project.getParent(), uuid);
|
2022-09-19 16:55:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return isChild;
|
|
|
|
}
|
2022-09-29 16:38:07 +02:00
|
|
|
|
|
|
|
private static boolean hasActiveChild(Project project) {
|
|
|
|
boolean hasActiveChild = false;
|
|
|
|
if (project.getChildren() != null){
|
|
|
|
for (Project child: project.getChildren()) {
|
2022-12-21 10:55:06 +01:00
|
|
|
if (Boolean.TRUE.equals(child.isActive()) || hasActiveChild) {
|
2022-09-29 16:38:07 +02:00
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
hasActiveChild = hasActiveChild(child);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return hasActiveChild;
|
|
|
|
}
|
2023-03-12 09:22:42 +01:00
|
|
|
|
|
|
|
private List<ProjectVersion> getProjectVersions(Project project) {
|
2023-06-27 23:02:45 +02:00
|
|
|
final Query<Project> query = pm.newQuery(Project.class);
|
|
|
|
query.setFilter("name == :name");
|
2023-03-12 09:22:42 +01:00
|
|
|
query.setParameters(project.getName());
|
2023-06-27 23:02:45 +02:00
|
|
|
query.setResult("uuid, version");
|
|
|
|
return query.executeResultList(ProjectVersion.class);
|
2023-03-12 09:22:42 +01:00
|
|
|
}
|
2021-03-29 13:55:25 -05:00
|
|
|
}
|