Skip to content

Commit

Permalink
Merge pull request #365 from DependencyTrack/port-pr-3068-in-DT
Browse files Browse the repository at this point in the history
Fix NullPointerException when checking for existence of projects without version - port upstream fix
  • Loading branch information
nscuro authored Oct 20, 2023
2 parents 404441e + 455a569 commit 492bd76
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,15 @@ ProjectQueryFilterBuilder withName(String name) {
}

ProjectQueryFilterBuilder withVersion(String version) {
params.put("version", version);
filterCriteria.add("(version == :version)");
if(version == null) {
// Version is optional for projects, but using null
// for parameter values bypasses the query compilation cache.
// https://github.com/DependencyTrack/dependency-track/issues/2540
filterCriteria.add("(version == null)");
} else {
params.put("version", version);
filterCriteria.add("(version == :version)");
}
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.dependencytrack.model.Project;
import org.dependencytrack.model.ProjectProperty;
import org.dependencytrack.model.ProjectVersion;
import org.dependencytrack.model.ServiceComponent;
import org.dependencytrack.model.Tag;
import org.dependencytrack.model.Vulnerability;
import org.dependencytrack.notification.NotificationConstants;
Expand Down Expand Up @@ -624,6 +625,15 @@ public Project clone(UUID from, String newVersion, boolean includeTags, boolean
boolean includeACL) {
final Project source = getObjectByUuid(Project.class, from, Project.FetchGroup.ALL.name());
if (source == null) {
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));
return null;
}
Project project = new Project();
Expand Down Expand Up @@ -680,6 +690,15 @@ public Project clone(UUID from, String newVersion, boolean includeTags, boolean
}
}

if (includeServices) {
final List<ServiceComponent> sourceServices = getAllServiceComponents(source);
if (sourceServices != null) {
for (final ServiceComponent sourceService : sourceServices) {
cloneServiceComponent(sourceService, project, false);
}
}
}

if (includeAuditHistory && includeComponents) {
final List<Analysis> analyses = super.getAnalyses(source);
if (analyses != null) {
Expand Down Expand Up @@ -1210,6 +1229,40 @@ private List<UUID> getParents(final UUID uuid, final List<UUID> parents) {
return getParents(parentUuid, parents);
}

/**
* 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);
if (version != null) {
query.setFilter("name == :name && version == :version");
query.setNamedParameters(Map.of(
"name", name,
"version", version
));
} 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 {
return query.executeResultUnique(Long.class) > 0;
} finally {
query.closeAll();
}
}

private static boolean isChildOf(Project project, UUID uuid) {
boolean isChild = false;
if (project.getParent() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,10 @@ public PaginatedResult getProjects(final Tag tag) {
return getProjectQueryManager().getProjects(tag);
}

public boolean doesProjectExist(final String name, final String version) {
return getProjectQueryManager().doesProjectExist(name, version);
}

public Tag getTagByName(final String name) {
return getProjectQueryManager().getTagByName(name);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ public ServiceComponent cloneServiceComponent(ServiceComponent sourceService, Pr
service.setNotes(sourceService.getNotes());
service.setVulnerabilities(sourceService.getVulnerabilities());
service.setProject(destinationProject);
return createServiceComponent(sourceService, commitIndex);
return createServiceComponent(service, commitIndex);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,8 @@ public Response createProject(Project jsonProject) {
Project parent = qm.getObjectByUuid(Project.class, jsonProject.getParent().getUuid());
jsonProject.setParent(parent);
}
Project project = qm.getProject(StringUtils.trimToNull(jsonProject.getName()), StringUtils.trimToNull(jsonProject.getVersion()));
if (project == null) {
if (!qm.doesProjectExist(StringUtils.trimToNull(jsonProject.getName()), StringUtils.trimToNull(jsonProject.getVersion()))) {
final Project project;
try {
project = qm.createProject(jsonProject, jsonProject.getTags(), true);
} catch (IllegalArgumentException e) {
Expand Down Expand Up @@ -370,7 +370,7 @@ public Response patchProject(
modified |= setIfDifferent(jsonProject, project, Project::getName, Project::setName);
modified |= setIfDifferent(jsonProject, project, Project::getVersion, Project::setVersion);
// if either name or version has been changed, verify that this new combination does not already exist
if (modified && qm.getProject(project.getName(), project.getVersion()) != null) {
if (modified && qm.doesProjectExist(project.getName(), project.getVersion())) {
return Response.status(Response.Status.CONFLICT).entity("A project with the specified name and version already exists.").build();
}
modified |= setIfDifferent(jsonProject, project, Project::getAuthor, Project::setAuthor);
Expand Down Expand Up @@ -509,7 +509,13 @@ public Response cloneProject(CloneProjectRequest jsonRequest) {
try (QueryManager qm = new QueryManager()) {
final Project sourceProject = qm.getObjectByUuid(Project.class, jsonRequest.getProject(), Project.FetchGroup.ALL.name());
if (sourceProject != null) {
LOGGER.info("Project " + sourceProject.toString() + " is being cloned by " + super.getPrincipal().getName());
if (!qm.hasAccess(super.getPrincipal(), sourceProject)) {
return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
}
if (qm.doesProjectExist(sourceProject.getName(), StringUtils.trimToNull(jsonRequest.getVersion()))) {
return Response.status(Response.Status.CONFLICT).entity("A project with the specified name and version already exists.").build();
}
LOGGER.info("Project " + sourceProject + " is being cloned by " + super.getPrincipal().getName());
Event.dispatch(new CloneProjectEvent(jsonRequest));
return Response.ok().build();
} else {
Expand Down
Loading

0 comments on commit 492bd76

Please sign in to comment.