Skip to content
Merged
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
Expand Up @@ -18,6 +18,7 @@
*/
package org.dependencytrack.persistence;

import alpine.common.logging.Logger;
import alpine.event.framework.Event;
import alpine.model.ApiKey;
import alpine.model.Permission;
Expand Down Expand Up @@ -53,6 +54,8 @@

final class ProjectQueryManager extends QueryManager implements IQueryManager {

private static final Logger LOGGER = Logger.getLogger(ProjectQueryManager.class);

/**
* Constructs a new QueryManager.
* @param pm a PersistenceManager object
Expand Down Expand Up @@ -741,6 +744,32 @@ private void preprocessACLs(final Query<Project> query, final String inputFilter
}
}

/**
* 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
*/
public boolean updateNewProjectACL(Project project, Principal principal) {
if (isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED) && principal instanceof ApiKey) {
ApiKey apiKey = (ApiKey) principal;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this what we want to do? If I cloned a project, I would expect all users and teams that had access to the project would also have access to the new project. This method appears to only grant access to the API key that performed the cloning.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This PR only covers creation of new projects. I was thinking about a separate PR for cloning, or do you prefer to have it combined here? It would add a field to the Clone Project request.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah correct. Different things. A separate PR for the cloning would be good.

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;
}

public boolean hasAccessManagementPermission(final UserPrincipal userPrincipal) {
for (Permission permission: getEffectivePermissions(userPrincipal)) {
if (Permissions.ACCESS_MANAGEMENT.name().equals(permission.getName())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,10 @@ public Project updateProject(Project transientProject, boolean commitIndex) {
return getProjectQueryManager().updateProject(transientProject, commitIndex);
}

public boolean updateNewProjectACL(Project transientProject, Principal principal) {
return getProjectQueryManager().updateNewProjectACL(transientProject, principal);
}

public Project clone(UUID from, String newVersion, boolean includeTags, boolean includeProperties,
boolean includeComponents, boolean includeServices, boolean includeAuditHistory) {
return getProjectQueryManager().clone(from, newVersion, includeTags, includeProperties,
Expand Down
11 changes: 7 additions & 4 deletions src/main/java/org/dependencytrack/resources/v1/BomResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.InputStream;
import java.security.Principal;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -195,7 +196,7 @@ public Response exportComponentAsCycloneDx (
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Upload a supported bill of material format document",
notes = "Expects CycloneDX along and a valid project UUID. If a UUID is not specified then the projectName and projectVersion must be specified. Optionally, if autoCreate is specified and 'true' and the project does not exist, the project will be created. In this scenario, the principal making the request will additionally need the PORTFOLIO_MANAGEMENT or PROJECT_CREATION_UPLOAD permission."
notes = "Expects CycloneDX along and a valid project UUID. If a UUID is not specified, then the projectName and projectVersion must be specified. Optionally, if autoCreate is specified and 'true' and the project does not exist, the project will be created. In this scenario, the principal making the request will additionally need the PORTFOLIO_MANAGEMENT or PROJECT_CREATION_UPLOAD permission."
)
@ApiResponses(value = {
@ApiResponse(code = 401, message = "Unauthorized"),
Expand Down Expand Up @@ -225,7 +226,8 @@ public Response uploadBom(BomSubmitRequest request) {
if (project == null && request.isAutoCreate()) {
if (hasPermission(Permissions.Constants.PORTFOLIO_MANAGEMENT) || hasPermission(Permissions.Constants.PROJECT_CREATION_UPLOAD)) {
project = qm.createProject(StringUtils.trimToNull(request.getProjectName()), null, StringUtils.trimToNull(request.getProjectVersion()), null, null, null, true, true);
//TODO - If portfolio access control is enabled, retrieve the principal (ApiKey only) and automatically grant access to the project to the team the key belongs to.
Principal principal = getPrincipal();
qm.updateNewProjectACL(project, principal);
} else {
return Response.status(Response.Status.UNAUTHORIZED).entity("The principal does not have permission to create project.").build();
}
Expand All @@ -240,7 +242,7 @@ public Response uploadBom(BomSubmitRequest request) {
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(
value = "Upload a supported bill of material format document",
notes = "Expects CycloneDX along and a valid project UUID. If a UUID is not specified, than the projectName and projectVersion must be specified. Optionally, if autoCreate is specified and 'true' and the project does not exist, the project will be created. In this scenario, the principal making the request will additionally need the PORTFOLIO_MANAGEMENT or PROJECT_CREATION_UPLOAD permission."
notes = "Expects CycloneDX along and a valid project UUID. If a UUID is not specified, then the projectName and projectVersion must be specified. Optionally, if autoCreate is specified and 'true' and the project does not exist, the project will be created. In this scenario, the principal making the request will additionally need the PORTFOLIO_MANAGEMENT or PROJECT_CREATION_UPLOAD permission."
)
@ApiResponses(value = {
@ApiResponse(code = 401, message = "Unauthorized"),
Expand Down Expand Up @@ -268,7 +270,8 @@ public Response uploadBom(@FormDataParam("project") String projectUuid,
if (project == null && autoCreate) {
if (hasPermission(Permissions.Constants.PORTFOLIO_MANAGEMENT) || hasPermission(Permissions.Constants.PROJECT_CREATION_UPLOAD)) {
project = qm.createProject(trimmedProjectName, null, trimmedProjectVersion, null, null, null, true, true);
//TODO - If portfolio access control is enabled, retrieve the principal (ApiKey only) and automatically grant access to the project to the team the key belongs to.
Principal principal = getPrincipal();
qm.updateNewProjectACL(project, principal);
} else {
return Response.status(Response.Status.UNAUTHORIZED).entity("The principal does not have permission to create project.").build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
import org.dependencytrack.persistence.QueryManager;
import org.dependencytrack.resources.v1.vo.CloneProjectRequest;

import java.security.Principal;

import javax.validation.Validator;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
Expand Down Expand Up @@ -211,6 +213,8 @@ public Response createProject(Project jsonProject) {
Project project = qm.getProject(StringUtils.trimToNull(jsonProject.getName()), StringUtils.trimToNull(jsonProject.getVersion()));
if (project == null) {
project = qm.createProject(jsonProject, jsonProject.getTags(), true);
Principal principal = getPrincipal();
qm.updateNewProjectACL(project, principal);
LOGGER.info("Project " + project.toString() + " created by " + super.getPrincipal().getName());
return Response.status(Response.Status.CREATED).entity(project).build();
} else {
Expand Down