From 1737c455a29300cddcf971ffd531fe4c5ef3617a Mon Sep 17 00:00:00 2001
From: Thomas Schauer-Koeckeis
Date: Fri, 23 Aug 2024 14:01:06 +0200
Subject: [PATCH 01/13] feat: Team Selection Added additional API point to get
teams Initial Team is given to the project
Signed-off-by: Thomas Schauer-Koeckeis
---
.../dependencytrack/model/AvailableTeams.java | 56 +++
.../org/dependencytrack/model/LittleTeam.java | 53 +++
.../org/dependencytrack/model/Project.java | 39 +-
.../resources/v1/ProjectResource.java | 448 ++++++++----------
.../resources/v1/TeamResource.java | 197 ++++----
5 files changed, 444 insertions(+), 349 deletions(-)
create mode 100644 src/main/java/org/dependencytrack/model/AvailableTeams.java
create mode 100644 src/main/java/org/dependencytrack/model/LittleTeam.java
diff --git a/src/main/java/org/dependencytrack/model/AvailableTeams.java b/src/main/java/org/dependencytrack/model/AvailableTeams.java
new file mode 100644
index 0000000000..1a4522166f
--- /dev/null
+++ b/src/main/java/org/dependencytrack/model/AvailableTeams.java
@@ -0,0 +1,56 @@
+/*
+ * 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.model;
+
+import java.io.Serializable;
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class AvailableTeams implements Serializable {
+ private boolean required;
+ private List teams;
+
+ public boolean isRequired() {
+ return required;
+ }
+
+ public void setRequired(final boolean required) {
+ this.required = required;
+ }
+
+ public List getTeams() {
+ return teams;
+ }
+
+ public void setTeams(final List teams) {
+ this.teams = teams;
+ }
+
+ @Override
+ public String toString() {
+ List strlistTeams = teams.stream()
+ .map(Object::toString)
+ .toList();
+ String strTeams = String.join(",", strlistTeams);
+ return String.format("required: %s, teams: [ %s ]", required, strTeams);
+ }
+
+}
diff --git a/src/main/java/org/dependencytrack/model/LittleTeam.java b/src/main/java/org/dependencytrack/model/LittleTeam.java
new file mode 100644
index 0000000000..5355b5a0e0
--- /dev/null
+++ b/src/main/java/org/dependencytrack/model/LittleTeam.java
@@ -0,0 +1,53 @@
+/*
+ * 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.model;
+
+import java.io.Serializable;
+
+import java.util.UUID;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class LittleTeam implements Serializable {
+
+ private UUID value;
+ private String text;
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public UUID getValue() {
+ return value;
+ }
+
+ public void setValue(UUID value) {
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{value: %s, text: %s}", value.toString(), text);
+ }
+}
diff --git a/src/main/java/org/dependencytrack/model/Project.java b/src/main/java/org/dependencytrack/model/Project.java
index 74fdef9446..74b8480b3c 100644
--- a/src/main/java/org/dependencytrack/model/Project.java
+++ b/src/main/java/org/dependencytrack/model/Project.java
@@ -190,7 +190,8 @@ public enum FetchGroup {
@Index(name = "PROJECT_CPE_IDX")
@Size(max = 255)
@JsonDeserialize(using = TrimmedStringDeserializer.class)
- //Patterns obtained from https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd
+ // Patterns obtained from
+ // https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd
@Pattern(regexp = "(cpe:2\\.3:[aho\\*\\-](:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!\"#$$%&'\\(\\)\\+,/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\\*\\-]))(:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!\"#$$%&'\\(\\)\\+,/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){4})|([c][pP][eE]:/[AHOaho]?(:[A-Za-z0-9\\._\\-~%]*){0,6})", message = "The CPE must conform to the CPE v2.2 or v2.3 specification defined by NIST")
private String cpe;
@@ -223,7 +224,7 @@ public enum FetchGroup {
@Persistent
@Column(name = "PARENT_PROJECT_ID")
- @JsonIncludeProperties(value = {"name", "version", "uuid"})
+ @JsonIncludeProperties(value = { "name", "version", "uuid" })
private Project parent;
@Persistent(mappedBy = "parent")
@@ -240,7 +241,8 @@ public enum FetchGroup {
private List tags;
/**
- * Convenience field which will contain the date of the last entry in the {@link Bom} table
+ * Convenience field which will contain the date of the last entry in the
+ * {@link Bom} table
*/
@Persistent
@Index(name = "PROJECT_LASTBOMIMPORT_IDX")
@@ -249,7 +251,8 @@ public enum FetchGroup {
private Date lastBomImport;
/**
- * Convenience field which will contain the format of the last entry in the {@link Bom} table
+ * Convenience field which will contain the format of the last entry in the
+ * {@link Bom} table
*/
@Persistent
@Index(name = "PROJECT_LASTBOMIMPORT_FORMAT_IDX")
@@ -257,7 +260,8 @@ public enum FetchGroup {
private String lastBomImportFormat;
/**
- * Convenience field which stores the Inherited Risk Score (IRS) of the last metric in the {@link ProjectMetrics} table
+ * Convenience field which stores the Inherited Risk Score (IRS) of the last
+ * metric in the {@link ProjectMetrics} table
*/
@Persistent
@Index(name = "PROJECT_LAST_RISKSCORE_IDX")
@@ -289,6 +293,7 @@ public enum FetchGroup {
private transient ProjectMetrics metrics;
private transient List versions;
private transient List dependencyGraph;
+ private UUID initialTeam;
public long getId() {
return id;
@@ -308,20 +313,22 @@ public void setAuthors(List authors) {
@Deprecated
@JsonInclude(JsonInclude.Include.NON_EMPTY)
- public String getAuthor(){
+ public String getAuthor() {
return ModelConverter.convertContactsToString(this.authors);
}
@Deprecated
- public void setAuthor(String author){
- if(this.authors==null){
+ public void setAuthor(String author) {
+ if (this.authors == null) {
this.authors = new ArrayList<>();
- } else{
+ } else {
this.authors.clear();
}
- this.authors.add(new OrganizationalContact() {{
- setName(author);
- }});
+ this.authors.add(new OrganizationalContact() {
+ {
+ setName(author);
+ }
+ });
}
public String getPublisher() {
@@ -560,6 +567,14 @@ public void setMetadata(final ProjectMetadata metadata) {
this.metadata = metadata;
}
+ public UUID getInitialTeam() {
+ return this.initialTeam;
+ }
+
+ public void setInitialTeam(UUID initialTeam) {
+ this.initialTeam = initialTeam;
+ }
+
@JsonIgnore
public List getDependencyGraph() {
return dependencyGraph;
diff --git a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
index c9cd063908..ef8ed26d49 100644
--- a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
@@ -21,6 +21,9 @@
import alpine.common.logging.Logger;
import alpine.event.framework.Event;
import alpine.model.Team;
+import alpine.model.UserPrincipal;
+import alpine.model.ConfigProperty;
+import alpine.model.Permission;
import alpine.persistence.PaginatedResult;
import alpine.server.auth.PermissionRequired;
import alpine.server.resources.AlpineResource;
@@ -65,6 +68,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Set;
+import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Function;
@@ -86,39 +90,31 @@ public class ProjectResource extends AlpineResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Returns a list of all projects",
- description = "Requires permission VIEW_PORTFOLIO
"
- )
+ @Operation(summary = "Returns a list of all projects", description = "Requires permission VIEW_PORTFOLIO
")
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "A list of all projects",
- headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
- content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
- ),
+ @ApiResponse(responseCode = "200", description = "A list of all projects", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
- public Response getProjects(@Parameter(description = "The optional name of the project to query on", required = false)
- @QueryParam("name") String name,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
- @QueryParam("excludeInactive") boolean excludeInactive,
- @Parameter(description = "Optionally excludes children projects from being returned", required = false)
- @QueryParam("onlyRoot") boolean onlyRoot,
- @Parameter(description = "The UUID of the team which projects shall be excluded", schema = @Schema(type = "string", format = "uuid"), required = false)
- @QueryParam("notAssignedToTeamWithUuid") @ValidUuid String notAssignedToTeamWithUuid) {
+ public Response getProjects(
+ @Parameter(description = "The optional name of the project to query on", required = false) @QueryParam("name") String name,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive,
+ @Parameter(description = "Optionally excludes children projects from being returned", required = false) @QueryParam("onlyRoot") boolean onlyRoot,
+ @Parameter(description = "The UUID of the team which projects shall be excluded", schema = @Schema(type = "string", format = "uuid"), required = false) @QueryParam("notAssignedToTeamWithUuid") @ValidUuid String notAssignedToTeamWithUuid) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
Team notAssignedToTeam = null;
if (StringUtils.isNotEmpty(notAssignedToTeamWithUuid)) {
notAssignedToTeam = qm.getObjectByUuid(Team.class, notAssignedToTeamWithUuid);
if (notAssignedToTeam == null) {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the team could not be found.").build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the team could not be found.")
+ .build();
}
}
- final PaginatedResult result = (name != null) ? qm.getProjects(name, excludeInactive, onlyRoot, notAssignedToTeam) : qm.getProjects(true, excludeInactive, onlyRoot, notAssignedToTeam);
+ final PaginatedResult result = (name != null)
+ ? qm.getProjects(name, excludeInactive, onlyRoot, notAssignedToTeam)
+ : qm.getProjects(true, excludeInactive, onlyRoot, notAssignedToTeam);
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build();
}
}
@@ -126,31 +122,24 @@ public Response getProjects(@Parameter(description = "The optional name of the p
@GET
@Path("/{uuid}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Returns a specific project",
- description = "Requires permission VIEW_PORTFOLIO
"
- )
+ @Operation(summary = "Returns a specific project", description = "Requires permission VIEW_PORTFOLIO
")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "A specific project",
- content = @Content(schema = @Schema(implementation = Project.class))
- ),
+ @ApiResponse(responseCode = "200", description = "A specific project", content = @Content(schema = @Schema(implementation = Project.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Access to the specified project is forbidden"),
@ApiResponse(responseCode = "404", description = "The project could not be found")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getProject(
- @Parameter(description = "The UUID of the project to retrieve", schema = @Schema(type = "string", format = "uuid"), required = true)
- @PathParam("uuid") @ValidUuid String uuid) {
+ @Parameter(description = "The UUID of the project to retrieve", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid) {
try (QueryManager qm = new QueryManager()) {
final Project project = qm.getProject(uuid);
if (project != null) {
if (qm.hasAccess(super.getPrincipal(), project)) {
return Response.ok(project).build();
} else {
- return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("Access to the specified project is forbidden").build();
}
} else {
return Response.status(Response.Status.NOT_FOUND).entity("The project could not be found.").build();
@@ -161,34 +150,25 @@ public Response getProject(
@GET
@Path("/lookup")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Returns a specific project by its name and version",
- operationId = "getProjectByNameAndVersion",
- description = "Requires permission VIEW_PORTFOLIO
"
- )
+ @Operation(summary = "Returns a specific project by its name and version", operationId = "getProjectByNameAndVersion", description = "Requires permission VIEW_PORTFOLIO
")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "A specific project by its name and version",
- content = @Content(schema = @Schema(implementation = Project.class))
- ),
+ @ApiResponse(responseCode = "200", description = "A specific project by its name and version", content = @Content(schema = @Schema(implementation = Project.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Access to the specified project is forbidden"),
@ApiResponse(responseCode = "404", description = "The project could not be found")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getProject(
- @Parameter(description = "The name of the project to query on", required = true)
- @QueryParam("name") String name,
- @Parameter(description = "The version of the project to query on", required = true)
- @QueryParam("version") String version) {
+ @Parameter(description = "The name of the project to query on", required = true) @QueryParam("name") String name,
+ @Parameter(description = "The version of the project to query on", required = true) @QueryParam("version") String version) {
try (QueryManager qm = new QueryManager()) {
final Project project = qm.getProject(name, version);
if (project != null) {
if (qm.hasAccess(super.getPrincipal(), project)) {
return Response.ok(project).build();
} else {
- return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("Access to the specified project is forbidden").build();
}
} else {
return Response.status(Response.Status.NOT_FOUND).entity("The project could not be found.").build();
@@ -199,28 +179,17 @@ public Response getProject(
@GET
@Path("/tag/{tag}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Returns a list of all projects by tag",
- description = "Requires permission VIEW_PORTFOLIO
"
- )
+ @Operation(summary = "Returns a list of all projects by tag", description = "Requires permission VIEW_PORTFOLIO
")
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "A list of all projects by tag",
- headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
- content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
- ),
+ @ApiResponse(responseCode = "200", description = "A list of all projects by tag", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getProjectsByTag(
- @Parameter(description = "The tag to query on", required = true)
- @PathParam("tag") String tagString,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
- @QueryParam("excludeInactive") boolean excludeInactive,
- @Parameter(description = "Optionally excludes children projects from being returned", required = false)
- @QueryParam("onlyRoot") boolean onlyRoot) {
+ @Parameter(description = "The tag to query on", required = true) @PathParam("tag") String tagString,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive,
+ @Parameter(description = "Optionally excludes children projects from being returned", required = false) @QueryParam("onlyRoot") boolean onlyRoot) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final Tag tag = qm.getTagByName(tagString);
final PaginatedResult result = qm.getProjects(tag, true, excludeInactive, onlyRoot);
@@ -231,53 +200,36 @@ public Response getProjectsByTag(
@GET
@Path("/classifier/{classifier}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Returns a list of all projects by classifier",
- description = "Requires permission VIEW_PORTFOLIO
"
- )
+ @Operation(summary = "Returns a list of all projects by classifier", description = "Requires permission VIEW_PORTFOLIO
")
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "A list of all projects by classifier",
- headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
- content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
- ),
+ @ApiResponse(responseCode = "200", description = "A list of all projects by classifier", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getProjectsByClassifier(
- @Parameter(description = "The classifier to query on", required = true)
- @PathParam("classifier") String classifierString,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
- @QueryParam("excludeInactive") boolean excludeInactive,
- @Parameter(description = "Optionally excludes children projects from being returned", required = false)
- @QueryParam("onlyRoot") boolean onlyRoot) {
+ @Parameter(description = "The classifier to query on", required = true) @PathParam("classifier") String classifierString,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive,
+ @Parameter(description = "Optionally excludes children projects from being returned", required = false) @QueryParam("onlyRoot") boolean onlyRoot) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final Classifier classifier = Classifier.valueOf(classifierString);
final PaginatedResult result = qm.getProjects(classifier, true, excludeInactive, onlyRoot);
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build();
} catch (IllegalArgumentException e) {
- return Response.status(Response.Status.BAD_REQUEST).entity("The classifier type specified is not valid.").build();
+ return Response.status(Response.Status.BAD_REQUEST).entity("The classifier type specified is not valid.")
+ .build();
}
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Creates a new project",
- description = """
- If a parent project exists, parent.uuid
is required
- Requires permission PORTFOLIO_MANAGEMENT
- """
- )
+ @Operation(summary = "Creates a new project", description = """
+ If a parent project exists, parent.uuid
is required
+ Requires permission PORTFOLIO_MANAGEMENT
+ """)
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "201",
- description = "The created project",
- content = @Content(schema = @Schema(implementation = Project.class))
- ),
+ @ApiResponse(responseCode = "201", description = "The created project", content = @Content(schema = @Schema(implementation = Project.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "409", description = """
@@ -299,30 +251,86 @@ public Response createProject(Project jsonProject) {
validator.validateProperty(jsonProject, "classifier"),
validator.validateProperty(jsonProject, "cpe"),
validator.validateProperty(jsonProject, "purl"),
- validator.validateProperty(jsonProject, "swidTagId")
- );
+ validator.validateProperty(jsonProject, "swidTagId"),
+ validator.validateProperty(jsonProject, "initialTeam"));
if (jsonProject.getClassifier() == null) {
jsonProject.setClassifier(Classifier.APPLICATION);
}
try (QueryManager qm = new QueryManager()) {
if (jsonProject.getParent() != null && jsonProject.getParent().getUuid() != null) {
Project parent = qm.getObjectByUuid(Project.class, jsonProject.getParent().getUuid());
- jsonProject.setParent(parent);
+ jsonProject.setParent(parent);
}
- if (!qm.doesProjectExist(StringUtils.trimToNull(jsonProject.getName()), StringUtils.trimToNull(jsonProject.getVersion()))) {
+ 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){
+ } catch (IllegalArgumentException e) {
LOGGER.debug(e.getMessage());
- return Response.status(Response.Status.CONFLICT).entity("An inactive Parent cannot be selected as parent").build();
+ return Response.status(Response.Status.CONFLICT)
+ .entity("An inactive Parent cannot be selected as parent")
+ .build();
+ }
+ UserPrincipal user;
+ if (super.isLdapUser()) {
+ user = qm.getLdapUser(getPrincipal().getName());
+ } else if (super.isManagedUser()) {
+ user = qm.getManagedUser(getPrincipal().getName());
+ } else if (super.isOidcUser()) {
+ user = qm.getOidcUser(getPrincipal().getName());
+ } else {
+ return Response.status(401).build();
+ }
+ boolean isAdmin = false;
+ boolean required = false;
+ final List configProperties = qm.getConfigProperties();
+ for (final ConfigProperty configProperty : configProperties) {
+ // Checks if User needs to supply a Team
+ if (configProperty.getGroupName().equals("access-management")
+ && configProperty.getPropertyName().equals("acl.enabled")) {
+ required = configProperty.getPropertyValue().equals("true");
+ break;
+ }
+ }
+ List permissions = user.getPermissions();
+ for (Permission permission : permissions) {
+ // Checks if user has Right to submit any team to the project
+ if (permission.getName().equals("ACCESS_MANAGEMENT")) {
+ isAdmin = true;
+ break;
+ }
+ }
+ if (required && jsonProject.getInitialTeam() == null) {
+ return Response.status(422).build();
+ }
+ final UUID teamUuid = jsonProject.getInitialTeam();
+ if (!isAdmin) {
+ boolean hasTeam = false;
+ List teams = user.getTeams();
+ for (Team team : teams) {
+ if (team.getUuid().equals(teamUuid)) {
+ hasTeam = true;
+ break;
+ }
+ }
+ if (!hasTeam) {
+ return Response.status(403).build();
+ }
+
+ }
+ if (jsonProject.getInitialTeam() != null) {
+ final Team team = qm.getObjectByUuid(Team.class, teamUuid);
+ project.addAccessTeam(team);
}
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 {
- return Response.status(Response.Status.CONFLICT).entity("A project with the specified name already exists.").build();
+ return Response.status(Response.Status.CONFLICT)
+ .entity("A project with the specified name already exists.")
+ .build();
}
}
}
@@ -330,16 +338,9 @@ public Response createProject(Project jsonProject) {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Updates a project",
- description = "Requires permission PORTFOLIO_MANAGEMENT
"
- )
+ @Operation(summary = "Updates a project", description = "Requires permission PORTFOLIO_MANAGEMENT
")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "The updated project",
- content = @Content(schema = @Schema(implementation = Project.class))
- ),
+ @ApiResponse(responseCode = "200", description = "The updated project", content = @Content(schema = @Schema(implementation = Project.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found"),
@ApiResponse(responseCode = "409", description = """
@@ -363,8 +364,7 @@ public Response updateProject(Project jsonProject) {
validator.validateProperty(jsonProject, "classifier"),
validator.validateProperty(jsonProject, "cpe"),
validator.validateProperty(jsonProject, "purl"),
- validator.validateProperty(jsonProject, "swidTagId")
- );
+ validator.validateProperty(jsonProject, "swidTagId"));
if (jsonProject.getClassifier() == null) {
jsonProject.setClassifier(Classifier.APPLICATION);
}
@@ -372,7 +372,8 @@ public Response updateProject(Project jsonProject) {
Project project = qm.getObjectByUuid(Project.class, jsonProject.getUuid());
if (project != null) {
if (!qm.hasAccess(super.getPrincipal(), project)) {
- return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("Access to the specified project is forbidden").build();
}
final String name = StringUtils.trimToNull(jsonProject.getName());
final String version = StringUtils.trimToNull(jsonProject.getVersion());
@@ -384,17 +385,19 @@ public Response updateProject(Project jsonProject) {
}
try {
project = qm.updateProject(jsonProject, true);
- } catch (IllegalArgumentException e){
+ } catch (IllegalArgumentException e) {
LOGGER.debug(e.getMessage());
return Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build();
}
LOGGER.info("Project " + project.toString() + " updated by " + super.getPrincipal().getName());
return Response.ok(project).build();
} else {
- return Response.status(Response.Status.CONFLICT).entity("A project with the specified name and version already exists.").build();
+ return Response.status(Response.Status.CONFLICT)
+ .entity("A project with the specified name and version already exists.").build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
+ .build();
}
}
}
@@ -403,16 +406,9 @@ public Response updateProject(Project jsonProject) {
@Path("/{uuid}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Partially updates a project",
- description = "Requires permission PORTFOLIO_MANAGEMENT
"
- )
+ @Operation(summary = "Partially updates a project", description = "Requires permission PORTFOLIO_MANAGEMENT
")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "The updated project",
- content = @Content(schema = @Schema(implementation = Project.class))
- ),
+ @ApiResponse(responseCode = "200", description = "The updated project", content = @Content(schema = @Schema(implementation = Project.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found"),
@ApiResponse(responseCode = "409", description = """
@@ -425,8 +421,7 @@ public Response updateProject(Project jsonProject) {
})
@PermissionRequired(Permissions.Constants.PORTFOLIO_MANAGEMENT)
public Response patchProject(
- @Parameter(description = "The UUID of the project to modify", schema = @Schema(type = "string", format = "uuid"), required = true)
- @PathParam("uuid") @ValidUuid String uuid,
+ @Parameter(description = "The UUID of the project to modify", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid,
Project jsonProject) {
final Validator validator = getValidator();
failOnValidationError(
@@ -440,22 +435,24 @@ public Response patchProject(
validator.validateProperty(jsonProject, "classifier"),
validator.validateProperty(jsonProject, "cpe"),
validator.validateProperty(jsonProject, "purl"),
- validator.validateProperty(jsonProject, "swidTagId")
- );
+ validator.validateProperty(jsonProject, "swidTagId"));
try (QueryManager qm = new QueryManager()) {
Project project = qm.getObjectByUuid(Project.class, uuid);
if (project != null) {
if (!qm.hasAccess(super.getPrincipal(), project)) {
- return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("Access to the specified project is forbidden").build();
}
var modified = false;
project = qm.detachWithGroups(project, List.of(FetchGroup.DEFAULT, Project.FetchGroup.PARENT.name()));
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 either name or version has been changed, verify that this new combination
+ // does not already exist
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();
+ return Response.status(Response.Status.CONFLICT)
+ .entity("A project with the specified name and version already exists.").build();
}
modified |= setIfDifferent(jsonProject, project, Project::getAuthors, Project::setAuthors);
modified |= setIfDifferent(jsonProject, project, Project::getPublisher, Project::setPublisher);
@@ -471,10 +468,12 @@ public Response patchProject(
if (jsonProject.getParent() != null && jsonProject.getParent().getUuid() != null) {
final Project parent = qm.getObjectByUuid(Project.class, jsonProject.getParent().getUuid());
if (parent == null) {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the parent project could not be found.").build();
+ return Response.status(Response.Status.NOT_FOUND)
+ .entity("The UUID of the parent project could not be found.").build();
}
if (!qm.hasAccess(getPrincipal(), parent)) {
- return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified parent project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("Access to the specified parent project is forbidden").build();
}
modified |= project.getParent() == null || !parent.getUuid().equals(project.getParent().getUuid());
project.setParent(parent);
@@ -484,13 +483,13 @@ public Response patchProject(
project.setTags(jsonProject.getTags());
}
if (isCollectionModified(jsonProject.getExternalReferences(), project.getExternalReferences())) {
- modified = true;
- project.setExternalReferences(jsonProject.getExternalReferences());
+ modified = true;
+ project.setExternalReferences(jsonProject.getExternalReferences());
}
if (modified) {
try {
project = qm.updateProject(project, true);
- } catch (IllegalArgumentException e){
+ } catch (IllegalArgumentException e) {
LOGGER.debug(e.getMessage());
return Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build();
}
@@ -500,16 +499,18 @@ public Response patchProject(
return Response.notModified().build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
+ .build();
}
}
}
/**
- * returns `true` if the given [updated] collection should be considered an update of the [original] collection.
+ * returns `true` if the given [updated] collection should be considered an
+ * update of the [original] collection.
*/
private static boolean isCollectionModified(Collection updated, Collection original) {
- return updated != null && (!Collections.isEmpty(updated) || !Collections.isEmpty(original));
+ return updated != null && (!Collections.isEmpty(updated) || !Collections.isEmpty(original));
}
/**
@@ -518,16 +519,17 @@ private static boolean isCollectionModified(Collection updated, Collectio
* only if the new value is not {@code null} and it is not
* {@link Object#equals(java.lang.Object) equal to} the old value.
*
- * @param the type of the old and new value
+ * @param the type of the old and new value
* @param source the source object that contains the new value
* @param target the target object that should be updated
* @param getter the method to retrieve the new value from {@code source}
- * and the old value from {@code target}
+ * and the old value from {@code target}
* @param setter the method to set the new value on {@code target}
* @return {@code true} if {@code target} has been changed, else
- * {@code false}
+ * {@code false}
*/
- private boolean setIfDifferent(final Project source, final Project target, final Function getter, final BiConsumer setter) {
+ private boolean setIfDifferent(final Project source, final Project target, final Function getter,
+ final BiConsumer setter) {
final T newValue = getter.apply(source);
if (newValue != null && !newValue.equals(getter.apply(target))) {
setter.accept(target, newValue);
@@ -541,10 +543,7 @@ private boolean setIfDifferent(final Project source, final Project target, f
@Path("/{uuid}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Deletes a project",
- description = "Requires permission PORTFOLIO_MANAGEMENT
"
- )
+ @Operation(summary = "Deletes a project", description = "Requires permission PORTFOLIO_MANAGEMENT
")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Project removed successfully"),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@@ -553,8 +552,7 @@ private boolean setIfDifferent(final Project source, final Project target, f
})
@PermissionRequired(Permissions.Constants.PORTFOLIO_MANAGEMENT)
public Response deleteProject(
- @Parameter(description = "The UUID of the project to delete", schema = @Schema(type = "string", format = "uuid"), required = true)
- @PathParam("uuid") @ValidUuid String uuid) {
+ @Parameter(description = "The UUID of the project to delete", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid) {
try (QueryManager qm = new QueryManager()) {
final Project project = qm.getObjectByUuid(Project.class, uuid, Project.FetchGroup.ALL.name());
if (project != null) {
@@ -563,10 +561,13 @@ public Response deleteProject(
qm.recursivelyDelete(project, true);
return Response.status(Response.Status.NO_CONTENT).build();
} else {
- return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("Access to the specified project is forbidden")
+ .build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
+ .build();
}
}
}
@@ -575,16 +576,9 @@ public Response deleteProject(
@Path("/clone")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Clones a project",
- description = "Requires permission PORTFOLIO_MANAGEMENT
"
- )
+ @Operation(summary = "Clones a project", description = "Requires permission PORTFOLIO_MANAGEMENT
")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "Token to be used for checking cloning progress",
- content = @Content(schema = @Schema(implementation = BomUploadResponse.class))
- ),
+ @ApiResponse(responseCode = "200", description = "Token to be used for checking cloning progress", content = @Content(schema = @Schema(implementation = BomUploadResponse.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found")
})
@@ -593,16 +587,19 @@ public Response cloneProject(CloneProjectRequest jsonRequest) {
final Validator validator = super.getValidator();
failOnValidationError(
validator.validateProperty(jsonRequest, "project"),
- validator.validateProperty(jsonRequest, "version")
- );
+ validator.validateProperty(jsonRequest, "version"));
try (QueryManager qm = new QueryManager()) {
- final Project sourceProject = qm.getObjectByUuid(Project.class, jsonRequest.getProject(), Project.FetchGroup.ALL.name());
+ final Project sourceProject = qm.getObjectByUuid(Project.class, jsonRequest.getProject(),
+ Project.FetchGroup.ALL.name());
if (sourceProject != null) {
if (!qm.hasAccess(super.getPrincipal(), sourceProject)) {
- return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
+ 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();
+ 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());
@@ -610,36 +607,27 @@ public Response cloneProject(CloneProjectRequest jsonRequest) {
Event.dispatch(event);
return Response.ok(java.util.Collections.singletonMap("token", event.getChainIdentifier())).build();
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
+ .build();
}
}
}
-
@GET
@Path("/{uuid}/children")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Returns a list of all children for a project",
- description = "Requires permission VIEW_PORTFOLIO
"
- )
+ @Operation(summary = "Returns a list of all children for a project", description = "Requires permission VIEW_PORTFOLIO
")
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "A list of all children for a project",
- headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
- content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
- ),
+ @ApiResponse(responseCode = "200", description = "A list of all children for a project", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Access to the specified project is forbidden"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
- public Response getChildrenProjects(@Parameter(description = "The UUID of the project to get the children from", schema = @Schema(type = "string", format = "uuid"), required = true)
- @PathParam("uuid") @ValidUuid String uuid,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
- @QueryParam("excludeInactive") boolean excludeInactive) {
+ public Response getChildrenProjects(
+ @Parameter(description = "The UUID of the project to get the children from", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final Project project = qm.getObjectByUuid(Project.class, uuid);
if (project != null) {
@@ -647,10 +635,13 @@ public Response getChildrenProjects(@Parameter(description = "The UUID of the pr
if (qm.hasAccess(super.getPrincipal(), project)) {
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build();
} else {
- return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("Access to the specified project is forbidden")
+ .build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
+ .build();
}
}
}
@@ -658,42 +649,35 @@ public Response getChildrenProjects(@Parameter(description = "The UUID of the pr
@GET
@Path("/{uuid}/children/classifier/{classifier}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Returns a list of all children for a project by classifier",
- description = "Requires permission VIEW_PORTFOLIO
"
- )
+ @Operation(summary = "Returns a list of all children for a project by classifier", description = "Requires permission VIEW_PORTFOLIO
")
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "A list of all children for a project by classifier",
- headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
- content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
- ),
+ @ApiResponse(responseCode = "200", description = "A list of all children for a project by classifier", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Access to the specified project is forbidden"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getChildrenProjectsByClassifier(
- @Parameter(description = "The classifier to query on", required = true)
- @PathParam("classifier") String classifierString,
- @Parameter(description = "The UUID of the project to get the children from", schema = @Schema(type = "string", format = "uuid"), required = true)
- @PathParam("uuid") @ValidUuid String uuid,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
- @QueryParam("excludeInactive") boolean excludeInactive) {
+ @Parameter(description = "The classifier to query on", required = true) @PathParam("classifier") String classifierString,
+ @Parameter(description = "The UUID of the project to get the children from", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final Project project = qm.getObjectByUuid(Project.class, uuid);
if (project != null) {
final Classifier classifier = Classifier.valueOf(classifierString);
- final PaginatedResult result = qm.getChildrenProjects(classifier, project.getUuid(), true, excludeInactive);
+ final PaginatedResult result = qm.getChildrenProjects(classifier, project.getUuid(), true,
+ excludeInactive);
if (qm.hasAccess(super.getPrincipal(), project)) {
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build();
} else {
- return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("Access to the specified project is forbidden")
+ .build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
+ .build();
}
}
}
@@ -701,30 +685,19 @@ public Response getChildrenProjectsByClassifier(
@GET
@Path("/{uuid}/children/tag/{tag}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Returns a list of all children for a project by tag",
- description = "Requires permission VIEW_PORTFOLIO
"
- )
+ @Operation(summary = "Returns a list of all children for a project by tag", description = "Requires permission VIEW_PORTFOLIO
")
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "A list of all children for a project by tag",
- headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
- content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
- ),
+ @ApiResponse(responseCode = "200", description = "A list of all children for a project by tag", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Access to the specified project is forbidden"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getChildrenProjectsByTag(
- @Parameter(description = "The tag to query on", required = true)
- @PathParam("tag") String tagString,
- @Parameter(description = "The UUID of the project to get the children from", schema = @Schema(type = "string", format = "uuid"), required = true)
- @PathParam("uuid") @ValidUuid String uuid,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
- @QueryParam("excludeInactive") boolean excludeInactive) {
+ @Parameter(description = "The tag to query on", required = true) @PathParam("tag") String tagString,
+ @Parameter(description = "The UUID of the project to get the children from", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final Project project = qm.getObjectByUuid(Project.class, uuid);
if (project != null) {
@@ -733,10 +706,13 @@ public Response getChildrenProjectsByTag(
if (qm.hasAccess(super.getPrincipal(), project)) {
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build();
} else {
- return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("Access to the specified project is forbidden")
+ .build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
+ .build();
}
}
}
@@ -744,41 +720,35 @@ public Response getChildrenProjectsByTag(
@GET
@Path("/withoutDescendantsOf/{uuid}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Returns a list of all projects without the descendants of the selected project",
- description = "Requires permission VIEW_PORTFOLIO
"
- )
+ @Operation(summary = "Returns a list of all projects without the descendants of the selected project", description = "Requires permission VIEW_PORTFOLIO
")
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "A list of all projects without the descendants of the selected project",
- headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
- content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
- ),
+ @ApiResponse(responseCode = "200", description = "A list of all projects without the descendants of the selected project", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Access to the specified project is forbidden"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getProjectsWithoutDescendantsOf(
- @Parameter(description = "The UUID of the project which descendants will be excluded", schema = @Schema(type = "string", format = "uuid"), required = true)
- @PathParam("uuid") @ValidUuid String uuid,
- @Parameter(description = "The optional name of the project to query on", required = false)
- @QueryParam("name") String name,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
- @QueryParam("excludeInactive") boolean excludeInactive) {
+ @Parameter(description = "The UUID of the project which descendants will be excluded", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid,
+ @Parameter(description = "The optional name of the project to query on", required = false) @QueryParam("name") String name,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final Project project = qm.getObjectByUuid(Project.class, uuid);
if (project != null) {
if (qm.hasAccess(super.getPrincipal(), project)) {
- final PaginatedResult result = (name != null) ? qm.getProjectsWithoutDescendantsOf(name, excludeInactive, project) : qm.getProjectsWithoutDescendantsOf(excludeInactive, project);
+ final PaginatedResult result = (name != null)
+ ? qm.getProjectsWithoutDescendantsOf(name, excludeInactive, project)
+ : qm.getProjectsWithoutDescendantsOf(excludeInactive, project);
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build();
- } else{
- return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
+ } else {
+ return Response.status(Response.Status.FORBIDDEN)
+ .entity("Access to the specified project is forbidden")
+ .build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
+ .build();
}
}
}
diff --git a/src/main/java/org/dependencytrack/resources/v1/TeamResource.java b/src/main/java/org/dependencytrack/resources/v1/TeamResource.java
index b55b421988..a065858ea4 100644
--- a/src/main/java/org/dependencytrack/resources/v1/TeamResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/TeamResource.java
@@ -20,8 +20,11 @@
import alpine.Config;
import alpine.common.logging.Logger;
+import alpine.model.ConfigProperty;
import alpine.model.ApiKey;
+import alpine.model.Permission;
import alpine.model.Team;
+import alpine.model.UserPrincipal;
import alpine.server.auth.PermissionRequired;
import alpine.server.resources.AlpineResource;
import io.swagger.v3.oas.annotations.Operation;
@@ -37,6 +40,8 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import org.dependencytrack.auth.Permissions;
import org.dependencytrack.model.validation.ValidUuid;
+import org.dependencytrack.model.AvailableTeams;
+import org.dependencytrack.model.LittleTeam;
import org.dependencytrack.persistence.QueryManager;
import org.dependencytrack.resources.v1.vo.TeamSelfResponse;
import org.owasp.security.logging.SecurityMarkers;
@@ -52,6 +57,8 @@
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
+
+import java.util.ArrayList;
import java.util.List;
import static org.datanucleus.PropertyNames.PROPERTY_RETAIN_VALUES;
@@ -74,17 +81,9 @@ public class TeamResource extends AlpineResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Returns a list of all teams",
- description = "Requires permission ACCESS_MANAGEMENT
"
- )
+ @Operation(summary = "Returns a list of all teams", description = "Requires permission ACCESS_MANAGEMENT
")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "A list of all teams",
- headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of teams", schema = @Schema(format = "integer")),
- content = @Content(array = @ArraySchema(schema = @Schema(implementation = Team.class)))
- ),
+ @ApiResponse(responseCode = "200", description = "A list of all teams", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of teams", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Team.class)))),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
@@ -99,23 +98,15 @@ public Response getTeams() {
@GET
@Path("/{uuid}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Returns a specific team",
- description = "Requires permission ACCESS_MANAGEMENT
"
- )
+ @Operation(summary = "Returns a specific team", description = "Requires permission ACCESS_MANAGEMENT
")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "A specific team",
- content = @Content(schema = @Schema(implementation = Team.class))
- ),
+ @ApiResponse(responseCode = "200", description = "A specific team", content = @Content(schema = @Schema(implementation = Team.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The team could not be found")
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
public Response getTeam(
- @Parameter(description = "The UUID of the team to retrieve", schema = @Schema(type = "string", format = "uuid"), required = true)
- @PathParam("uuid") @ValidUuid String uuid) {
+ @Parameter(description = "The UUID of the team to retrieve", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid) {
try (QueryManager qm = new QueryManager()) {
final Team team = qm.getObjectByUuid(Team.class, uuid);
if (team != null) {
@@ -129,26 +120,18 @@ public Response getTeam(
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Creates a new team",
- description = "Requires permission ACCESS_MANAGEMENT
"
- )
+ @Operation(summary = "Creates a new team", description = "Requires permission ACCESS_MANAGEMENT
")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "201",
- description = "The created team",
- content = @Content(schema = @Schema(implementation = Team.class))
- ),
+ @ApiResponse(responseCode = "201", description = "The created team", content = @Content(schema = @Schema(implementation = Team.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
- //public Response createTeam(String jsonRequest) {
+ // public Response createTeam(String jsonRequest) {
public Response createTeam(Team jsonTeam) {
- //Team team = MapperUtil.readAsObjectOf(Team.class, jsonRequest);
+ // Team team = MapperUtil.readAsObjectOf(Team.class, jsonRequest);
final Validator validator = super.getValidator();
failOnValidationError(
- validator.validateProperty(jsonTeam, "name")
- );
+ validator.validateProperty(jsonTeam, "name"));
try (QueryManager qm = new QueryManager()) {
final Team team = qm.createTeam(jsonTeam.getName(), false);
@@ -160,16 +143,9 @@ public Response createTeam(Team jsonTeam) {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Updates a team's fields",
- description = "Requires permission ACCESS_MANAGEMENT
"
- )
+ @Operation(summary = "Updates a team's fields", description = "Requires permission ACCESS_MANAGEMENT
")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "The updated team",
- content = @Content(schema = @Schema(implementation = Team.class))
- ),
+ @ApiResponse(responseCode = "200", description = "The updated team", content = @Content(schema = @Schema(implementation = Team.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The team could not be found")
})
@@ -177,13 +153,12 @@ public Response createTeam(Team jsonTeam) {
public Response updateTeam(Team jsonTeam) {
final Validator validator = super.getValidator();
failOnValidationError(
- validator.validateProperty(jsonTeam, "name")
- );
+ validator.validateProperty(jsonTeam, "name"));
try (QueryManager qm = new QueryManager()) {
Team team = qm.getObjectByUuid(Team.class, jsonTeam.getUuid());
if (team != null) {
team.setName(jsonTeam.getName());
- //todo: set permissions
+ // todo: set permissions
team = qm.updateTeam(jsonTeam);
super.logSecurityEvent(LOGGER, SecurityMarkers.SECURITY_AUDIT, "Team updated: " + team.getName());
return Response.ok(team).build();
@@ -196,10 +171,7 @@ public Response updateTeam(Team jsonTeam) {
@DELETE
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Deletes a team",
- description = "Requires permission ACCESS_MANAGEMENT
"
- )
+ @Operation(summary = "Deletes a team", description = "Requires permission ACCESS_MANAGEMENT
")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Team removed successfully"),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@@ -220,26 +192,77 @@ public Response deleteTeam(Team jsonTeam) {
}
}
+ @GET
+ @Path("/available-teams")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Operation(summary = "Returns a list of Teams what are available as selection", description = "Requires permission PORTFOLIO_MANAGEMENT
")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "The Available Teams", content = @Content(schema = @Schema(implementation = AvailableTeams.class))),
+ @ApiResponse(responseCode = "401", description = "Unauthorized"),
+ @ApiResponse(responseCode = "404", description = "Teams could not be found")
+ })
+ @PermissionRequired(Permissions.Constants.PORTFOLIO_MANAGEMENT)
+ public Response availableTeams() {
+ UserPrincipal user;
+ boolean isAllTeams = false;
+ boolean required = false;
+ try (QueryManager qm = new QueryManager()) {
+ if (super.isLdapUser()) {
+ user = qm.getLdapUser(getPrincipal().getName());
+ } else if (super.isManagedUser()) {
+ user = qm.getManagedUser(getPrincipal().getName());
+ } else if (super.isOidcUser()) {
+ user = qm.getOidcUser(getPrincipal().getName());
+ } else {
+ return Response.status(401).build();
+ }
+ final List allTeams = qm.getTeams();
+ final List configProperties = qm.getConfigProperties();
+ for (final ConfigProperty configProperty : configProperties) {
+ // Replace the value of encrypted strings with the pre-defined placeholder
+ if (configProperty.getGroupName().equals("access-management")
+ && configProperty.getPropertyName().equals("acl.enabled")) {
+ required = configProperty.getPropertyValue().equals("true");
+ break;
+ }
+ }
+ qm.getPersistenceManager().detachCopyAll(configProperties);
+ qm.close();
+ List permissions = user.getPermissions();
+ for (Permission permission : permissions) {
+ if (permission.getName().equals("ACCESS_MANAGEMENT")) {
+ isAllTeams = true;
+ break;
+ }
+ }
+ AvailableTeams response = new AvailableTeams();
+ response.setRequired(required);
+ List availableTeams = new ArrayList();
+ List teams = isAllTeams ? allTeams : user.getTeams();
+ for (Team team : teams) {
+ LittleTeam newTeam = new LittleTeam();
+ newTeam.setValue(team.getUuid());
+ newTeam.setText(team.getName());
+ availableTeams.add(newTeam);
+
+ }
+ response.setTeams(availableTeams);
+ return Response.ok(response).build();
+ }
+ }
+
@PUT
@Path("/{uuid}/key")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Generates an API key and returns its value",
- description = "Requires permission ACCESS_MANAGEMENT
"
- )
+ @Operation(summary = "Generates an API key and returns its value", description = "Requires permission ACCESS_MANAGEMENT
")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "201",
- description = "The created API key",
- content = @Content(schema = @Schema(implementation = ApiKey.class))
- ),
+ @ApiResponse(responseCode = "201", description = "The created API key", content = @Content(schema = @Schema(implementation = ApiKey.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The team could not be found")
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
public Response generateApiKey(
- @Parameter(description = "The UUID of the team to generate a key for", schema = @Schema(type = "string", format = "uuid"), required = true)
- @PathParam("uuid") @ValidUuid String uuid) {
+ @Parameter(description = "The UUID of the team to generate a key for", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid) {
try (QueryManager qm = new QueryManager()) {
final Team team = qm.getObjectByUuid(Team.class, uuid);
if (team != null) {
@@ -254,23 +277,15 @@ public Response generateApiKey(
@POST
@Path("/key/{apikey}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Regenerates an API key by removing the specified key, generating a new one and returning its value",
- description = "Requires permission ACCESS_MANAGEMENT
"
- )
+ @Operation(summary = "Regenerates an API key by removing the specified key, generating a new one and returning its value", description = "Requires permission ACCESS_MANAGEMENT
")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "The re-generated API key",
- content = @Content(schema = @Schema(implementation = ApiKey.class))
- ),
+ @ApiResponse(responseCode = "200", description = "The re-generated API key", content = @Content(schema = @Schema(implementation = ApiKey.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The API key could not be found")
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
public Response regenerateApiKey(
- @Parameter(description = "The API key to regenerate", required = true)
- @PathParam("apikey") String apikey) {
+ @Parameter(description = "The API key to regenerate", required = true) @PathParam("apikey") String apikey) {
try (QueryManager qm = new QueryManager()) {
ApiKey apiKey = qm.getApiKey(apikey);
if (apiKey != null) {
@@ -286,22 +301,15 @@ public Response regenerateApiKey(
@Path("/key/{key}/comment")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Updates an API key's comment",
- description = "Requires permission ACCESS_MANAGEMENT
"
- )
+ @Operation(summary = "Updates an API key's comment", description = "Requires permission ACCESS_MANAGEMENT
")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "The updated API key",
- content = @Content(schema = @Schema(implementation = ApiKey.class))
- ),
+ @ApiResponse(responseCode = "200", description = "The updated API key", content = @Content(schema = @Schema(implementation = ApiKey.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The API key could not be found")
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
public Response updateApiKeyComment(@PathParam("key") final String key,
- final String comment) {
+ final String comment) {
try (final var qm = new QueryManager()) {
qm.getPersistenceManager().setProperty(PROPERTY_RETAIN_VALUES, "true");
@@ -322,10 +330,7 @@ public Response updateApiKeyComment(@PathParam("key") final String key,
@DELETE
@Path("/key/{apikey}")
- @Operation(
- summary = "Deletes the specified API key",
- description = "Requires permission ACCESS_MANAGEMENT
"
- )
+ @Operation(summary = "Deletes the specified API key", description = "Requires permission ACCESS_MANAGEMENT
")
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "API key removed successfully"),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@@ -333,8 +338,7 @@ public Response updateApiKeyComment(@PathParam("key") final String key,
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
public Response deleteApiKey(
- @Parameter(description = "The API key to delete", required = true)
- @PathParam("apikey") String apikey) {
+ @Parameter(description = "The API key to delete", required = true) @PathParam("apikey") String apikey) {
try (QueryManager qm = new QueryManager()) {
final ApiKey apiKey = qm.getApiKey(apikey);
if (apiKey != null) {
@@ -349,14 +353,9 @@ public Response deleteApiKey(
@GET
@Path("self")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(
- summary = "Returns information about the current team.")
+ @Operation(summary = "Returns information about the current team.")
@ApiResponses(value = {
- @ApiResponse(
- responseCode = "200",
- description = "Information about the current team",
- content = @Content(schema = @Schema(implementation = TeamSelfResponse.class))
- ),
+ @ApiResponse(responseCode = "200", description = "Information about the current team", content = @Content(schema = @Schema(implementation = TeamSelfResponse.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "400", description = "Invalid API key supplied"),
@ApiResponse(responseCode = "404", description = "No Team for the given API key found")
@@ -365,19 +364,21 @@ public Response getSelf() {
if (Config.getInstance().getPropertyAsBoolean(Config.AlpineKey.ENFORCE_AUTHENTICATION)) {
try (var qm = new QueryManager()) {
if (isApiKey()) {
- final var apiKey = qm.getApiKey(((ApiKey)getPrincipal()).getKey());
+ final var apiKey = qm.getApiKey(((ApiKey) getPrincipal()).getKey());
final var team = apiKey.getTeams().stream().findFirst();
if (team.isPresent()) {
return Response.ok(new TeamSelfResponse(team.get())).build();
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("No Team for the given API key found.").build();
+ return Response.status(Response.Status.NOT_FOUND).entity("No Team for the given API key found.")
+ .build();
}
} else {
return Response.status(Response.Status.BAD_REQUEST).entity("Invalid API key supplied.").build();
}
}
}
- // Authentication is not enabled, but we need to return a positive response without any principal data.
+ // Authentication is not enabled, but we need to return a positive response
+ // without any principal data.
return Response.ok().build();
}
}
From 4af6f943eabf59183c164554fd00ffd2a833096e Mon Sep 17 00:00:00 2001
From: Thomas Schauer-Koeckeis
Date: Tue, 27 Aug 2024 10:02:06 +0200
Subject: [PATCH 02/13] Fixed most stuff from review
Signed-off-by: Thomas Schauer-Koeckeis
---
.../org/dependencytrack/model/Project.java | 41 +-
.../resources/v1/ProjectResource.java | 479 ++++++++++--------
.../resources/v1/TeamResource.java | 197 ++++---
.../resources/v1/vo/VisibleTeams.java | 27 +
4 files changed, 425 insertions(+), 319 deletions(-)
create mode 100644 src/main/java/org/dependencytrack/resources/v1/vo/VisibleTeams.java
diff --git a/src/main/java/org/dependencytrack/model/Project.java b/src/main/java/org/dependencytrack/model/Project.java
index 74b8480b3c..6fc9dae62d 100644
--- a/src/main/java/org/dependencytrack/model/Project.java
+++ b/src/main/java/org/dependencytrack/model/Project.java
@@ -190,8 +190,7 @@ public enum FetchGroup {
@Index(name = "PROJECT_CPE_IDX")
@Size(max = 255)
@JsonDeserialize(using = TrimmedStringDeserializer.class)
- // Patterns obtained from
- // https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd
+ //Patterns obtained from https://csrc.nist.gov/schema/cpe/2.3/cpe-naming_2.3.xsd
@Pattern(regexp = "(cpe:2\\.3:[aho\\*\\-](:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!\"#$$%&'\\(\\)\\+,/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){5}(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\\*\\-]))(:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|(\\\\[\\\\\\*\\?!\"#$$%&'\\(\\)\\+,/:;<=>@\\[\\]\\^`\\{\\|}~]))+(\\?*|\\*?))|[\\*\\-])){4})|([c][pP][eE]:/[AHOaho]?(:[A-Za-z0-9\\._\\-~%]*){0,6})", message = "The CPE must conform to the CPE v2.2 or v2.3 specification defined by NIST")
private String cpe;
@@ -224,7 +223,7 @@ public enum FetchGroup {
@Persistent
@Column(name = "PARENT_PROJECT_ID")
- @JsonIncludeProperties(value = { "name", "version", "uuid" })
+ @JsonIncludeProperties(value = {"name", "version", "uuid"})
private Project parent;
@Persistent(mappedBy = "parent")
@@ -241,8 +240,7 @@ public enum FetchGroup {
private List tags;
/**
- * Convenience field which will contain the date of the last entry in the
- * {@link Bom} table
+ * Convenience field which will contain the date of the last entry in the {@link Bom} table
*/
@Persistent
@Index(name = "PROJECT_LASTBOMIMPORT_IDX")
@@ -251,8 +249,7 @@ public enum FetchGroup {
private Date lastBomImport;
/**
- * Convenience field which will contain the format of the last entry in the
- * {@link Bom} table
+ * Convenience field which will contain the format of the last entry in the {@link Bom} table
*/
@Persistent
@Index(name = "PROJECT_LASTBOMIMPORT_FORMAT_IDX")
@@ -260,8 +257,7 @@ public enum FetchGroup {
private String lastBomImportFormat;
/**
- * Convenience field which stores the Inherited Risk Score (IRS) of the last
- * metric in the {@link ProjectMetrics} table
+ * Convenience field which stores the Inherited Risk Score (IRS) of the last metric in the {@link ProjectMetrics} table
*/
@Persistent
@Index(name = "PROJECT_LAST_RISKSCORE_IDX")
@@ -277,7 +273,7 @@ public enum FetchGroup {
@Join(column = "PROJECT_ID")
@Element(column = "TEAM_ID")
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
- @JsonIgnore
+ //@JsonIgnore
private List accessTeams;
@Persistent(defaultFetchGroup = "true")
@@ -293,7 +289,6 @@ public enum FetchGroup {
private transient ProjectMetrics metrics;
private transient List versions;
private transient List dependencyGraph;
- private UUID initialTeam;
public long getId() {
return id;
@@ -313,22 +308,20 @@ public void setAuthors(List authors) {
@Deprecated
@JsonInclude(JsonInclude.Include.NON_EMPTY)
- public String getAuthor() {
+ public String getAuthor(){
return ModelConverter.convertContactsToString(this.authors);
}
@Deprecated
- public void setAuthor(String author) {
- if (this.authors == null) {
+ public void setAuthor(String author){
+ if(this.authors==null){
this.authors = new ArrayList<>();
- } else {
+ } else{
this.authors.clear();
}
- this.authors.add(new OrganizationalContact() {
- {
- setName(author);
- }
- });
+ this.authors.add(new OrganizationalContact() {{
+ setName(author);
+ }});
}
public String getPublisher() {
@@ -567,14 +560,6 @@ public void setMetadata(final ProjectMetadata metadata) {
this.metadata = metadata;
}
- public UUID getInitialTeam() {
- return this.initialTeam;
- }
-
- public void setInitialTeam(UUID initialTeam) {
- this.initialTeam = initialTeam;
- }
-
@JsonIgnore
public List getDependencyGraph() {
return dependencyGraph;
diff --git a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
index ef8ed26d49..31c84c83e4 100644
--- a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
@@ -20,10 +20,9 @@
import alpine.common.logging.Logger;
import alpine.event.framework.Event;
+import alpine.model.ApiKey;
import alpine.model.Team;
import alpine.model.UserPrincipal;
-import alpine.model.ConfigProperty;
-import alpine.model.Permission;
import alpine.persistence.PaginatedResult;
import alpine.server.auth.PermissionRequired;
import alpine.server.resources.AlpineResource;
@@ -42,6 +41,7 @@
import org.dependencytrack.auth.Permissions;
import org.dependencytrack.event.CloneProjectEvent;
import org.dependencytrack.model.Classifier;
+import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.Tag;
import org.dependencytrack.model.validation.ValidUuid;
@@ -65,10 +65,10 @@
import jakarta.ws.rs.core.Response;
import javax.jdo.FetchGroup;
import java.security.Principal;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
-import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Function;
@@ -90,31 +90,39 @@ public class ProjectResource extends AlpineResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a list of all projects", description = "Requires permission VIEW_PORTFOLIO
")
+ @Operation(
+ summary = "Returns a list of all projects",
+ description = "Requires permission VIEW_PORTFOLIO
"
+ )
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "A list of all projects", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "A list of all projects",
+ headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
- public Response getProjects(
- @Parameter(description = "The optional name of the project to query on", required = false) @QueryParam("name") String name,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive,
- @Parameter(description = "Optionally excludes children projects from being returned", required = false) @QueryParam("onlyRoot") boolean onlyRoot,
- @Parameter(description = "The UUID of the team which projects shall be excluded", schema = @Schema(type = "string", format = "uuid"), required = false) @QueryParam("notAssignedToTeamWithUuid") @ValidUuid String notAssignedToTeamWithUuid) {
+ public Response getProjects(@Parameter(description = "The optional name of the project to query on", required = false)
+ @QueryParam("name") String name,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
+ @QueryParam("excludeInactive") boolean excludeInactive,
+ @Parameter(description = "Optionally excludes children projects from being returned", required = false)
+ @QueryParam("onlyRoot") boolean onlyRoot,
+ @Parameter(description = "The UUID of the team which projects shall be excluded", schema = @Schema(type = "string", format = "uuid"), required = false)
+ @QueryParam("notAssignedToTeamWithUuid") @ValidUuid String notAssignedToTeamWithUuid) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
Team notAssignedToTeam = null;
if (StringUtils.isNotEmpty(notAssignedToTeamWithUuid)) {
notAssignedToTeam = qm.getObjectByUuid(Team.class, notAssignedToTeamWithUuid);
if (notAssignedToTeam == null) {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the team could not be found.")
- .build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the team could not be found.").build();
}
}
- final PaginatedResult result = (name != null)
- ? qm.getProjects(name, excludeInactive, onlyRoot, notAssignedToTeam)
- : qm.getProjects(true, excludeInactive, onlyRoot, notAssignedToTeam);
+ final PaginatedResult result = (name != null) ? qm.getProjects(name, excludeInactive, onlyRoot, notAssignedToTeam) : qm.getProjects(true, excludeInactive, onlyRoot, notAssignedToTeam);
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build();
}
}
@@ -122,24 +130,31 @@ public Response getProjects(
@GET
@Path("/{uuid}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a specific project", description = "Requires permission VIEW_PORTFOLIO
")
+ @Operation(
+ summary = "Returns a specific project",
+ description = "Requires permission VIEW_PORTFOLIO
"
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "A specific project", content = @Content(schema = @Schema(implementation = Project.class))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "A specific project",
+ content = @Content(schema = @Schema(implementation = Project.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Access to the specified project is forbidden"),
@ApiResponse(responseCode = "404", description = "The project could not be found")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getProject(
- @Parameter(description = "The UUID of the project to retrieve", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid) {
+ @Parameter(description = "The UUID of the project to retrieve", schema = @Schema(type = "string", format = "uuid"), required = true)
+ @PathParam("uuid") @ValidUuid String uuid) {
try (QueryManager qm = new QueryManager()) {
final Project project = qm.getProject(uuid);
if (project != null) {
if (qm.hasAccess(super.getPrincipal(), project)) {
return Response.ok(project).build();
} else {
- return Response.status(Response.Status.FORBIDDEN)
- .entity("Access to the specified project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
}
} else {
return Response.status(Response.Status.NOT_FOUND).entity("The project could not be found.").build();
@@ -150,25 +165,34 @@ public Response getProject(
@GET
@Path("/lookup")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a specific project by its name and version", operationId = "getProjectByNameAndVersion", description = "Requires permission VIEW_PORTFOLIO
")
+ @Operation(
+ summary = "Returns a specific project by its name and version",
+ operationId = "getProjectByNameAndVersion",
+ description = "Requires permission VIEW_PORTFOLIO
"
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "A specific project by its name and version", content = @Content(schema = @Schema(implementation = Project.class))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "A specific project by its name and version",
+ content = @Content(schema = @Schema(implementation = Project.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Access to the specified project is forbidden"),
@ApiResponse(responseCode = "404", description = "The project could not be found")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getProject(
- @Parameter(description = "The name of the project to query on", required = true) @QueryParam("name") String name,
- @Parameter(description = "The version of the project to query on", required = true) @QueryParam("version") String version) {
+ @Parameter(description = "The name of the project to query on", required = true)
+ @QueryParam("name") String name,
+ @Parameter(description = "The version of the project to query on", required = true)
+ @QueryParam("version") String version) {
try (QueryManager qm = new QueryManager()) {
final Project project = qm.getProject(name, version);
if (project != null) {
if (qm.hasAccess(super.getPrincipal(), project)) {
return Response.ok(project).build();
} else {
- return Response.status(Response.Status.FORBIDDEN)
- .entity("Access to the specified project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
}
} else {
return Response.status(Response.Status.NOT_FOUND).entity("The project could not be found.").build();
@@ -179,17 +203,28 @@ public Response getProject(
@GET
@Path("/tag/{tag}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a list of all projects by tag", description = "Requires permission VIEW_PORTFOLIO
")
+ @Operation(
+ summary = "Returns a list of all projects by tag",
+ description = "Requires permission VIEW_PORTFOLIO
"
+ )
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "A list of all projects by tag", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "A list of all projects by tag",
+ headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getProjectsByTag(
- @Parameter(description = "The tag to query on", required = true) @PathParam("tag") String tagString,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive,
- @Parameter(description = "Optionally excludes children projects from being returned", required = false) @QueryParam("onlyRoot") boolean onlyRoot) {
+ @Parameter(description = "The tag to query on", required = true)
+ @PathParam("tag") String tagString,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
+ @QueryParam("excludeInactive") boolean excludeInactive,
+ @Parameter(description = "Optionally excludes children projects from being returned", required = false)
+ @QueryParam("onlyRoot") boolean onlyRoot) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final Tag tag = qm.getTagByName(tagString);
final PaginatedResult result = qm.getProjects(tag, true, excludeInactive, onlyRoot);
@@ -200,36 +235,53 @@ public Response getProjectsByTag(
@GET
@Path("/classifier/{classifier}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a list of all projects by classifier", description = "Requires permission VIEW_PORTFOLIO
")
+ @Operation(
+ summary = "Returns a list of all projects by classifier",
+ description = "Requires permission VIEW_PORTFOLIO
"
+ )
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "A list of all projects by classifier", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "A list of all projects by classifier",
+ headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getProjectsByClassifier(
- @Parameter(description = "The classifier to query on", required = true) @PathParam("classifier") String classifierString,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive,
- @Parameter(description = "Optionally excludes children projects from being returned", required = false) @QueryParam("onlyRoot") boolean onlyRoot) {
+ @Parameter(description = "The classifier to query on", required = true)
+ @PathParam("classifier") String classifierString,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
+ @QueryParam("excludeInactive") boolean excludeInactive,
+ @Parameter(description = "Optionally excludes children projects from being returned", required = false)
+ @QueryParam("onlyRoot") boolean onlyRoot) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final Classifier classifier = Classifier.valueOf(classifierString);
final PaginatedResult result = qm.getProjects(classifier, true, excludeInactive, onlyRoot);
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build();
} catch (IllegalArgumentException e) {
- return Response.status(Response.Status.BAD_REQUEST).entity("The classifier type specified is not valid.")
- .build();
+ return Response.status(Response.Status.BAD_REQUEST).entity("The classifier type specified is not valid.").build();
}
}
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Creates a new project", description = """
- If a parent project exists, parent.uuid
is required
- Requires permission PORTFOLIO_MANAGEMENT
- """)
+ @Operation(
+ summary = "Creates a new project",
+ description = """
+ If a parent project exists, parent.uuid
is required
+ Requires permission PORTFOLIO_MANAGEMENT
+ """
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "201", description = "The created project", content = @Content(schema = @Schema(implementation = Project.class))),
+ @ApiResponse(
+ responseCode = "201",
+ description = "The created project",
+ content = @Content(schema = @Schema(implementation = Project.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "409", description = """
@@ -252,85 +304,58 @@ public Response createProject(Project jsonProject) {
validator.validateProperty(jsonProject, "cpe"),
validator.validateProperty(jsonProject, "purl"),
validator.validateProperty(jsonProject, "swidTagId"),
- validator.validateProperty(jsonProject, "initialTeam"));
+ validator.validateProperty(jsonProject, "accessTeams")
+ );
if (jsonProject.getClassifier() == null) {
jsonProject.setClassifier(Classifier.APPLICATION);
}
try (QueryManager qm = new QueryManager()) {
if (jsonProject.getParent() != null && jsonProject.getParent().getUuid() != null) {
Project parent = qm.getObjectByUuid(Project.class, jsonProject.getParent().getUuid());
- jsonProject.setParent(parent);
+ jsonProject.setParent(parent);
}
- 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) {
- LOGGER.debug(e.getMessage());
- return Response.status(Response.Status.CONFLICT)
- .entity("An inactive Parent cannot be selected as parent")
- .build();
- }
- UserPrincipal user;
- if (super.isLdapUser()) {
- user = qm.getLdapUser(getPrincipal().getName());
- } else if (super.isManagedUser()) {
- user = qm.getManagedUser(getPrincipal().getName());
- } else if (super.isOidcUser()) {
- user = qm.getOidcUser(getPrincipal().getName());
- } else {
- return Response.status(401).build();
- }
- boolean isAdmin = false;
- boolean required = false;
- final List configProperties = qm.getConfigProperties();
- for (final ConfigProperty configProperty : configProperties) {
- // Checks if User needs to supply a Team
- if (configProperty.getGroupName().equals("access-management")
- && configProperty.getPropertyName().equals("acl.enabled")) {
- required = configProperty.getPropertyValue().equals("true");
- break;
- }
- }
- List permissions = user.getPermissions();
- for (Permission permission : permissions) {
- // Checks if user has Right to submit any team to the project
- if (permission.getName().equals("ACCESS_MANAGEMENT")) {
- isAdmin = true;
+ final List choosenTeams = jsonProject.getAccessTeams();
+ LOGGER.info(choosenTeams.toString());
+ Principal principal = getPrincipal();
+ List userTeams = new ArrayList();
+ if (principal instanceof final UserPrincipal userPrincipal) {
+ userTeams = userPrincipal.getTeams();
+ } else if (principal instanceof final ApiKey apiKey) {
+ userTeams = apiKey.getTeams();
+ }
+ boolean required = qm.isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED);
+ boolean isAdmin = qm.hasAccessManagementPermission(principal);
+ if (required && choosenTeams.size() == 0) {
+ return Response.status(422).build();
+ }
+ List visibleTeams = isAdmin ? qm.getTeams() : userTeams;
+ boolean hasTeam;
+ for (Team choosenTeam : choosenTeams) {
+ hasTeam = false;
+ LOGGER.info(Boolean.toString(visibleTeams.contains(choosenTeam)) + choosenTeam.getName());
+ for (Team team : visibleTeams) {
+ if (team.getUuid().equals(choosenTeam.getUuid())) {
+ hasTeam = true;
break;
}
}
- if (required && jsonProject.getInitialTeam() == null) {
- return Response.status(422).build();
+ if (!hasTeam) {
+ return Response.status(403).build();
}
- final UUID teamUuid = jsonProject.getInitialTeam();
- if (!isAdmin) {
- boolean hasTeam = false;
- List teams = user.getTeams();
- for (Team team : teams) {
- if (team.getUuid().equals(teamUuid)) {
- hasTeam = true;
- break;
- }
- }
- if (!hasTeam) {
- return Response.status(403).build();
- }
-
- }
- if (jsonProject.getInitialTeam() != null) {
- final Team team = qm.getObjectByUuid(Team.class, teamUuid);
- project.addAccessTeam(team);
+ }
+ 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){
+ LOGGER.debug(e.getMessage());
+ return Response.status(Response.Status.CONFLICT).entity("An inactive Parent cannot be selected as parent").build();
}
- 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 {
- return Response.status(Response.Status.CONFLICT)
- .entity("A project with the specified name already exists.")
- .build();
+ return Response.status(Response.Status.CONFLICT).entity("A project with the specified name already exists.").build();
}
}
}
@@ -338,9 +363,16 @@ public Response createProject(Project jsonProject) {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Updates a project", description = "Requires permission PORTFOLIO_MANAGEMENT
")
+ @Operation(
+ summary = "Updates a project",
+ description = "Requires permission PORTFOLIO_MANAGEMENT
"
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "The updated project", content = @Content(schema = @Schema(implementation = Project.class))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "The updated project",
+ content = @Content(schema = @Schema(implementation = Project.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found"),
@ApiResponse(responseCode = "409", description = """
@@ -364,7 +396,8 @@ public Response updateProject(Project jsonProject) {
validator.validateProperty(jsonProject, "classifier"),
validator.validateProperty(jsonProject, "cpe"),
validator.validateProperty(jsonProject, "purl"),
- validator.validateProperty(jsonProject, "swidTagId"));
+ validator.validateProperty(jsonProject, "swidTagId")
+ );
if (jsonProject.getClassifier() == null) {
jsonProject.setClassifier(Classifier.APPLICATION);
}
@@ -372,8 +405,7 @@ public Response updateProject(Project jsonProject) {
Project project = qm.getObjectByUuid(Project.class, jsonProject.getUuid());
if (project != null) {
if (!qm.hasAccess(super.getPrincipal(), project)) {
- return Response.status(Response.Status.FORBIDDEN)
- .entity("Access to the specified project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
}
final String name = StringUtils.trimToNull(jsonProject.getName());
final String version = StringUtils.trimToNull(jsonProject.getVersion());
@@ -385,19 +417,17 @@ public Response updateProject(Project jsonProject) {
}
try {
project = qm.updateProject(jsonProject, true);
- } catch (IllegalArgumentException e) {
+ } catch (IllegalArgumentException e){
LOGGER.debug(e.getMessage());
return Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build();
}
LOGGER.info("Project " + project.toString() + " updated by " + super.getPrincipal().getName());
return Response.ok(project).build();
} else {
- return Response.status(Response.Status.CONFLICT)
- .entity("A project with the specified name and version already exists.").build();
+ return Response.status(Response.Status.CONFLICT).entity("A project with the specified name and version already exists.").build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
- .build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
}
}
}
@@ -406,9 +436,16 @@ public Response updateProject(Project jsonProject) {
@Path("/{uuid}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Partially updates a project", description = "Requires permission PORTFOLIO_MANAGEMENT
")
+ @Operation(
+ summary = "Partially updates a project",
+ description = "Requires permission PORTFOLIO_MANAGEMENT
"
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "The updated project", content = @Content(schema = @Schema(implementation = Project.class))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "The updated project",
+ content = @Content(schema = @Schema(implementation = Project.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found"),
@ApiResponse(responseCode = "409", description = """
@@ -421,7 +458,8 @@ public Response updateProject(Project jsonProject) {
})
@PermissionRequired(Permissions.Constants.PORTFOLIO_MANAGEMENT)
public Response patchProject(
- @Parameter(description = "The UUID of the project to modify", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid,
+ @Parameter(description = "The UUID of the project to modify", schema = @Schema(type = "string", format = "uuid"), required = true)
+ @PathParam("uuid") @ValidUuid String uuid,
Project jsonProject) {
final Validator validator = getValidator();
failOnValidationError(
@@ -435,24 +473,22 @@ public Response patchProject(
validator.validateProperty(jsonProject, "classifier"),
validator.validateProperty(jsonProject, "cpe"),
validator.validateProperty(jsonProject, "purl"),
- validator.validateProperty(jsonProject, "swidTagId"));
+ validator.validateProperty(jsonProject, "swidTagId")
+ );
try (QueryManager qm = new QueryManager()) {
Project project = qm.getObjectByUuid(Project.class, uuid);
if (project != null) {
if (!qm.hasAccess(super.getPrincipal(), project)) {
- return Response.status(Response.Status.FORBIDDEN)
- .entity("Access to the specified project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
}
var modified = false;
project = qm.detachWithGroups(project, List.of(FetchGroup.DEFAULT, Project.FetchGroup.PARENT.name()));
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 either name or version has been changed, verify that this new combination does not already exist
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();
+ return Response.status(Response.Status.CONFLICT).entity("A project with the specified name and version already exists.").build();
}
modified |= setIfDifferent(jsonProject, project, Project::getAuthors, Project::setAuthors);
modified |= setIfDifferent(jsonProject, project, Project::getPublisher, Project::setPublisher);
@@ -468,12 +504,10 @@ public Response patchProject(
if (jsonProject.getParent() != null && jsonProject.getParent().getUuid() != null) {
final Project parent = qm.getObjectByUuid(Project.class, jsonProject.getParent().getUuid());
if (parent == null) {
- return Response.status(Response.Status.NOT_FOUND)
- .entity("The UUID of the parent project could not be found.").build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the parent project could not be found.").build();
}
if (!qm.hasAccess(getPrincipal(), parent)) {
- return Response.status(Response.Status.FORBIDDEN)
- .entity("Access to the specified parent project is forbidden").build();
+ return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified parent project is forbidden").build();
}
modified |= project.getParent() == null || !parent.getUuid().equals(project.getParent().getUuid());
project.setParent(parent);
@@ -483,13 +517,13 @@ public Response patchProject(
project.setTags(jsonProject.getTags());
}
if (isCollectionModified(jsonProject.getExternalReferences(), project.getExternalReferences())) {
- modified = true;
- project.setExternalReferences(jsonProject.getExternalReferences());
+ modified = true;
+ project.setExternalReferences(jsonProject.getExternalReferences());
}
if (modified) {
try {
project = qm.updateProject(project, true);
- } catch (IllegalArgumentException e) {
+ } catch (IllegalArgumentException e){
LOGGER.debug(e.getMessage());
return Response.status(Response.Status.CONFLICT).entity(e.getMessage()).build();
}
@@ -499,18 +533,16 @@ public Response patchProject(
return Response.notModified().build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
- .build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
}
}
}
/**
- * returns `true` if the given [updated] collection should be considered an
- * update of the [original] collection.
+ * returns `true` if the given [updated] collection should be considered an update of the [original] collection.
*/
private static boolean isCollectionModified(Collection updated, Collection original) {
- return updated != null && (!Collections.isEmpty(updated) || !Collections.isEmpty(original));
+ return updated != null && (!Collections.isEmpty(updated) || !Collections.isEmpty(original));
}
/**
@@ -519,17 +551,16 @@ private static boolean isCollectionModified(Collection updated, Collectio
* only if the new value is not {@code null} and it is not
* {@link Object#equals(java.lang.Object) equal to} the old value.
*
- * @param the type of the old and new value
+ * @param the type of the old and new value
* @param source the source object that contains the new value
* @param target the target object that should be updated
* @param getter the method to retrieve the new value from {@code source}
- * and the old value from {@code target}
+ * and the old value from {@code target}
* @param setter the method to set the new value on {@code target}
* @return {@code true} if {@code target} has been changed, else
- * {@code false}
+ * {@code false}
*/
- private boolean setIfDifferent(final Project source, final Project target, final Function getter,
- final BiConsumer setter) {
+ private boolean setIfDifferent(final Project source, final Project target, final Function getter, final BiConsumer setter) {
final T newValue = getter.apply(source);
if (newValue != null && !newValue.equals(getter.apply(target))) {
setter.accept(target, newValue);
@@ -543,7 +574,10 @@ private boolean setIfDifferent(final Project source, final Project target, f
@Path("/{uuid}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Deletes a project", description = "Requires permission PORTFOLIO_MANAGEMENT
")
+ @Operation(
+ summary = "Deletes a project",
+ description = "Requires permission PORTFOLIO_MANAGEMENT
"
+ )
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Project removed successfully"),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@@ -552,7 +586,8 @@ private boolean setIfDifferent(final Project source, final Project target, f
})
@PermissionRequired(Permissions.Constants.PORTFOLIO_MANAGEMENT)
public Response deleteProject(
- @Parameter(description = "The UUID of the project to delete", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid) {
+ @Parameter(description = "The UUID of the project to delete", schema = @Schema(type = "string", format = "uuid"), required = true)
+ @PathParam("uuid") @ValidUuid String uuid) {
try (QueryManager qm = new QueryManager()) {
final Project project = qm.getObjectByUuid(Project.class, uuid, Project.FetchGroup.ALL.name());
if (project != null) {
@@ -561,13 +596,10 @@ public Response deleteProject(
qm.recursivelyDelete(project, true);
return Response.status(Response.Status.NO_CONTENT).build();
} else {
- return Response.status(Response.Status.FORBIDDEN)
- .entity("Access to the specified project is forbidden")
- .build();
+ return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
- .build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
}
}
}
@@ -576,9 +608,16 @@ public Response deleteProject(
@Path("/clone")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Clones a project", description = "Requires permission PORTFOLIO_MANAGEMENT
")
+ @Operation(
+ summary = "Clones a project",
+ description = "Requires permission PORTFOLIO_MANAGEMENT
"
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "Token to be used for checking cloning progress", content = @Content(schema = @Schema(implementation = BomUploadResponse.class))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "Token to be used for checking cloning progress",
+ content = @Content(schema = @Schema(implementation = BomUploadResponse.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found")
})
@@ -587,19 +626,16 @@ public Response cloneProject(CloneProjectRequest jsonRequest) {
final Validator validator = super.getValidator();
failOnValidationError(
validator.validateProperty(jsonRequest, "project"),
- validator.validateProperty(jsonRequest, "version"));
+ validator.validateProperty(jsonRequest, "version")
+ );
try (QueryManager qm = new QueryManager()) {
- final Project sourceProject = qm.getObjectByUuid(Project.class, jsonRequest.getProject(),
- Project.FetchGroup.ALL.name());
+ final Project sourceProject = qm.getObjectByUuid(Project.class, jsonRequest.getProject(), Project.FetchGroup.ALL.name());
if (sourceProject != null) {
if (!qm.hasAccess(super.getPrincipal(), sourceProject)) {
- return Response.status(Response.Status.FORBIDDEN)
- .entity("Access to the specified project is forbidden")
- .build();
+ 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();
+ 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());
@@ -607,27 +643,36 @@ public Response cloneProject(CloneProjectRequest jsonRequest) {
Event.dispatch(event);
return Response.ok(java.util.Collections.singletonMap("token", event.getChainIdentifier())).build();
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
- .build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
}
}
}
+
@GET
@Path("/{uuid}/children")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a list of all children for a project", description = "Requires permission VIEW_PORTFOLIO
")
+ @Operation(
+ summary = "Returns a list of all children for a project",
+ description = "Requires permission VIEW_PORTFOLIO
"
+ )
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "A list of all children for a project", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "A list of all children for a project",
+ headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Access to the specified project is forbidden"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
- public Response getChildrenProjects(
- @Parameter(description = "The UUID of the project to get the children from", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive) {
+ public Response getChildrenProjects(@Parameter(description = "The UUID of the project to get the children from", schema = @Schema(type = "string", format = "uuid"), required = true)
+ @PathParam("uuid") @ValidUuid String uuid,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
+ @QueryParam("excludeInactive") boolean excludeInactive) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final Project project = qm.getObjectByUuid(Project.class, uuid);
if (project != null) {
@@ -635,13 +680,10 @@ public Response getChildrenProjects(
if (qm.hasAccess(super.getPrincipal(), project)) {
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build();
} else {
- return Response.status(Response.Status.FORBIDDEN)
- .entity("Access to the specified project is forbidden")
- .build();
+ return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
- .build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
}
}
}
@@ -649,35 +691,42 @@ public Response getChildrenProjects(
@GET
@Path("/{uuid}/children/classifier/{classifier}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a list of all children for a project by classifier", description = "Requires permission VIEW_PORTFOLIO
")
+ @Operation(
+ summary = "Returns a list of all children for a project by classifier",
+ description = "Requires permission VIEW_PORTFOLIO
"
+ )
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "A list of all children for a project by classifier", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "A list of all children for a project by classifier",
+ headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Access to the specified project is forbidden"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getChildrenProjectsByClassifier(
- @Parameter(description = "The classifier to query on", required = true) @PathParam("classifier") String classifierString,
- @Parameter(description = "The UUID of the project to get the children from", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive) {
+ @Parameter(description = "The classifier to query on", required = true)
+ @PathParam("classifier") String classifierString,
+ @Parameter(description = "The UUID of the project to get the children from", schema = @Schema(type = "string", format = "uuid"), required = true)
+ @PathParam("uuid") @ValidUuid String uuid,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
+ @QueryParam("excludeInactive") boolean excludeInactive) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final Project project = qm.getObjectByUuid(Project.class, uuid);
if (project != null) {
final Classifier classifier = Classifier.valueOf(classifierString);
- final PaginatedResult result = qm.getChildrenProjects(classifier, project.getUuid(), true,
- excludeInactive);
+ final PaginatedResult result = qm.getChildrenProjects(classifier, project.getUuid(), true, excludeInactive);
if (qm.hasAccess(super.getPrincipal(), project)) {
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build();
} else {
- return Response.status(Response.Status.FORBIDDEN)
- .entity("Access to the specified project is forbidden")
- .build();
+ return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
- .build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
}
}
}
@@ -685,19 +734,30 @@ public Response getChildrenProjectsByClassifier(
@GET
@Path("/{uuid}/children/tag/{tag}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a list of all children for a project by tag", description = "Requires permission VIEW_PORTFOLIO
")
+ @Operation(
+ summary = "Returns a list of all children for a project by tag",
+ description = "Requires permission VIEW_PORTFOLIO
"
+ )
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "A list of all children for a project by tag", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "A list of all children for a project by tag",
+ headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Access to the specified project is forbidden"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getChildrenProjectsByTag(
- @Parameter(description = "The tag to query on", required = true) @PathParam("tag") String tagString,
- @Parameter(description = "The UUID of the project to get the children from", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive) {
+ @Parameter(description = "The tag to query on", required = true)
+ @PathParam("tag") String tagString,
+ @Parameter(description = "The UUID of the project to get the children from", schema = @Schema(type = "string", format = "uuid"), required = true)
+ @PathParam("uuid") @ValidUuid String uuid,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
+ @QueryParam("excludeInactive") boolean excludeInactive) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final Project project = qm.getObjectByUuid(Project.class, uuid);
if (project != null) {
@@ -706,13 +766,10 @@ public Response getChildrenProjectsByTag(
if (qm.hasAccess(super.getPrincipal(), project)) {
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build();
} else {
- return Response.status(Response.Status.FORBIDDEN)
- .entity("Access to the specified project is forbidden")
- .build();
+ return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
- .build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
}
}
}
@@ -720,35 +777,41 @@ public Response getChildrenProjectsByTag(
@GET
@Path("/withoutDescendantsOf/{uuid}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a list of all projects without the descendants of the selected project", description = "Requires permission VIEW_PORTFOLIO
")
+ @Operation(
+ summary = "Returns a list of all projects without the descendants of the selected project",
+ description = "Requires permission VIEW_PORTFOLIO
"
+ )
@PaginatedApi
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "A list of all projects without the descendants of the selected project", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "A list of all projects without the descendants of the selected project",
+ headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of projects", schema = @Schema(format = "integer")),
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = Project.class)))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "403", description = "Access to the specified project is forbidden"),
@ApiResponse(responseCode = "404", description = "The UUID of the project could not be found")
})
@PermissionRequired(Permissions.Constants.VIEW_PORTFOLIO)
public Response getProjectsWithoutDescendantsOf(
- @Parameter(description = "The UUID of the project which descendants will be excluded", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid,
- @Parameter(description = "The optional name of the project to query on", required = false) @QueryParam("name") String name,
- @Parameter(description = "Optionally excludes inactive projects from being returned", required = false) @QueryParam("excludeInactive") boolean excludeInactive) {
+ @Parameter(description = "The UUID of the project which descendants will be excluded", schema = @Schema(type = "string", format = "uuid"), required = true)
+ @PathParam("uuid") @ValidUuid String uuid,
+ @Parameter(description = "The optional name of the project to query on", required = false)
+ @QueryParam("name") String name,
+ @Parameter(description = "Optionally excludes inactive projects from being returned", required = false)
+ @QueryParam("excludeInactive") boolean excludeInactive) {
try (QueryManager qm = new QueryManager(getAlpineRequest())) {
final Project project = qm.getObjectByUuid(Project.class, uuid);
if (project != null) {
if (qm.hasAccess(super.getPrincipal(), project)) {
- final PaginatedResult result = (name != null)
- ? qm.getProjectsWithoutDescendantsOf(name, excludeInactive, project)
- : qm.getProjectsWithoutDescendantsOf(excludeInactive, project);
+ final PaginatedResult result = (name != null) ? qm.getProjectsWithoutDescendantsOf(name, excludeInactive, project) : qm.getProjectsWithoutDescendantsOf(excludeInactive, project);
return Response.ok(result.getObjects()).header(TOTAL_COUNT_HEADER, result.getTotal()).build();
- } else {
- return Response.status(Response.Status.FORBIDDEN)
- .entity("Access to the specified project is forbidden")
- .build();
+ } else{
+ return Response.status(Response.Status.FORBIDDEN).entity("Access to the specified project is forbidden").build();
}
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.")
- .build();
+ return Response.status(Response.Status.NOT_FOUND).entity("The UUID of the project could not be found.").build();
}
}
}
diff --git a/src/main/java/org/dependencytrack/resources/v1/TeamResource.java b/src/main/java/org/dependencytrack/resources/v1/TeamResource.java
index a065858ea4..23817c545c 100644
--- a/src/main/java/org/dependencytrack/resources/v1/TeamResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/TeamResource.java
@@ -20,9 +20,7 @@
import alpine.Config;
import alpine.common.logging.Logger;
-import alpine.model.ConfigProperty;
import alpine.model.ApiKey;
-import alpine.model.Permission;
import alpine.model.Team;
import alpine.model.UserPrincipal;
import alpine.server.auth.PermissionRequired;
@@ -39,11 +37,11 @@
import io.swagger.v3.oas.annotations.security.SecurityRequirements;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.dependencytrack.auth.Permissions;
+import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.validation.ValidUuid;
-import org.dependencytrack.model.AvailableTeams;
-import org.dependencytrack.model.LittleTeam;
import org.dependencytrack.persistence.QueryManager;
import org.dependencytrack.resources.v1.vo.TeamSelfResponse;
+import org.dependencytrack.resources.v1.vo.VisibleTeams;
import org.owasp.security.logging.SecurityMarkers;
import jakarta.validation.Validator;
@@ -57,7 +55,7 @@
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
-
+import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
@@ -81,9 +79,17 @@ public class TeamResource extends AlpineResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a list of all teams", description = "Requires permission ACCESS_MANAGEMENT
")
+ @Operation(
+ summary = "Returns a list of all teams",
+ description = "Requires permission ACCESS_MANAGEMENT
"
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "A list of all teams", headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of teams", schema = @Schema(format = "integer")), content = @Content(array = @ArraySchema(schema = @Schema(implementation = Team.class)))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "A list of all teams",
+ headers = @Header(name = TOTAL_COUNT_HEADER, description = "The total number of teams", schema = @Schema(format = "integer")),
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = Team.class)))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
@@ -98,15 +104,23 @@ public Response getTeams() {
@GET
@Path("/{uuid}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a specific team", description = "Requires permission ACCESS_MANAGEMENT
")
+ @Operation(
+ summary = "Returns a specific team",
+ description = "Requires permission ACCESS_MANAGEMENT
"
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "A specific team", content = @Content(schema = @Schema(implementation = Team.class))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "A specific team",
+ content = @Content(schema = @Schema(implementation = Team.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The team could not be found")
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
public Response getTeam(
- @Parameter(description = "The UUID of the team to retrieve", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid) {
+ @Parameter(description = "The UUID of the team to retrieve", schema = @Schema(type = "string", format = "uuid"), required = true)
+ @PathParam("uuid") @ValidUuid String uuid) {
try (QueryManager qm = new QueryManager()) {
final Team team = qm.getObjectByUuid(Team.class, uuid);
if (team != null) {
@@ -120,18 +134,26 @@ public Response getTeam(
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Creates a new team", description = "Requires permission ACCESS_MANAGEMENT
")
+ @Operation(
+ summary = "Creates a new team",
+ description = "Requires permission ACCESS_MANAGEMENT
"
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "201", description = "The created team", content = @Content(schema = @Schema(implementation = Team.class))),
+ @ApiResponse(
+ responseCode = "201",
+ description = "The created team",
+ content = @Content(schema = @Schema(implementation = Team.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
- // public Response createTeam(String jsonRequest) {
+ //public Response createTeam(String jsonRequest) {
public Response createTeam(Team jsonTeam) {
- // Team team = MapperUtil.readAsObjectOf(Team.class, jsonRequest);
+ //Team team = MapperUtil.readAsObjectOf(Team.class, jsonRequest);
final Validator validator = super.getValidator();
failOnValidationError(
- validator.validateProperty(jsonTeam, "name"));
+ validator.validateProperty(jsonTeam, "name")
+ );
try (QueryManager qm = new QueryManager()) {
final Team team = qm.createTeam(jsonTeam.getName(), false);
@@ -143,9 +165,16 @@ public Response createTeam(Team jsonTeam) {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Updates a team's fields", description = "Requires permission ACCESS_MANAGEMENT
")
+ @Operation(
+ summary = "Updates a team's fields",
+ description = "Requires permission ACCESS_MANAGEMENT
"
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "The updated team", content = @Content(schema = @Schema(implementation = Team.class))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "The updated team",
+ content = @Content(schema = @Schema(implementation = Team.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The team could not be found")
})
@@ -153,12 +182,13 @@ public Response createTeam(Team jsonTeam) {
public Response updateTeam(Team jsonTeam) {
final Validator validator = super.getValidator();
failOnValidationError(
- validator.validateProperty(jsonTeam, "name"));
+ validator.validateProperty(jsonTeam, "name")
+ );
try (QueryManager qm = new QueryManager()) {
Team team = qm.getObjectByUuid(Team.class, jsonTeam.getUuid());
if (team != null) {
team.setName(jsonTeam.getName());
- // todo: set permissions
+ //todo: set permissions
team = qm.updateTeam(jsonTeam);
super.logSecurityEvent(LOGGER, SecurityMarkers.SECURITY_AUDIT, "Team updated: " + team.getName());
return Response.ok(team).build();
@@ -171,7 +201,10 @@ public Response updateTeam(Team jsonTeam) {
@DELETE
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Deletes a team", description = "Requires permission ACCESS_MANAGEMENT
")
+ @Operation(
+ summary = "Deletes a team",
+ description = "Requires permission ACCESS_MANAGEMENT
"
+ )
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "Team removed successfully"),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@@ -193,60 +226,28 @@ public Response deleteTeam(Team jsonTeam) {
}
@GET
- @Path("/available-teams")
+ @Path("/visible")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a list of Teams what are available as selection", description = "Requires permission PORTFOLIO_MANAGEMENT")
+ @Operation(summary = "Returns a list of Teams what are visible", description = "Requires permission PORTFOLIO_MANAGEMENT")
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "The Available Teams", content = @Content(schema = @Schema(implementation = AvailableTeams.class))),
+ @ApiResponse(responseCode = "200", description = "The Visible Teams", content = @Content(schema = @Schema(implementation = VisibleTeams.class))),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "Teams could not be found")
})
- @PermissionRequired(Permissions.Constants.PORTFOLIO_MANAGEMENT)
public Response availableTeams() {
- UserPrincipal user;
- boolean isAllTeams = false;
- boolean required = false;
try (QueryManager qm = new QueryManager()) {
- if (super.isLdapUser()) {
- user = qm.getLdapUser(getPrincipal().getName());
- } else if (super.isManagedUser()) {
- user = qm.getManagedUser(getPrincipal().getName());
- } else if (super.isOidcUser()) {
- user = qm.getOidcUser(getPrincipal().getName());
- } else {
- return Response.status(401).build();
- }
- final List allTeams = qm.getTeams();
- final List configProperties = qm.getConfigProperties();
- for (final ConfigProperty configProperty : configProperties) {
- // Replace the value of encrypted strings with the pre-defined placeholder
- if (configProperty.getGroupName().equals("access-management")
- && configProperty.getPropertyName().equals("acl.enabled")) {
- required = configProperty.getPropertyValue().equals("true");
- break;
- }
+ Principal user = getPrincipal();
+ List userTeams = new ArrayList();
+ if (user instanceof final UserPrincipal userPrincipal) {
+ userTeams = userPrincipal.getTeams();
+ } else if (user instanceof final ApiKey apiKey) {
+ userTeams = apiKey.getTeams();
}
- qm.getPersistenceManager().detachCopyAll(configProperties);
- qm.close();
- List permissions = user.getPermissions();
- for (Permission permission : permissions) {
- if (permission.getName().equals("ACCESS_MANAGEMENT")) {
- isAllTeams = true;
- break;
- }
- }
- AvailableTeams response = new AvailableTeams();
- response.setRequired(required);
- List availableTeams = new ArrayList();
- List teams = isAllTeams ? allTeams : user.getTeams();
- for (Team team : teams) {
- LittleTeam newTeam = new LittleTeam();
- newTeam.setValue(team.getUuid());
- newTeam.setText(team.getName());
- availableTeams.add(newTeam);
+ boolean required = qm.isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED);
+ boolean isAllTeams = qm.hasAccessManagementPermission(user);
+ List teams = isAllTeams ? qm.getTeams() : userTeams;
+ VisibleTeams response = new VisibleTeams(required, teams);
- }
- response.setTeams(availableTeams);
return Response.ok(response).build();
}
}
@@ -254,15 +255,23 @@ public Response availableTeams() {
@PUT
@Path("/{uuid}/key")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Generates an API key and returns its value", description = "Requires permission ACCESS_MANAGEMENT
")
+ @Operation(
+ summary = "Generates an API key and returns its value",
+ description = "Requires permission ACCESS_MANAGEMENT
"
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "201", description = "The created API key", content = @Content(schema = @Schema(implementation = ApiKey.class))),
+ @ApiResponse(
+ responseCode = "201",
+ description = "The created API key",
+ content = @Content(schema = @Schema(implementation = ApiKey.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The team could not be found")
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
public Response generateApiKey(
- @Parameter(description = "The UUID of the team to generate a key for", schema = @Schema(type = "string", format = "uuid"), required = true) @PathParam("uuid") @ValidUuid String uuid) {
+ @Parameter(description = "The UUID of the team to generate a key for", schema = @Schema(type = "string", format = "uuid"), required = true)
+ @PathParam("uuid") @ValidUuid String uuid) {
try (QueryManager qm = new QueryManager()) {
final Team team = qm.getObjectByUuid(Team.class, uuid);
if (team != null) {
@@ -277,15 +286,23 @@ public Response generateApiKey(
@POST
@Path("/key/{apikey}")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Regenerates an API key by removing the specified key, generating a new one and returning its value", description = "Requires permission ACCESS_MANAGEMENT
")
+ @Operation(
+ summary = "Regenerates an API key by removing the specified key, generating a new one and returning its value",
+ description = "Requires permission ACCESS_MANAGEMENT
"
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "The re-generated API key", content = @Content(schema = @Schema(implementation = ApiKey.class))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "The re-generated API key",
+ content = @Content(schema = @Schema(implementation = ApiKey.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The API key could not be found")
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
public Response regenerateApiKey(
- @Parameter(description = "The API key to regenerate", required = true) @PathParam("apikey") String apikey) {
+ @Parameter(description = "The API key to regenerate", required = true)
+ @PathParam("apikey") String apikey) {
try (QueryManager qm = new QueryManager()) {
ApiKey apiKey = qm.getApiKey(apikey);
if (apiKey != null) {
@@ -301,15 +318,22 @@ public Response regenerateApiKey(
@Path("/key/{key}/comment")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Updates an API key's comment", description = "Requires permission ACCESS_MANAGEMENT
")
+ @Operation(
+ summary = "Updates an API key's comment",
+ description = "Requires permission ACCESS_MANAGEMENT
"
+ )
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "The updated API key", content = @Content(schema = @Schema(implementation = ApiKey.class))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "The updated API key",
+ content = @Content(schema = @Schema(implementation = ApiKey.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "404", description = "The API key could not be found")
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
public Response updateApiKeyComment(@PathParam("key") final String key,
- final String comment) {
+ final String comment) {
try (final var qm = new QueryManager()) {
qm.getPersistenceManager().setProperty(PROPERTY_RETAIN_VALUES, "true");
@@ -330,7 +354,10 @@ public Response updateApiKeyComment(@PathParam("key") final String key,
@DELETE
@Path("/key/{apikey}")
- @Operation(summary = "Deletes the specified API key", description = "Requires permission ACCESS_MANAGEMENT
")
+ @Operation(
+ summary = "Deletes the specified API key",
+ description = "Requires permission ACCESS_MANAGEMENT
"
+ )
@ApiResponses(value = {
@ApiResponse(responseCode = "204", description = "API key removed successfully"),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@@ -338,7 +365,8 @@ public Response updateApiKeyComment(@PathParam("key") final String key,
})
@PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT)
public Response deleteApiKey(
- @Parameter(description = "The API key to delete", required = true) @PathParam("apikey") String apikey) {
+ @Parameter(description = "The API key to delete", required = true)
+ @PathParam("apikey") String apikey) {
try (QueryManager qm = new QueryManager()) {
final ApiKey apiKey = qm.getApiKey(apikey);
if (apiKey != null) {
@@ -353,9 +381,14 @@ public Response deleteApiKey(
@GET
@Path("self")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns information about the current team.")
+ @Operation(
+ summary = "Returns information about the current team.")
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "Information about the current team", content = @Content(schema = @Schema(implementation = TeamSelfResponse.class))),
+ @ApiResponse(
+ responseCode = "200",
+ description = "Information about the current team",
+ content = @Content(schema = @Schema(implementation = TeamSelfResponse.class))
+ ),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
@ApiResponse(responseCode = "400", description = "Invalid API key supplied"),
@ApiResponse(responseCode = "404", description = "No Team for the given API key found")
@@ -364,21 +397,19 @@ public Response getSelf() {
if (Config.getInstance().getPropertyAsBoolean(Config.AlpineKey.ENFORCE_AUTHENTICATION)) {
try (var qm = new QueryManager()) {
if (isApiKey()) {
- final var apiKey = qm.getApiKey(((ApiKey) getPrincipal()).getKey());
+ final var apiKey = qm.getApiKey(((ApiKey)getPrincipal()).getKey());
final var team = apiKey.getTeams().stream().findFirst();
if (team.isPresent()) {
return Response.ok(new TeamSelfResponse(team.get())).build();
} else {
- return Response.status(Response.Status.NOT_FOUND).entity("No Team for the given API key found.")
- .build();
+ return Response.status(Response.Status.NOT_FOUND).entity("No Team for the given API key found.").build();
}
} else {
return Response.status(Response.Status.BAD_REQUEST).entity("Invalid API key supplied.").build();
}
}
}
- // Authentication is not enabled, but we need to return a positive response
- // without any principal data.
+ // Authentication is not enabled, but we need to return a positive response without any principal data.
return Response.ok().build();
}
}
diff --git a/src/main/java/org/dependencytrack/resources/v1/vo/VisibleTeams.java b/src/main/java/org/dependencytrack/resources/v1/vo/VisibleTeams.java
new file mode 100644
index 0000000000..ba9f186262
--- /dev/null
+++ b/src/main/java/org/dependencytrack/resources/v1/vo/VisibleTeams.java
@@ -0,0 +1,27 @@
+/*
+ * 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.resources.v1.vo;
+
+import java.util.List;
+
+import alpine.model.Team;
+
+public record VisibleTeams(boolean required,
+ List teams) {
+}
From d5e095605d915637777123cfa64cf1b828a1628b Mon Sep 17 00:00:00 2001
From: Thomas Schauer-Koeckeis
Date: Tue, 27 Aug 2024 13:00:00 +0200
Subject: [PATCH 03/13] Now able to add accessTeam to the API Query and handles
right
Signed-off-by: Thomas Schauer-Koeckeis
---
.../org/dependencytrack/model/Project.java | 1 -
.../resources/v1/ProjectResource.java | 21 ++++++++-----------
2 files changed, 9 insertions(+), 13 deletions(-)
diff --git a/src/main/java/org/dependencytrack/model/Project.java b/src/main/java/org/dependencytrack/model/Project.java
index 6fc9dae62d..df8c5873b4 100644
--- a/src/main/java/org/dependencytrack/model/Project.java
+++ b/src/main/java/org/dependencytrack/model/Project.java
@@ -273,7 +273,6 @@ public enum FetchGroup {
@Join(column = "PROJECT_ID")
@Element(column = "TEAM_ID")
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
- //@JsonIgnore
private List accessTeams;
@Persistent(defaultFetchGroup = "true")
diff --git a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
index 31c84c83e4..e51db7ffbb 100644
--- a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
@@ -326,22 +326,19 @@ public Response createProject(Project jsonProject) {
boolean required = qm.isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED);
boolean isAdmin = qm.hasAccessManagementPermission(principal);
if (required && choosenTeams.size() == 0) {
- return Response.status(422).build();
+ return Response.status(422)
+ .entity("You need to specify at least one team to which the project should belong").build();
}
List visibleTeams = isAdmin ? qm.getTeams() : userTeams;
- boolean hasTeam;
+ jsonProject.setAccessTeams(new ArrayList());
for (Team choosenTeam : choosenTeams) {
- hasTeam = false;
- LOGGER.info(Boolean.toString(visibleTeams.contains(choosenTeam)) + choosenTeam.getName());
- for (Team team : visibleTeams) {
- if (team.getUuid().equals(choosenTeam.getUuid())) {
- hasTeam = true;
- break;
- }
- }
- if (!hasTeam) {
- return Response.status(403).build();
+ Team ormTeam = qm.getObjectByUuid(Team.class, choosenTeam.getUuid());
+ if (!visibleTeams.contains(ormTeam)) {
+ return isAdmin ? Response.status(404).entity("This team does not exist!").build()
+ : Response.status(403)
+ .entity("You don't have the permission to assign this team to a project.").build();
}
+ jsonProject.addAccessTeam(ormTeam);
}
if (!qm.doesProjectExist(StringUtils.trimToNull(jsonProject.getName()), StringUtils.trimToNull(jsonProject.getVersion()))) {
final Project project;
From 13ee8f4341a4b09d1f00840345fe6277e9f3922e Mon Sep 17 00:00:00 2001
From: Thomas Schauer-Koeckeis
Date: Tue, 27 Aug 2024 13:12:30 +0200
Subject: [PATCH 04/13] Deleted unused files
Signed-off-by: Thomas Schauer-Koeckeis
---
.../dependencytrack/model/AvailableTeams.java | 56 -------------------
.../org/dependencytrack/model/LittleTeam.java | 53 ------------------
2 files changed, 109 deletions(-)
delete mode 100644 src/main/java/org/dependencytrack/model/AvailableTeams.java
delete mode 100644 src/main/java/org/dependencytrack/model/LittleTeam.java
diff --git a/src/main/java/org/dependencytrack/model/AvailableTeams.java b/src/main/java/org/dependencytrack/model/AvailableTeams.java
deleted file mode 100644
index 1a4522166f..0000000000
--- a/src/main/java/org/dependencytrack/model/AvailableTeams.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.model;
-
-import java.io.Serializable;
-import java.util.List;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-
-@JsonInclude(JsonInclude.Include.NON_NULL)
-public class AvailableTeams implements Serializable {
- private boolean required;
- private List teams;
-
- public boolean isRequired() {
- return required;
- }
-
- public void setRequired(final boolean required) {
- this.required = required;
- }
-
- public List getTeams() {
- return teams;
- }
-
- public void setTeams(final List teams) {
- this.teams = teams;
- }
-
- @Override
- public String toString() {
- List strlistTeams = teams.stream()
- .map(Object::toString)
- .toList();
- String strTeams = String.join(",", strlistTeams);
- return String.format("required: %s, teams: [ %s ]", required, strTeams);
- }
-
-}
diff --git a/src/main/java/org/dependencytrack/model/LittleTeam.java b/src/main/java/org/dependencytrack/model/LittleTeam.java
deleted file mode 100644
index 5355b5a0e0..0000000000
--- a/src/main/java/org/dependencytrack/model/LittleTeam.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.model;
-
-import java.io.Serializable;
-
-import java.util.UUID;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-
-@JsonInclude(JsonInclude.Include.NON_NULL)
-public class LittleTeam implements Serializable {
-
- private UUID value;
- private String text;
-
- public String getText() {
- return text;
- }
-
- public void setText(String text) {
- this.text = text;
- }
-
- public UUID getValue() {
- return value;
- }
-
- public void setValue(UUID value) {
- this.value = value;
- }
-
- @Override
- public String toString() {
- return String.format("{value: %s, text: %s}", value.toString(), text);
- }
-}
From 639a4a58c394b40114afa9e32da574d7ac626151 Mon Sep 17 00:00:00 2001
From: Thomas Schauer-Koeckeis
Date: Tue, 27 Aug 2024 13:14:35 +0200
Subject: [PATCH 05/13] Removed Debug info
Signed-off-by: Thomas Schauer-Koeckeis
---
.../java/org/dependencytrack/resources/v1/ProjectResource.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
index e51db7ffbb..5edaf6bea5 100644
--- a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
@@ -315,7 +315,6 @@ public Response createProject(Project jsonProject) {
jsonProject.setParent(parent);
}
final List choosenTeams = jsonProject.getAccessTeams();
- LOGGER.info(choosenTeams.toString());
Principal principal = getPrincipal();
List userTeams = new ArrayList();
if (principal instanceof final UserPrincipal userPrincipal) {
From 78aa74b987372351eebdb3d8f8e692adc2323b6c Mon Sep 17 00:00:00 2001
From: Thomas Schauer-Koeckeis
Date: Tue, 27 Aug 2024 16:56:02 +0200
Subject: [PATCH 06/13] Fixed most tests
Signed-off-by: Thomas Schauer-Koeckeis
---
.../resources/v1/ProjectResourceTest.java | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
index acce124c10..adeabf8579 100644
--- a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
+++ b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
@@ -21,6 +21,7 @@
import alpine.common.util.UuidUtil;
import alpine.event.framework.EventService;
import alpine.model.IConfigProperty.PropertyType;
+import alpine.model.Team;
import alpine.server.filters.ApiFilter;
import alpine.server.filters.AuthenticationFilter;
import jakarta.json.Json;
@@ -284,6 +285,7 @@ public void getProjectByUuidTest() {
.withMatcher("childUuid", equalTo(childProject.getUuid().toString()))
.isEqualTo("""
{
+ "accessTeams": [],
"name": "acme-app",
"version": "1.0.0",
"uuid": "${json-unit.matches:projectUuid}",
@@ -413,6 +415,7 @@ public void getProjectByUnknownTagTest() {
@Test
public void createProjectTest(){
Project project = new Project();
+ project.setAccessTeams(new ArrayList());
project.setName("Acme Example");
project.setVersion("1.0");
project.setDescription("Test project");
@@ -433,6 +436,7 @@ public void createProjectTest(){
@Test
public void createProjectDuplicateTest() {
Project project = new Project();
+ project.setAccessTeams(new ArrayList());
project.setName("Acme Example");
project.setVersion("1.0");
Response response = jersey.target(V1_PROJECT)
@@ -452,6 +456,7 @@ public void createProjectDuplicateTest() {
@Test
public void createProjectWithoutVersionDuplicateTest() {
Project project = new Project();
+ project.setAccessTeams(new ArrayList());
project.setName("Acme Example");
Response response = jersey.target(V1_PROJECT)
.request()
@@ -708,6 +713,7 @@ public void patchProjectSuccessfullyPatchedTest() {
.withMatcher("projectUuid", equalTo(p1.getUuid().toString()))
.isEqualTo("""
{
+ "accessTeams": [],
"publisher": "new publisher",
"manufacturer": {
"name": "manufacturerName",
@@ -804,6 +810,7 @@ public void patchProjectParentTest() {
.withMatcher("parentProjectUuid", CoreMatchers.equalTo(newParent.getUuid().toString()))
.isEqualTo("""
{
+ "accessTeams": [],
"name": "DEF",
"version": "2.0",
"uuid": "${json-unit.matches:projectUuid}",
@@ -1162,6 +1169,7 @@ public void issue3883RegressionTest() {
.header(X_API_KEY, apiKey)
.put(Entity.json("""
{
+ "accessTeams": [],
"name": "acme-app-parent",
"version": "1.0.0"
}
@@ -1174,6 +1182,7 @@ public void issue3883RegressionTest() {
.header(X_API_KEY, apiKey)
.put(Entity.json("""
{
+ "accessTeams": [],
"name": "acme-app",
"version": "1.0.0",
"parent": {
@@ -1191,6 +1200,7 @@ public void issue3883RegressionTest() {
assertThat(response.getStatus()).isEqualTo(200);
assertThatJson(getPlainTextBody(response)).isEqualTo("""
{
+ "accessTeams": [],
"name": "acme-app-parent",
"version": "1.0.0",
"classifier": "APPLICATION",
@@ -1224,6 +1234,7 @@ public void issue3883RegressionTest() {
assertThat(response.getStatus()).isEqualTo(200);
assertThatJson(getPlainTextBody(response)).isEqualTo("""
{
+ "accessTeams": [],
"name": "acme-app",
"version": "1.0.0",
"classifier": "APPLICATION",
@@ -1264,7 +1275,8 @@ public void issue4048RegressionTest() {
final JsonObjectBuilder requestBodyBuilder = Json.createObjectBuilder()
.add("name", "project-%d-%d".formatted(i, j))
- .add("version", "%d.%d".formatted(i, j));
+ .add("version", "%d.%d".formatted(i, j))
+ .add("accessTeams", "[]");
if (parentUuid != null) {
requestBodyBuilder.add("parent", Json.createObjectBuilder()
.add("uuid", parentUuid.toString()));
From b24d6f90820cba80edcca40d98f7bc0e00998db5 Mon Sep 17 00:00:00 2001
From: Thomas Schauer-Koeckeis
Date: Wed, 28 Aug 2024 11:20:51 +0200
Subject: [PATCH 07/13] Fixed last failing test
Signed-off-by: Thomas Schauer-Koeckeis
---
.../org/dependencytrack/resources/v1/ProjectResourceTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
index adeabf8579..e3f5c7ea8d 100644
--- a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
+++ b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
@@ -1276,7 +1276,7 @@ public void issue4048RegressionTest() {
final JsonObjectBuilder requestBodyBuilder = Json.createObjectBuilder()
.add("name", "project-%d-%d".formatted(i, j))
.add("version", "%d.%d".formatted(i, j))
- .add("accessTeams", "[]");
+ .add("accessTeams",Json.createArrayBuilder().build());
if (parentUuid != null) {
requestBodyBuilder.add("parent", Json.createObjectBuilder()
.add("uuid", parentUuid.toString()));
From e663e7e70969f206a288ccd441a663d5c5a9e0f2 Mon Sep 17 00:00:00 2001
From: Thomas Schauer-Koeckeis
Date: Thu, 29 Aug 2024 11:10:26 +0200
Subject: [PATCH 08/13] Fixed Tests and bug for no Admin Users
Signed-off-by: Thomas Schauer-Koeckeis
---
.../resources/v1/ProjectResource.java | 8 +-
.../resources/v1/ProjectResourceTest.java | 120 ++++++++++++++++++
2 files changed, 125 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
index 5edaf6bea5..b85446aee7 100644
--- a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
@@ -69,6 +69,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Set;
+import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.function.Function;
@@ -324,19 +325,20 @@ public Response createProject(Project jsonProject) {
}
boolean required = qm.isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED);
boolean isAdmin = qm.hasAccessManagementPermission(principal);
- if (required && choosenTeams.size() == 0) {
+ if (required && choosenTeams.isEmpty()) {
return Response.status(422)
.entity("You need to specify at least one team to which the project should belong").build();
}
List visibleTeams = isAdmin ? qm.getTeams() : userTeams;
+ List visibleUuids = visibleTeams.isEmpty() ? new ArrayList(): visibleTeams.stream().map(Team::getUuid).toList();
jsonProject.setAccessTeams(new ArrayList());
for (Team choosenTeam : choosenTeams) {
- Team ormTeam = qm.getObjectByUuid(Team.class, choosenTeam.getUuid());
- if (!visibleTeams.contains(ormTeam)) {
+ if (!visibleUuids.contains(choosenTeam.getUuid())) {
return isAdmin ? Response.status(404).entity("This team does not exist!").build()
: Response.status(403)
.entity("You don't have the permission to assign this team to a project.").build();
}
+ Team ormTeam = qm.getObjectByUuid(Team.class, choosenTeam.getUuid());
jsonProject.addAccessTeam(ormTeam);
}
if (!qm.doesProjectExist(StringUtils.trimToNull(jsonProject.getName()), StringUtils.trimToNull(jsonProject.getVersion()))) {
diff --git a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
index e3f5c7ea8d..1418ab36e1 100644
--- a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
+++ b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
@@ -20,8 +20,12 @@
import alpine.common.util.UuidUtil;
import alpine.event.framework.EventService;
+import alpine.model.IConfigProperty;
+import alpine.model.ManagedUser;
import alpine.model.IConfigProperty.PropertyType;
import alpine.model.Team;
+import alpine.model.Permission;
+import alpine.server.auth.JsonWebToken;
import alpine.server.filters.ApiFilter;
import alpine.server.filters.AuthenticationFilter;
import jakarta.json.Json;
@@ -51,6 +55,7 @@
import org.dependencytrack.model.ServiceComponent;
import org.dependencytrack.model.Tag;
import org.dependencytrack.model.Vulnerability;
+import org.dependencytrack.persistence.DefaultObjectGenerator;
import org.dependencytrack.tasks.CloneProjectTask;
import org.dependencytrack.tasks.scanners.AnalyzerIdentity;
import org.glassfish.jersey.client.HttpUrlConnectorProvider;
@@ -76,6 +81,8 @@
import static org.hamcrest.Matchers.equalTo;
public class ProjectResourceTest extends ResourceTest {
+ private ManagedUser testUser;
+ private String jwt;
@ClassRule
public static JerseyTestRule jersey = new JerseyTestRule(
@@ -90,6 +97,23 @@ public void after() throws Exception {
super.after();
}
+ public void getUserToken(boolean isAdmin) {
+ testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH);
+ jwt = new JsonWebToken().createToken(testUser);
+ qm.addUserToTeam(testUser, team);
+ final var generator = new DefaultObjectGenerator();
+ generator.loadDefaultPermissions();
+ List permissionsList = new ArrayList();
+ final Permission permission = qm.getPermission("PORTFOLIO_MANAGEMENT");
+ permissionsList.add(permission);
+ testUser.setPermissions(permissionsList);
+ if (isAdmin) {
+ final Permission adminPermission = qm.getPermission("ACCESS_MANAGEMENT");
+ permissionsList.add(adminPermission);
+ testUser.setPermissions(permissionsList);
+ }
+ }
+
@Test
public void getProjectsDefaultRequestTest() {
for (int i=0; i<1000; i++) {
@@ -483,6 +507,102 @@ public void createProjectEmptyTest() {
Assert.assertEquals(400, response.getStatus(), 0);
}
+ @Test
+ public void createProjectWithExistingTeamRequiredTest() {
+ getUserToken(false);
+ Team AllowedTeam = qm.createTeam("AllowedTeam", false);
+ Project project = new Project();
+ project.setName("ProjectWithExistingTeamRequired");
+ qm.addUserToTeam(testUser, AllowedTeam);
+ qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
+ final JsonObject jsonTeam = Json.createObjectBuilder().add("uuid", AllowedTeam.getUuid().toString()).build();
+ final JsonObjectBuilder requestBodyBuilder = Json.createObjectBuilder()
+ .add("name", project.getName()).add("classifier", "CONTAINER").addNull("parent").add("active", true)
+ .add("accessTeams", Json.createArrayBuilder().add(jsonTeam).build());
+ Response response = jersey.target(V1_PROJECT)
+ .request()
+ .header("Authorization", "Bearer " + jwt)
+ .put(Entity.json(requestBodyBuilder.build().toString()));
+ Assert.assertEquals(201, response.getStatus(), 0);
+ }
+
+ @Test
+ public void createProjectWithoutExistingTeamRequiredTest() {
+ getUserToken(false);
+ Project project = new Project();
+ project.setName("ProjectWithoutExistingTeamRequired");
+ project.setAccessTeams(new ArrayList());
+ qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
+ Response response = jersey.target(V1_PROJECT)
+ .request()
+ .header("Authorization", "Bearer " + jwt)
+ .put(Entity.entity(project, MediaType.APPLICATION_JSON));
+ Assert.assertEquals(422, response.getStatus(), 0);
+ }
+
+ @Test
+ public void createProjectWithNotAllowedExistingTeamTest() {
+ getUserToken(false);
+ Team notAllowedTeam = qm.createTeam("NotAllowedTeam", false);
+ Project project = new Project();
+ project.setName("ProjectWithNotAllowedExistingTeam");
+ project.addAccessTeam(notAllowedTeam);
+ qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
+ Response response = jersey.target(V1_PROJECT)
+ .request()
+ .header("Authorization", "Bearer " + jwt)
+ .put(Entity.entity(project, MediaType.APPLICATION_JSON));
+ Assert.assertEquals(403, response.getStatus(), 0);
+ }
+
+ @Test
+ public void createProjectWithNotAllowedExistingTeamAdminTest() {
+ getUserToken(true);
+ Team notAllowedTeam = qm.createTeam("NotAllowedTeam", false);
+ Project project = new Project();
+ project.setName("ProjectWithNotAllowedExistingTeam");
+ project.addAccessTeam(notAllowedTeam);
+ qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
+ Response response = jersey.target(V1_PROJECT)
+ .request()
+ .header("Authorization", "Bearer " + jwt)
+ .put(Entity.entity(project, MediaType.APPLICATION_JSON));
+ Assert.assertEquals(201, response.getStatus(), 0);
+ }
+
+ @Test
+ public void createProjectWithNotExistingTeamNoAdminTest() {
+ getUserToken(false);
+ Team notAllowedTeam = new Team();
+ notAllowedTeam.setUuid(new UUID(1, 1));
+ notAllowedTeam.setName("NotAllowedTeam");
+ Project project = new Project();
+ project.addAccessTeam(notAllowedTeam);
+ project.setName("ProjectWithNotAllowedExistingTeam");
+ qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
+ Response response = jersey.target(V1_PROJECT)
+ .request()
+ .header("Authorization", "Bearer " + jwt)
+ .put(Entity.entity(project, MediaType.APPLICATION_JSON));
+ Assert.assertEquals(403, response.getStatus(), 0);
+ }
+
+ @Test
+ public void createProjectWithNotExistingTeamTest() {
+ getUserToken(true);
+ Team notAllowedTeam = new Team();
+ notAllowedTeam.setUuid(new UUID(1, 1));
+ notAllowedTeam.setName("NotAllowedTeam");
+ Project project = new Project();
+ project.addAccessTeam(notAllowedTeam);
+ project.setName("ProjectWithNotExistingTeam");
+ Response response = jersey.target(V1_PROJECT)
+ .request()
+ .header("Authorization", "Bearer " + jwt)
+ .put(Entity.entity(project, MediaType.APPLICATION_JSON));
+ Assert.assertEquals(404, response.getStatus(), 0);
+ }
+
@Test
public void updateProjectTest() {
Project project = qm.createProject("ABC", null, "1.0", null, null, null, true, false);
From d8f24999fe2f565ba675b8cba660427c771c1e09 Mon Sep 17 00:00:00 2001
From: Thomas Schauer-Koeckeis
Date: Fri, 30 Aug 2024 09:39:31 +0200
Subject: [PATCH 09/13] Added Tests for the visible API endpoint
Signed-off-by: Thomas Schauer-Koeckeis
---
.../resources/v1/TeamResourceTest.java | 88 +++++++++++++++++++
1 file changed, 88 insertions(+)
diff --git a/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java
index b1662d8d36..be052f347e 100644
--- a/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java
+++ b/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java
@@ -21,7 +21,9 @@
import alpine.common.util.UuidUtil;
import alpine.model.ApiKey;
import alpine.model.ConfigProperty;
+import alpine.model.IConfigProperty;
import alpine.model.ManagedUser;
+import alpine.model.Permission;
import alpine.model.Team;
import alpine.server.auth.JsonWebToken;
import alpine.server.filters.ApiFilter;
@@ -31,6 +33,7 @@
import org.dependencytrack.auth.Permissions;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.Project;
+import org.dependencytrack.persistence.DefaultObjectGenerator;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.server.ResourceConfig;
import org.junit.Assert;
@@ -42,6 +45,9 @@
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
+
+import java.util.ArrayList;
+import java.util.List;
import java.util.UUID;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
@@ -49,6 +55,9 @@
import static org.hamcrest.CoreMatchers.equalTo;
public class TeamResourceTest extends ResourceTest {
+ private ManagedUser testUser;
+ private String jwt;
+ private Team userNotPartof;
@ClassRule
public static JerseyTestRule jersey = new JerseyTestRule(
@@ -56,6 +65,21 @@ public class TeamResourceTest extends ResourceTest {
.register(ApiFilter.class)
.register(AuthenticationFilter.class));
+ public void getUserToken(boolean isAdmin) {
+ testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH);
+ jwt = new JsonWebToken().createToken(testUser);
+ qm.addUserToTeam(testUser, team);
+ userNotPartof = qm.createTeam("UserNotPartof", false);
+ if (isAdmin) {
+ final var generator = new DefaultObjectGenerator();
+ generator.loadDefaultPermissions();
+ List permissionsList = new ArrayList();
+ final Permission adminPermission = qm.getPermission("ACCESS_MANAGEMENT");
+ permissionsList.add(adminPermission);
+ testUser.setPermissions(permissionsList);
+ }
+ }
+
@Test
public void getTeamsTest() {
for (int i=0; i<1000; i++) {
@@ -206,6 +230,70 @@ public void deleteTeamWithAclTest() {
Assert.assertEquals(204, response.getStatus(), 0);
}
+ @Test
+ public void getVisibleAdminRequiredTeams() {
+ getUserToken(true);
+ qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
+ Response response = jersey.target(V1_TEAM + "/visible")
+ .request()
+ .header("Authorization", "Bearer " + jwt)
+ .get();
+ Assert.assertEquals(200, response.getStatus(), 0);
+ JsonObject body = parseJsonObject(response);
+ Assert.assertTrue(body.getBoolean("required"));
+ JsonArray teams = body.getJsonArray("teams");
+ Assert.assertEquals(teams.size(), 2);
+ Assert.assertEquals(teams.getFirst().asJsonObject().getString("uuid"), this.team.getUuid().toString());
+ Assert.assertEquals(teams.get(1).asJsonObject().getString("uuid"), userNotPartof.getUuid().toString());
+ }
+
+ @Test
+ public void getVisibleAdminNotRequiredTeams() {
+ getUserToken(true);
+ Response response = jersey.target(V1_TEAM + "/visible")
+ .request()
+ .header("Authorization", "Bearer " + jwt)
+ .get();
+ Assert.assertEquals(200, response.getStatus(), 0);
+ JsonObject body = parseJsonObject(response);
+ Assert.assertFalse(body.getBoolean("required"));
+ JsonArray teams = body.getJsonArray("teams");
+ Assert.assertEquals(teams.size(), 2);
+ Assert.assertEquals(teams.getFirst().asJsonObject().getString("uuid"), this.team.getUuid().toString());
+ Assert.assertEquals(teams.get(1).asJsonObject().getString("uuid"), userNotPartof.getUuid().toString());
+ }
+
+ @Test
+ public void getVisibleNotAdminRequiredTeams() {
+ getUserToken(false);
+ qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
+ Response response = jersey.target(V1_TEAM + "/visible")
+ .request()
+ .header("Authorization", "Bearer " + jwt)
+ .get();
+ Assert.assertEquals(200, response.getStatus(), 0);
+ JsonObject body = parseJsonObject(response);
+ Assert.assertTrue(body.getBoolean("required"));
+ JsonArray teams = body.getJsonArray("teams");
+ Assert.assertEquals(teams.size(), 1);
+ Assert.assertEquals(teams.getFirst().asJsonObject().getString("uuid"), this.team.getUuid().toString());
+ }
+
+ @Test
+ public void getVisibleNotAdminNotRequiredTeams() {
+ getUserToken(false);
+ Response response = jersey.target(V1_TEAM + "/visible")
+ .request()
+ .header("Authorization", "Bearer " + jwt)
+ .get();
+ Assert.assertEquals(200, response.getStatus(), 0);
+ JsonObject body = parseJsonObject(response);
+ Assert.assertFalse(body.getBoolean("required"));
+ JsonArray teams = body.getJsonArray("teams");
+ Assert.assertEquals(teams.size(), 1);
+ Assert.assertEquals(teams.getFirst().asJsonObject().getString("uuid"), this.team.getUuid().toString());
+ }
+
@Test
public void generateApiKeyTest() {
Team team = qm.createTeam("My Team", false);
From f45225ed9d03b564a330c7fc99f22e53c6c1346f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Schauer-K=C3=B6ckeis=20Thomas?=
Date: Tue, 10 Sep 2024 07:53:40 +0200
Subject: [PATCH 10/13] Fixed some things
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Thomas Schauer-Köckeis
---
.../java/org/dependencytrack/resources/v1/TeamResource.java | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/dependencytrack/resources/v1/TeamResource.java b/src/main/java/org/dependencytrack/resources/v1/TeamResource.java
index 23817c545c..fb447ac635 100644
--- a/src/main/java/org/dependencytrack/resources/v1/TeamResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/TeamResource.java
@@ -228,11 +228,10 @@ public Response deleteTeam(Team jsonTeam) {
@GET
@Path("/visible")
@Produces(MediaType.APPLICATION_JSON)
- @Operation(summary = "Returns a list of Teams what are visible", description = "Requires permission PORTFOLIO_MANAGEMENT")
+ @Operation(summary = "Returns a list of Teams that are visible", description = "")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "The Visible Teams", content = @Content(schema = @Schema(implementation = VisibleTeams.class))),
- @ApiResponse(responseCode = "401", description = "Unauthorized"),
- @ApiResponse(responseCode = "404", description = "Teams could not be found")
+ @ApiResponse(responseCode = "401", description = "Unauthorized")
})
public Response availableTeams() {
try (QueryManager qm = new QueryManager()) {
From 3822462c82a2bc876a634ff4d80bc13a70d98636 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Schauer-K=C3=B6ckeis=20Thomas?=
Date: Tue, 10 Sep 2024 07:52:20 +0200
Subject: [PATCH 11/13] Switched params for tests, so expected is the real
expected
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Thomas Schauer-Köckeis
---
.../resources/v1/TeamResourceTest.java | 20 +++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java
index be052f347e..0e463ba7df 100644
--- a/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java
+++ b/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java
@@ -242,9 +242,9 @@ public void getVisibleAdminRequiredTeams() {
JsonObject body = parseJsonObject(response);
Assert.assertTrue(body.getBoolean("required"));
JsonArray teams = body.getJsonArray("teams");
- Assert.assertEquals(teams.size(), 2);
- Assert.assertEquals(teams.getFirst().asJsonObject().getString("uuid"), this.team.getUuid().toString());
- Assert.assertEquals(teams.get(1).asJsonObject().getString("uuid"), userNotPartof.getUuid().toString());
+ Assert.assertEquals(2, teams.size());
+ Assert.assertEquals(this.team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
+ Assert.assertEquals(userNotPartof.getUuid().toString(), teams.get(1).asJsonObject().getString("uuid"));
}
@Test
@@ -258,9 +258,9 @@ public void getVisibleAdminNotRequiredTeams() {
JsonObject body = parseJsonObject(response);
Assert.assertFalse(body.getBoolean("required"));
JsonArray teams = body.getJsonArray("teams");
- Assert.assertEquals(teams.size(), 2);
- Assert.assertEquals(teams.getFirst().asJsonObject().getString("uuid"), this.team.getUuid().toString());
- Assert.assertEquals(teams.get(1).asJsonObject().getString("uuid"), userNotPartof.getUuid().toString());
+ Assert.assertEquals(2, teams.size());
+ Assert.assertEquals(this.team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
+ Assert.assertEquals(userNotPartof.getUuid().toString(), teams.get(1).asJsonObject().getString("uuid"));
}
@Test
@@ -275,8 +275,8 @@ public void getVisibleNotAdminRequiredTeams() {
JsonObject body = parseJsonObject(response);
Assert.assertTrue(body.getBoolean("required"));
JsonArray teams = body.getJsonArray("teams");
- Assert.assertEquals(teams.size(), 1);
- Assert.assertEquals(teams.getFirst().asJsonObject().getString("uuid"), this.team.getUuid().toString());
+ Assert.assertEquals(1, teams.size());
+ Assert.assertEquals(this.team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
}
@Test
@@ -290,8 +290,8 @@ public void getVisibleNotAdminNotRequiredTeams() {
JsonObject body = parseJsonObject(response);
Assert.assertFalse(body.getBoolean("required"));
JsonArray teams = body.getJsonArray("teams");
- Assert.assertEquals(teams.size(), 1);
- Assert.assertEquals(teams.getFirst().asJsonObject().getString("uuid"), this.team.getUuid().toString());
+ Assert.assertEquals(1, teams.size());
+ Assert.assertEquals(this.team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
}
@Test
From 0cb20df002ab4d119900dd2158d4a0045a4ef698 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20Schauer-K=C3=B6ckeis?=
Date: Fri, 20 Sep 2024 13:09:00 +0200
Subject: [PATCH 12/13] Implemented things from the review
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Thomas Schauer-Köckeis
---
.../model/ConfigPropertyConstants.java | 2 +-
.../org/dependencytrack/model/Project.java | 1 +
.../resources/v1/ProjectResource.java | 61 ++++++++------
.../resources/v1/TeamResource.java | 25 +++---
.../resources/v1/vo/VisibleTeams.java | 27 ------
.../resources/v1/ProjectResourceTest.java | 83 +++++++++++--------
.../resources/v1/TeamResourceTest.java | 56 ++++++-------
7 files changed, 124 insertions(+), 131 deletions(-)
delete mode 100644 src/main/java/org/dependencytrack/resources/v1/vo/VisibleTeams.java
diff --git a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java
index 7515206c91..fdc9ecb4f4 100644
--- a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java
+++ b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java
@@ -95,7 +95,7 @@ public enum ConfigPropertyConstants {
KENNA_SYNC_CADENCE("integrations", "kenna.sync.cadence", "60", PropertyType.INTEGER, "The cadence (in minutes) to upload to Kenna Security"),
KENNA_TOKEN("integrations", "kenna.token", null, PropertyType.ENCRYPTEDSTRING, "The token to use when authenticating to Kenna Security"),
KENNA_CONNECTOR_ID("integrations", "kenna.connector.id", null, PropertyType.STRING, "The Kenna Security connector identifier to upload to"),
- ACCESS_MANAGEMENT_ACL_ENABLED("access-management", "acl.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable access control to projects in the portfolio"),
+ ACCESS_MANAGEMENT_ACL_ENABLED("access-management", "acl.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable access control to projects in the portfolio", true),
NOTIFICATION_TEMPLATE_BASE_DIR("notification", "template.baseDir", SystemUtils.getEnvironmentVariable("DEFAULT_TEMPLATES_OVERRIDE_BASE_DIRECTORY", System.getProperty("user.home")), PropertyType.STRING, "The base directory to use when searching for notification templates"),
NOTIFICATION_TEMPLATE_DEFAULT_OVERRIDE_ENABLED("notification", "template.default.override.enabled", SystemUtils.getEnvironmentVariable("DEFAULT_TEMPLATES_OVERRIDE_ENABLED", "false"), PropertyType.BOOLEAN, "Flag to enable/disable override of default notification templates"),
TASK_SCHEDULER_LDAP_SYNC_CADENCE("task-scheduler", "ldap.sync.cadence", "6", PropertyType.INTEGER, "Sync cadence (in hours) for LDAP"),
diff --git a/src/main/java/org/dependencytrack/model/Project.java b/src/main/java/org/dependencytrack/model/Project.java
index df8c5873b4..5d10072ee4 100644
--- a/src/main/java/org/dependencytrack/model/Project.java
+++ b/src/main/java/org/dependencytrack/model/Project.java
@@ -273,6 +273,7 @@ public enum FetchGroup {
@Join(column = "PROJECT_ID")
@Element(column = "TEAM_ID")
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
+ @JsonInclude(value = JsonInclude.Include.NON_EMPTY)
private List accessTeams;
@Persistent(defaultFetchGroup = "true")
diff --git a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
index b85446aee7..b209b331e4 100644
--- a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java
@@ -284,11 +284,13 @@ public Response getProjectsByClassifier(
content = @Content(schema = @Schema(implementation = Project.class))
),
@ApiResponse(responseCode = "401", description = "Unauthorized"),
+ @ApiResponse(responseCode = "403", description = "You don't have the permission to assign this team to a project."),
@ApiResponse(responseCode = "409", description = """
- An inactive Parent cannot be selected as parent, or
- A project with the specified name already exists
"""),
+ @ApiResponse(responseCode = "422", description = "You need to specify at least one team to which the project should belong"),
})
@PermissionRequired(Permissions.Constants.PORTFOLIO_MANAGEMENT)
public Response createProject(Project jsonProject) {
@@ -315,33 +317,40 @@ public Response createProject(Project jsonProject) {
Project parent = qm.getObjectByUuid(Project.class, jsonProject.getParent().getUuid());
jsonProject.setParent(parent);
}
- final List choosenTeams = jsonProject.getAccessTeams();
- Principal principal = getPrincipal();
- List userTeams = new ArrayList();
- if (principal instanceof final UserPrincipal userPrincipal) {
- userTeams = userPrincipal.getTeams();
- } else if (principal instanceof final ApiKey apiKey) {
- userTeams = apiKey.getTeams();
- }
- boolean required = qm.isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED);
- boolean isAdmin = qm.hasAccessManagementPermission(principal);
- if (required && choosenTeams.isEmpty()) {
- return Response.status(422)
- .entity("You need to specify at least one team to which the project should belong").build();
- }
- List visibleTeams = isAdmin ? qm.getTeams() : userTeams;
- List visibleUuids = visibleTeams.isEmpty() ? new ArrayList(): visibleTeams.stream().map(Team::getUuid).toList();
- jsonProject.setAccessTeams(new ArrayList());
- for (Team choosenTeam : choosenTeams) {
- if (!visibleUuids.contains(choosenTeam.getUuid())) {
- return isAdmin ? Response.status(404).entity("This team does not exist!").build()
- : Response.status(403)
- .entity("You don't have the permission to assign this team to a project.").build();
+ if (!qm.doesProjectExist(StringUtils.trimToNull(jsonProject.getName()),
+ StringUtils.trimToNull(jsonProject.getVersion()))) {
+ final List chosenTeams = jsonProject.getAccessTeams() == null ? new ArrayList()
+ : jsonProject.getAccessTeams();
+ boolean required = qm.isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED);
+ if (required && chosenTeams.isEmpty()) {
+ return Response.status(422)
+ .entity("You need to specify at least one team to which the project should belong").build();
}
- Team ormTeam = qm.getObjectByUuid(Team.class, choosenTeam.getUuid());
- jsonProject.addAccessTeam(ormTeam);
- }
- if (!qm.doesProjectExist(StringUtils.trimToNull(jsonProject.getName()), StringUtils.trimToNull(jsonProject.getVersion()))) {
+ Principal principal = getPrincipal();
+ if (!chosenTeams.isEmpty()) {
+ List userTeams = new ArrayList();
+ if (principal instanceof final UserPrincipal userPrincipal) {
+ userTeams = userPrincipal.getTeams();
+ } else if (principal instanceof final ApiKey apiKey) {
+ userTeams = apiKey.getTeams();
+ }
+ boolean isAdmin = qm.hasAccessManagementPermission(principal);
+ List visibleTeams = isAdmin ? qm.getTeams() : userTeams;
+ List visibleUuids = visibleTeams.isEmpty() ? new ArrayList()
+ : visibleTeams.stream().map(Team::getUuid).toList();
+ jsonProject.setAccessTeams(new ArrayList());
+ for (Team choosenTeam : chosenTeams) {
+ if (!visibleUuids.contains(choosenTeam.getUuid())) {
+ return isAdmin ? Response.status(404).entity("This team does not exist!").build()
+ : Response.status(403)
+ .entity("You don't have the permission to assign this team to a project.")
+ .build();
+ }
+ Team ormTeam = qm.getObjectByUuid(Team.class, choosenTeam.getUuid());
+ jsonProject.addAccessTeam(ormTeam);
+ }
+ }
+
final Project project;
try {
project = qm.createProject(jsonProject, jsonProject.getTags(), true);
diff --git a/src/main/java/org/dependencytrack/resources/v1/TeamResource.java b/src/main/java/org/dependencytrack/resources/v1/TeamResource.java
index fb447ac635..469a55a258 100644
--- a/src/main/java/org/dependencytrack/resources/v1/TeamResource.java
+++ b/src/main/java/org/dependencytrack/resources/v1/TeamResource.java
@@ -37,11 +37,9 @@
import io.swagger.v3.oas.annotations.security.SecurityRequirements;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.dependencytrack.auth.Permissions;
-import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.validation.ValidUuid;
import org.dependencytrack.persistence.QueryManager;
import org.dependencytrack.resources.v1.vo.TeamSelfResponse;
-import org.dependencytrack.resources.v1.vo.VisibleTeams;
import org.owasp.security.logging.SecurityMarkers;
import jakarta.validation.Validator;
@@ -230,24 +228,25 @@ public Response deleteTeam(Team jsonTeam) {
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Returns a list of Teams that are visible", description = "")
@ApiResponses(value = {
- @ApiResponse(responseCode = "200", description = "The Visible Teams", content = @Content(schema = @Schema(implementation = VisibleTeams.class))),
+ @ApiResponse(responseCode = "200", description = "The Visible Teams", content = @Content(array = @ArraySchema(schema = @Schema(implementation = Team.class)))),
@ApiResponse(responseCode = "401", description = "Unauthorized")
})
public Response availableTeams() {
try (QueryManager qm = new QueryManager()) {
Principal user = getPrincipal();
- List userTeams = new ArrayList();
- if (user instanceof final UserPrincipal userPrincipal) {
- userTeams = userPrincipal.getTeams();
- } else if (user instanceof final ApiKey apiKey) {
- userTeams = apiKey.getTeams();
- }
- boolean required = qm.isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED);
boolean isAllTeams = qm.hasAccessManagementPermission(user);
- List teams = isAllTeams ? qm.getTeams() : userTeams;
- VisibleTeams response = new VisibleTeams(required, teams);
+ List teams = new ArrayList();
+ if (isAllTeams) {
+ teams = qm.getTeams();
+ } else {
+ if (user instanceof final UserPrincipal userPrincipal) {
+ teams = userPrincipal.getTeams();
+ } else if (user instanceof final ApiKey apiKey) {
+ teams = apiKey.getTeams();
+ }
+ }
- return Response.ok(response).build();
+ return Response.ok(teams).build();
}
}
diff --git a/src/main/java/org/dependencytrack/resources/v1/vo/VisibleTeams.java b/src/main/java/org/dependencytrack/resources/v1/vo/VisibleTeams.java
deleted file mode 100644
index ba9f186262..0000000000
--- a/src/main/java/org/dependencytrack/resources/v1/vo/VisibleTeams.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.resources.v1.vo;
-
-import java.util.List;
-
-import alpine.model.Team;
-
-public record VisibleTeams(boolean required,
- List teams) {
-}
diff --git a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
index 1418ab36e1..039f4ec4fc 100644
--- a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
+++ b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
@@ -20,9 +20,8 @@
import alpine.common.util.UuidUtil;
import alpine.event.framework.EventService;
-import alpine.model.IConfigProperty;
-import alpine.model.ManagedUser;
import alpine.model.IConfigProperty.PropertyType;
+import alpine.model.ManagedUser;
import alpine.model.Team;
import alpine.model.Permission;
import alpine.server.auth.JsonWebToken;
@@ -97,7 +96,7 @@ public void after() throws Exception {
super.after();
}
- public void getUserToken(boolean isAdmin) {
+ public void setUpUser(boolean isAdmin, boolean isRequired) {
testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH);
jwt = new JsonWebToken().createToken(testUser);
qm.addUserToTeam(testUser, team);
@@ -112,6 +111,14 @@ public void getUserToken(boolean isAdmin) {
permissionsList.add(adminPermission);
testUser.setPermissions(permissionsList);
}
+ if (isRequired) {
+ qm.createConfigProperty(
+ ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(),
+ ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(),
+ "true",
+ ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(),
+ null);
+ }
}
@Test
@@ -309,7 +316,6 @@ public void getProjectByUuidTest() {
.withMatcher("childUuid", equalTo(childProject.getUuid().toString()))
.isEqualTo("""
{
- "accessTeams": [],
"name": "acme-app",
"version": "1.0.0",
"uuid": "${json-unit.matches:projectUuid}",
@@ -439,7 +445,6 @@ public void getProjectByUnknownTagTest() {
@Test
public void createProjectTest(){
Project project = new Project();
- project.setAccessTeams(new ArrayList());
project.setName("Acme Example");
project.setVersion("1.0");
project.setDescription("Test project");
@@ -460,7 +465,6 @@ public void createProjectTest(){
@Test
public void createProjectDuplicateTest() {
Project project = new Project();
- project.setAccessTeams(new ArrayList());
project.setName("Acme Example");
project.setVersion("1.0");
Response response = jersey.target(V1_PROJECT)
@@ -480,7 +484,6 @@ public void createProjectDuplicateTest() {
@Test
public void createProjectWithoutVersionDuplicateTest() {
Project project = new Project();
- project.setAccessTeams(new ArrayList());
project.setName("Acme Example");
Response response = jersey.target(V1_PROJECT)
.request()
@@ -509,30 +512,31 @@ public void createProjectEmptyTest() {
@Test
public void createProjectWithExistingTeamRequiredTest() {
- getUserToken(false);
+ setUpUser(false, true);
Team AllowedTeam = qm.createTeam("AllowedTeam", false);
Project project = new Project();
project.setName("ProjectWithExistingTeamRequired");
qm.addUserToTeam(testUser, AllowedTeam);
- qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
final JsonObject jsonTeam = Json.createObjectBuilder().add("uuid", AllowedTeam.getUuid().toString()).build();
final JsonObjectBuilder requestBodyBuilder = Json.createObjectBuilder()
- .add("name", project.getName()).add("classifier", "CONTAINER").addNull("parent").add("active", true)
+ .add("name", project.getName()).add("classifier", "CONTAINER").addNull("parent").add("active", true).add("tags", Json.createArrayBuilder())
.add("accessTeams", Json.createArrayBuilder().add(jsonTeam).build());
Response response = jersey.target(V1_PROJECT)
.request()
.header("Authorization", "Bearer " + jwt)
.put(Entity.json(requestBodyBuilder.build().toString()));
- Assert.assertEquals(201, response.getStatus(), 0);
+ Assert.assertEquals(201, response.getStatus());
+ JsonObject returnedProject = parseJsonObject(response);
+ JsonArray teams = returnedProject.getJsonArray("accessTeams");
+ Assert.assertEquals(teams.size(), 1);
+ Assert.assertEquals(AllowedTeam.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
}
@Test
public void createProjectWithoutExistingTeamRequiredTest() {
- getUserToken(false);
+ setUpUser(false, true);
Project project = new Project();
project.setName("ProjectWithoutExistingTeamRequired");
- project.setAccessTeams(new ArrayList());
- qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
Response response = jersey.target(V1_PROJECT)
.request()
.header("Authorization", "Bearer " + jwt)
@@ -542,54 +546,55 @@ public void createProjectWithoutExistingTeamRequiredTest() {
@Test
public void createProjectWithNotAllowedExistingTeamTest() {
- getUserToken(false);
+ setUpUser(false, true);
Team notAllowedTeam = qm.createTeam("NotAllowedTeam", false);
Project project = new Project();
project.setName("ProjectWithNotAllowedExistingTeam");
project.addAccessTeam(notAllowedTeam);
- qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
Response response = jersey.target(V1_PROJECT)
.request()
.header("Authorization", "Bearer " + jwt)
.put(Entity.entity(project, MediaType.APPLICATION_JSON));
- Assert.assertEquals(403, response.getStatus(), 0);
+ Assert.assertEquals(403, response.getStatus());
}
@Test
public void createProjectWithNotAllowedExistingTeamAdminTest() {
- getUserToken(true);
+ setUpUser(true, true);
Team notAllowedTeam = qm.createTeam("NotAllowedTeam", false);
Project project = new Project();
project.setName("ProjectWithNotAllowedExistingTeam");
project.addAccessTeam(notAllowedTeam);
- qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
Response response = jersey.target(V1_PROJECT)
.request()
.header("Authorization", "Bearer " + jwt)
.put(Entity.entity(project, MediaType.APPLICATION_JSON));
- Assert.assertEquals(201, response.getStatus(), 0);
+ Assert.assertEquals(201, response.getStatus());
+ JsonObject returnedProject = parseJsonObject(response);
+ JsonArray teams = returnedProject.getJsonArray("accessTeams");
+ Assert.assertEquals(teams.size(), 1);
+ Assert.assertEquals(notAllowedTeam.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
}
@Test
public void createProjectWithNotExistingTeamNoAdminTest() {
- getUserToken(false);
+ setUpUser(false, true);
Team notAllowedTeam = new Team();
notAllowedTeam.setUuid(new UUID(1, 1));
notAllowedTeam.setName("NotAllowedTeam");
Project project = new Project();
project.addAccessTeam(notAllowedTeam);
project.setName("ProjectWithNotAllowedExistingTeam");
- qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
Response response = jersey.target(V1_PROJECT)
.request()
.header("Authorization", "Bearer " + jwt)
.put(Entity.entity(project, MediaType.APPLICATION_JSON));
- Assert.assertEquals(403, response.getStatus(), 0);
+ Assert.assertEquals(403, response.getStatus());
}
@Test
public void createProjectWithNotExistingTeamTest() {
- getUserToken(true);
+ setUpUser(true, false);
Team notAllowedTeam = new Team();
notAllowedTeam.setUuid(new UUID(1, 1));
notAllowedTeam.setName("NotAllowedTeam");
@@ -600,7 +605,26 @@ public void createProjectWithNotExistingTeamTest() {
.request()
.header("Authorization", "Bearer " + jwt)
.put(Entity.entity(project, MediaType.APPLICATION_JSON));
- Assert.assertEquals(404, response.getStatus(), 0);
+ Assert.assertEquals(404, response.getStatus());
+ }
+
+ @Test
+ public void createProjectWithApiKeyTest() {
+ Project project = new Project();
+ project.setName("ProjectWithNotExistingTeam");
+ final JsonObject jsonTeam = Json.createObjectBuilder().add("uuid", team.getUuid().toString()).build();
+ final JsonObjectBuilder requestBodyBuilder = Json.createObjectBuilder()
+ .add("name", project.getName()).add("classifier", "CONTAINER").addNull("parent").add("active", true).add("tags", Json.createArrayBuilder())
+ .add("accessTeams", Json.createArrayBuilder().add(jsonTeam).build());
+ Response response = jersey.target(V1_PROJECT)
+ .request()
+ .header(X_API_KEY, apiKey)
+ .put(Entity.json(requestBodyBuilder.build().toString()));
+ Assert.assertEquals(201, response.getStatus());
+ JsonObject returnedProject = parseJsonObject(response);
+ JsonArray teams = returnedProject.getJsonArray("accessTeams");
+ Assert.assertEquals(teams.size(), 1);
+ Assert.assertEquals(team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
}
@Test
@@ -833,7 +857,6 @@ public void patchProjectSuccessfullyPatchedTest() {
.withMatcher("projectUuid", equalTo(p1.getUuid().toString()))
.isEqualTo("""
{
- "accessTeams": [],
"publisher": "new publisher",
"manufacturer": {
"name": "manufacturerName",
@@ -930,7 +953,6 @@ public void patchProjectParentTest() {
.withMatcher("parentProjectUuid", CoreMatchers.equalTo(newParent.getUuid().toString()))
.isEqualTo("""
{
- "accessTeams": [],
"name": "DEF",
"version": "2.0",
"uuid": "${json-unit.matches:projectUuid}",
@@ -1289,7 +1311,6 @@ public void issue3883RegressionTest() {
.header(X_API_KEY, apiKey)
.put(Entity.json("""
{
- "accessTeams": [],
"name": "acme-app-parent",
"version": "1.0.0"
}
@@ -1302,7 +1323,6 @@ public void issue3883RegressionTest() {
.header(X_API_KEY, apiKey)
.put(Entity.json("""
{
- "accessTeams": [],
"name": "acme-app",
"version": "1.0.0",
"parent": {
@@ -1320,7 +1340,6 @@ public void issue3883RegressionTest() {
assertThat(response.getStatus()).isEqualTo(200);
assertThatJson(getPlainTextBody(response)).isEqualTo("""
{
- "accessTeams": [],
"name": "acme-app-parent",
"version": "1.0.0",
"classifier": "APPLICATION",
@@ -1354,7 +1373,6 @@ public void issue3883RegressionTest() {
assertThat(response.getStatus()).isEqualTo(200);
assertThatJson(getPlainTextBody(response)).isEqualTo("""
{
- "accessTeams": [],
"name": "acme-app",
"version": "1.0.0",
"classifier": "APPLICATION",
@@ -1395,8 +1413,7 @@ public void issue4048RegressionTest() {
final JsonObjectBuilder requestBodyBuilder = Json.createObjectBuilder()
.add("name", "project-%d-%d".formatted(i, j))
- .add("version", "%d.%d".formatted(i, j))
- .add("accessTeams",Json.createArrayBuilder().build());
+ .add("version", "%d.%d".formatted(i, j));
if (parentUuid != null) {
requestBodyBuilder.add("parent", Json.createObjectBuilder()
.add("uuid", parentUuid.toString()));
diff --git a/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java
index 0e463ba7df..2d55542cb2 100644
--- a/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java
+++ b/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java
@@ -21,7 +21,6 @@
import alpine.common.util.UuidUtil;
import alpine.model.ApiKey;
import alpine.model.ConfigProperty;
-import alpine.model.IConfigProperty;
import alpine.model.ManagedUser;
import alpine.model.Permission;
import alpine.model.Team;
@@ -55,7 +54,6 @@
import static org.hamcrest.CoreMatchers.equalTo;
public class TeamResourceTest extends ResourceTest {
- private ManagedUser testUser;
private String jwt;
private Team userNotPartof;
@@ -65,8 +63,8 @@ public class TeamResourceTest extends ResourceTest {
.register(ApiFilter.class)
.register(AuthenticationFilter.class));
- public void getUserToken(boolean isAdmin) {
- testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH);
+ public void setUpUser(boolean isAdmin) {
+ ManagedUser testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH);
jwt = new JsonWebToken().createToken(testUser);
qm.addUserToTeam(testUser, team);
userNotPartof = qm.createTeam("UserNotPartof", false);
@@ -231,67 +229,63 @@ public void deleteTeamWithAclTest() {
}
@Test
- public void getVisibleAdminRequiredTeams() {
- getUserToken(true);
- qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
+ public void getVisibleAdminTeams() {
+ setUpUser(true);
Response response = jersey.target(V1_TEAM + "/visible")
.request()
.header("Authorization", "Bearer " + jwt)
.get();
Assert.assertEquals(200, response.getStatus(), 0);
- JsonObject body = parseJsonObject(response);
- Assert.assertTrue(body.getBoolean("required"));
- JsonArray teams = body.getJsonArray("teams");
+ JsonArray teams = parseJsonArray(response);
Assert.assertEquals(2, teams.size());
Assert.assertEquals(this.team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
Assert.assertEquals(userNotPartof.getUuid().toString(), teams.get(1).asJsonObject().getString("uuid"));
}
@Test
- public void getVisibleAdminNotRequiredTeams() {
- getUserToken(true);
+ public void getVisibleNotAdminTeams() {
+ setUpUser(false);
Response response = jersey.target(V1_TEAM + "/visible")
.request()
.header("Authorization", "Bearer " + jwt)
.get();
Assert.assertEquals(200, response.getStatus(), 0);
- JsonObject body = parseJsonObject(response);
- Assert.assertFalse(body.getBoolean("required"));
- JsonArray teams = body.getJsonArray("teams");
- Assert.assertEquals(2, teams.size());
+ JsonArray teams = parseJsonArray(response);
+ Assert.assertEquals(1, teams.size());
Assert.assertEquals(this.team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
- Assert.assertEquals(userNotPartof.getUuid().toString(), teams.get(1).asJsonObject().getString("uuid"));
}
@Test
- public void getVisibleNotAdminRequiredTeams() {
- getUserToken(false);
- qm.createConfigProperty("access-management", "acl.enabled", "true", IConfigProperty.PropertyType.BOOLEAN, "");
+ public void getVisibleNotAdminApiKeyTeams() {
Response response = jersey.target(V1_TEAM + "/visible")
.request()
- .header("Authorization", "Bearer " + jwt)
+ .header(X_API_KEY, apiKey)
.get();
Assert.assertEquals(200, response.getStatus(), 0);
- JsonObject body = parseJsonObject(response);
- Assert.assertTrue(body.getBoolean("required"));
- JsonArray teams = body.getJsonArray("teams");
+ JsonArray teams = parseJsonArray(response);
Assert.assertEquals(1, teams.size());
Assert.assertEquals(this.team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
}
@Test
- public void getVisibleNotAdminNotRequiredTeams() {
- getUserToken(false);
+ public void getVisibleAdminApiKeyTeams() {
+ userNotPartof = qm.createTeam("UserNotPartof", false);
+ final var generator = new DefaultObjectGenerator();
+ generator.loadDefaultPermissions();
+ List permissionsList = new ArrayList();
+ final Permission adminPermission = qm.getPermission("ACCESS_MANAGEMENT");
+ permissionsList.add(adminPermission);
+ this.team.setPermissions(permissionsList);
+
Response response = jersey.target(V1_TEAM + "/visible")
.request()
- .header("Authorization", "Bearer " + jwt)
+ .header(X_API_KEY, apiKey)
.get();
Assert.assertEquals(200, response.getStatus(), 0);
- JsonObject body = parseJsonObject(response);
- Assert.assertFalse(body.getBoolean("required"));
- JsonArray teams = body.getJsonArray("teams");
- Assert.assertEquals(1, teams.size());
+ JsonArray teams = parseJsonArray(response);
+ Assert.assertEquals(2, teams.size());
Assert.assertEquals(this.team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
+ Assert.assertEquals(userNotPartof.getUuid().toString(), teams.get(1).asJsonObject().getString("uuid"));
}
@Test
From 11b005d7d98aa46159e5cb0b850d471bc4c0f1aa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20Schauer-K=C3=B6ckeis?=
Date: Tue, 24 Sep 2024 16:54:03 +0200
Subject: [PATCH 13/13] Excluded accessTeams for json encoding and updated
tests
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Thomas Schauer-Köckeis
---
.../org/dependencytrack/model/Project.java | 4 +-
.../resources/v1/ProjectResourceTest.java | 70 ++++++-------------
2 files changed, 25 insertions(+), 49 deletions(-)
diff --git a/src/main/java/org/dependencytrack/model/Project.java b/src/main/java/org/dependencytrack/model/Project.java
index 5d10072ee4..01148e4e5d 100644
--- a/src/main/java/org/dependencytrack/model/Project.java
+++ b/src/main/java/org/dependencytrack/model/Project.java
@@ -24,6 +24,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonIncludeProperties;
+import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
@@ -273,7 +274,6 @@ public enum FetchGroup {
@Join(column = "PROJECT_ID")
@Element(column = "TEAM_ID")
@Order(extensions = @Extension(vendorName = "datanucleus", key = "list-ordering", value = "name ASC"))
- @JsonInclude(value = JsonInclude.Include.NON_EMPTY)
private List accessTeams;
@Persistent(defaultFetchGroup = "true")
@@ -537,10 +537,12 @@ public void setVersions(List versions) {
this.versions = versions;
}
+ @JsonIgnore
public List getAccessTeams() {
return accessTeams;
}
+ @JsonSetter
public void setAccessTeams(List accessTeams) {
this.accessTeams = accessTeams;
}
diff --git a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
index 039f4ec4fc..76b3fe5ac7 100644
--- a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
+++ b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java
@@ -96,7 +96,7 @@ public void after() throws Exception {
super.after();
}
- public void setUpUser(boolean isAdmin, boolean isRequired) {
+ public JsonObjectBuilder setUpEnvironment(boolean isAdmin, boolean isRequired, String name, Team team1) {
testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH);
jwt = new JsonWebToken().createToken(testUser);
qm.addUserToTeam(testUser, team);
@@ -119,6 +119,13 @@ public void setUpUser(boolean isAdmin, boolean isRequired) {
ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(),
null);
}
+ final JsonObjectBuilder jsonProject = Json.createObjectBuilder()
+ .add("name", name).add("classifier", "CONTAINER").addNull("parent").add("active", true).add("tags", Json.createArrayBuilder());
+ if (team1 != null) {
+ final JsonObject jsonTeam = Json.createObjectBuilder().add("uuid", team1.getUuid().toString()).build();
+ jsonProject.add("accessTeams", Json.createArrayBuilder().add(jsonTeam).build());
+ }
+ return jsonProject;
}
@Test
@@ -512,119 +519,86 @@ public void createProjectEmptyTest() {
@Test
public void createProjectWithExistingTeamRequiredTest() {
- setUpUser(false, true);
Team AllowedTeam = qm.createTeam("AllowedTeam", false);
- Project project = new Project();
- project.setName("ProjectWithExistingTeamRequired");
+ final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(false, true, "ProjectWithExistingTeamRequired", AllowedTeam);
qm.addUserToTeam(testUser, AllowedTeam);
- final JsonObject jsonTeam = Json.createObjectBuilder().add("uuid", AllowedTeam.getUuid().toString()).build();
- final JsonObjectBuilder requestBodyBuilder = Json.createObjectBuilder()
- .add("name", project.getName()).add("classifier", "CONTAINER").addNull("parent").add("active", true).add("tags", Json.createArrayBuilder())
- .add("accessTeams", Json.createArrayBuilder().add(jsonTeam).build());
Response response = jersey.target(V1_PROJECT)
.request()
.header("Authorization", "Bearer " + jwt)
.put(Entity.json(requestBodyBuilder.build().toString()));
Assert.assertEquals(201, response.getStatus());
JsonObject returnedProject = parseJsonObject(response);
- JsonArray teams = returnedProject.getJsonArray("accessTeams");
- Assert.assertEquals(teams.size(), 1);
- Assert.assertEquals(AllowedTeam.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
}
@Test
public void createProjectWithoutExistingTeamRequiredTest() {
- setUpUser(false, true);
- Project project = new Project();
- project.setName("ProjectWithoutExistingTeamRequired");
+ final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(false, true, "ProjectWithoutExistingTeamRequired", null);
Response response = jersey.target(V1_PROJECT)
.request()
.header("Authorization", "Bearer " + jwt)
- .put(Entity.entity(project, MediaType.APPLICATION_JSON));
+ .put(Entity.json(requestBodyBuilder.build().toString()));
Assert.assertEquals(422, response.getStatus(), 0);
}
@Test
public void createProjectWithNotAllowedExistingTeamTest() {
- setUpUser(false, true);
Team notAllowedTeam = qm.createTeam("NotAllowedTeam", false);
- Project project = new Project();
- project.setName("ProjectWithNotAllowedExistingTeam");
- project.addAccessTeam(notAllowedTeam);
+ final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(false, true, "ProjectWithNotAllowedExistingTeam", notAllowedTeam);
Response response = jersey.target(V1_PROJECT)
.request()
.header("Authorization", "Bearer " + jwt)
- .put(Entity.entity(project, MediaType.APPLICATION_JSON));
+ .put(Entity.json(requestBodyBuilder.build().toString()));
Assert.assertEquals(403, response.getStatus());
}
@Test
public void createProjectWithNotAllowedExistingTeamAdminTest() {
- setUpUser(true, true);
- Team notAllowedTeam = qm.createTeam("NotAllowedTeam", false);
- Project project = new Project();
- project.setName("ProjectWithNotAllowedExistingTeam");
- project.addAccessTeam(notAllowedTeam);
+ Team AllowedTeam = qm.createTeam("NotAllowedTeam", false);
+ final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(false, true, "ProjectWithNotAllowedExistingTeam", AllowedTeam);
+ qm.addUserToTeam(testUser, AllowedTeam);
Response response = jersey.target(V1_PROJECT)
.request()
.header("Authorization", "Bearer " + jwt)
- .put(Entity.entity(project, MediaType.APPLICATION_JSON));
+ .put(Entity.json(requestBodyBuilder.build().toString()));
Assert.assertEquals(201, response.getStatus());
JsonObject returnedProject = parseJsonObject(response);
- JsonArray teams = returnedProject.getJsonArray("accessTeams");
- Assert.assertEquals(teams.size(), 1);
- Assert.assertEquals(notAllowedTeam.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
}
@Test
public void createProjectWithNotExistingTeamNoAdminTest() {
- setUpUser(false, true);
Team notAllowedTeam = new Team();
notAllowedTeam.setUuid(new UUID(1, 1));
notAllowedTeam.setName("NotAllowedTeam");
- Project project = new Project();
- project.addAccessTeam(notAllowedTeam);
- project.setName("ProjectWithNotAllowedExistingTeam");
+ final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(false, true, "ProjectWithNotAllowedExistingTeam", notAllowedTeam);
Response response = jersey.target(V1_PROJECT)
.request()
.header("Authorization", "Bearer " + jwt)
- .put(Entity.entity(project, MediaType.APPLICATION_JSON));
+ .put(Entity.json(requestBodyBuilder.build().toString()));
Assert.assertEquals(403, response.getStatus());
}
@Test
public void createProjectWithNotExistingTeamTest() {
- setUpUser(true, false);
Team notAllowedTeam = new Team();
notAllowedTeam.setUuid(new UUID(1, 1));
notAllowedTeam.setName("NotAllowedTeam");
- Project project = new Project();
- project.addAccessTeam(notAllowedTeam);
- project.setName("ProjectWithNotExistingTeam");
+ final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(true, true, "ProjectWithNotAllowedExistingTeam", notAllowedTeam);
Response response = jersey.target(V1_PROJECT)
.request()
.header("Authorization", "Bearer " + jwt)
- .put(Entity.entity(project, MediaType.APPLICATION_JSON));
+ .put(Entity.json(requestBodyBuilder.build().toString()));
Assert.assertEquals(404, response.getStatus());
}
@Test
public void createProjectWithApiKeyTest() {
- Project project = new Project();
- project.setName("ProjectWithNotExistingTeam");
- final JsonObject jsonTeam = Json.createObjectBuilder().add("uuid", team.getUuid().toString()).build();
- final JsonObjectBuilder requestBodyBuilder = Json.createObjectBuilder()
- .add("name", project.getName()).add("classifier", "CONTAINER").addNull("parent").add("active", true).add("tags", Json.createArrayBuilder())
- .add("accessTeams", Json.createArrayBuilder().add(jsonTeam).build());
+ final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(false, true, "ProjectWithNotAllowedExistingTeam", team);
Response response = jersey.target(V1_PROJECT)
.request()
.header(X_API_KEY, apiKey)
.put(Entity.json(requestBodyBuilder.build().toString()));
Assert.assertEquals(201, response.getStatus());
JsonObject returnedProject = parseJsonObject(response);
- JsonArray teams = returnedProject.getJsonArray("accessTeams");
- Assert.assertEquals(teams.size(), 1);
- Assert.assertEquals(team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid"));
}
@Test