From 6f111199a0f984faa9d5dd143ac0908f3116ca27 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Mon, 30 Sep 2024 13:40:13 +0100 Subject: [PATCH] Visible Endpoint returns only Visible Teams(name, uuid) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Thomas Schauer-Köckeis <75749982+Gepardgame@users.noreply.github.com> --- .../resources/v1/TeamResource.java | 36 +++++++++++ .../resources/v1/vo/VisibleTeams.java | 24 ++++++++ .../resources/v1/TeamResourceTest.java | 60 +++++++++++++++++-- 3 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/dependencytrack/resources/v1/vo/VisibleTeams.java diff --git a/src/main/java/org/dependencytrack/resources/v1/TeamResource.java b/src/main/java/org/dependencytrack/resources/v1/TeamResource.java index 4356046a7..50b1061e9 100644 --- a/src/main/java/org/dependencytrack/resources/v1/TeamResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/TeamResource.java @@ -22,6 +22,7 @@ import alpine.common.logging.Logger; import alpine.model.ApiKey; 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; @@ -50,8 +51,11 @@ 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 java.security.Principal; +import java.util.ArrayList; import java.util.List; import static org.datanucleus.PropertyNames.PROPERTY_RETAIN_VALUES; @@ -220,6 +224,38 @@ public Response deleteTeam(Team jsonTeam) { } } + @GET + @Path("/visible") + @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(array = @ArraySchema(schema = @Schema(implementation = VisibleTeams.class)))), + @ApiResponse(responseCode = "401", description = "Unauthorized") + }) + @PermissionRequired({Permissions.Constants.ACCESS_MANAGEMENT, Permissions.Constants.ACCESS_MANAGEMENT_READ}) + public Response availableTeams() { + try (QueryManager qm = new QueryManager()) { + Principal user = getPrincipal(); + boolean isAllTeams = qm.hasAccessManagementPermission(user); + 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(); + } + } + + List response = new ArrayList<>(); + for (Team team : teams) { + response.add(new VisibleTeams(team.getName(), team.getUuid())); + } + return Response.ok(response).build(); + } + } + @PUT @Path("/{uuid}/key") @Produces(MediaType.APPLICATION_JSON) 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 000000000..465bde00c --- /dev/null +++ b/src/main/java/org/dependencytrack/resources/v1/vo/VisibleTeams.java @@ -0,0 +1,24 @@ +/* + * 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.UUID; + +public record VisibleTeams(String name, UUID uuid) { +} diff --git a/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java index 1cb1f3102..2e02bae4a 100644 --- a/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java @@ -22,26 +22,30 @@ import alpine.model.ApiKey; import alpine.model.ConfigProperty; import alpine.model.ManagedUser; +import alpine.model.Permission; import alpine.model.Team; import alpine.server.auth.JsonWebToken; import alpine.server.filters.ApiFilter; import alpine.server.filters.AuthenticationFilter; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.dependencytrack.JerseyTestRule; import org.dependencytrack.ResourceTest; 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; import org.junit.ClassRule; import org.junit.Test; -import jakarta.json.JsonArray; -import jakarta.json.JsonObject; -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; @@ -314,4 +318,50 @@ public void updateApiKeyCommentNotFoundTest() { assertThat(response.getStatus()).isEqualTo(404); assertThat(getPlainTextBody(response)).isEqualTo("The API key could not be found."); } + + @Test + public void getVisibleNonApiKeyTeams() { + Response response = jersey.target(V1_TEAM + "/visible") + .request() + .header(X_API_KEY, apiKey) + .get(Response.class); + Assert.assertEquals(200, response.getStatus(), 0); + JsonArray teams = parseJsonArray(response); + Assert.assertEquals(1, teams.size()); + Assert.assertEquals(this.team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid")); + } + + @Test + public void getVisibleNotAdminApiKeyTeams() { + qm.createTeam("foo", true); + Response response = jersey.target(V1_TEAM + "/visible") + .request() + .header(X_API_KEY, apiKey) + .get(); + Assert.assertEquals(200, response.getStatus(), 0); + JsonArray teams = parseJsonArray(response); + Assert.assertEquals(1, teams.size()); + Assert.assertEquals(this.team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid")); + } + + @Test + public void getVisibleAdminApiKeyTeams() { + var user = qm.createTeam("user", true); + final DefaultObjectGenerator generator = new DefaultObjectGenerator(); + generator.loadDefaultPermissions(); + List permissionsList = new ArrayList<>(); + final Permission adminPermission = qm.getPermission(Permissions.ACCESS_MANAGEMENT.name()); + permissionsList.add(adminPermission); + this.team.setPermissions(permissionsList); + + Response response = jersey.target(V1_TEAM + "/visible") + .request() + .header(X_API_KEY, apiKey) + .get(); + Assert.assertEquals(200, response.getStatus(), 0); + JsonArray teams = parseJsonArray(response); + Assert.assertEquals(2, teams.size()); + Assert.assertEquals(this.team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid")); + Assert.assertEquals(user.getUuid().toString(), teams.get(1).asJsonObject().getString("uuid")); + } }