diff --git a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java index 3286f6745..4a2ec0692 100644 --- a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java +++ b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java @@ -89,7 +89,7 @@ public enum ConfigPropertyConstants { KENNA_SYNC_CADENCE("integrations", "kenna.sync.cadence", "60", PropertyType.INTEGER, "The cadence (in minutes) to upload to Kenna Security", ConfigPropertyAccessMode.READ_WRITE), KENNA_TOKEN("integrations", "kenna.token", null, PropertyType.ENCRYPTEDSTRING, "The token to use when authenticating to Kenna Security", ConfigPropertyAccessMode.READ_WRITE), KENNA_CONNECTOR_ID("integrations", "kenna.connector.id", null, PropertyType.STRING, "The Kenna Security connector identifier to upload to", ConfigPropertyAccessMode.READ_WRITE), - ACCESS_MANAGEMENT_ACL_ENABLED("access-management", "acl.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable access control to projects in the portfolio", ConfigPropertyAccessMode.READ_WRITE), + ACCESS_MANAGEMENT_ACL_ENABLED("access-management", "acl.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable access control to projects in the portfolio", ConfigPropertyAccessMode.READ_WRITE, 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", ConfigPropertyAccessMode.READ_WRITE), 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", ConfigPropertyAccessMode.READ_WRITE), TASK_SCHEDULER_LDAP_SYNC_CADENCE("task-scheduler", "ldap.sync.cadence", "6", PropertyType.INTEGER, "Sync cadence (in hours) for LDAP", ConfigPropertyAccessMode.READ_WRITE), diff --git a/src/main/java/org/dependencytrack/model/Project.java b/src/main/java/org/dependencytrack/model/Project.java index 5e5a33765..504be45bf 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.annotation.JsonView; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; @@ -37,7 +38,6 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; - import org.dependencytrack.persistence.converter.OrganizationalContactsJsonConverter; import org.dependencytrack.persistence.converter.OrganizationalEntityJsonConverter; import org.dependencytrack.resources.v1.serializers.CustomPackageURLSerializer; @@ -297,7 +297,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") @@ -548,10 +547,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/main/java/org/dependencytrack/resources/v1/ProjectResource.java b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java index 4e9779a81..c66745698 100644 --- a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java @@ -20,7 +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.persistence.PaginatedResult; import alpine.server.auth.PermissionRequired; import alpine.server.resources.AlpineResource; @@ -72,6 +74,7 @@ import java.security.Principal; import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -80,7 +83,9 @@ import java.util.function.Function; import static alpine.event.framework.Event.isEventBeingProcessed; +import static java.util.Objects.requireNonNullElseGet; import static org.dependencytrack.persistence.jdbi.JdbiFactory.withJdbiHandle; +import static org.dependencytrack.util.PersistenceUtil.isPersistent; import static org.dependencytrack.util.PersistenceUtil.isUniqueConstraintViolation; /** @@ -369,6 +374,13 @@ public Response getProjectsByClassifier( summary = "Creates a new project", description = """

If a parent project exists, parent.uuid is required

+

+ When portfolio access control is enabled, one or more teams to grant access + to can be provided via accessTeams. Either uuid or + name of a team must be specified. Only teams which the authenticated + principal is a member of can be assigned. Principals with ACCESS_MANAGEMENT + permission can assign any team. +

Requires permission PORTFOLIO_MANAGEMENT or PORTFOLIO_MANAGEMENT_CREATE

""" ) @ApiResponses(value = { @@ -377,15 +389,16 @@ public Response getProjectsByClassifier( description = "The created project", content = @Content(schema = @Schema(implementation = Project.class)) ), + @ApiResponse(responseCode = "400", description = "Bad Request"), @ApiResponse(responseCode = "401", description = "Unauthorized"), @ApiResponse(responseCode = "409", description = """
  • An inactive Parent cannot be selected as parent, or
  • A project with the specified name already exists
  • -
"""), + """) }) @PermissionRequired({Permissions.Constants.PORTFOLIO_MANAGEMENT, Permissions.Constants.PORTFOLIO_MANAGEMENT_CREATE}) - public Response createProject(Project jsonProject) { + public Response createProject(final Project jsonProject) { final Validator validator = super.getValidator(); failOnValidationError( validator.validateProperty(jsonProject, "authors"), @@ -397,7 +410,8 @@ 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, "accessTeams") ); if (jsonProject.getClassifier() == null) { jsonProject.setClassifier(Classifier.APPLICATION); @@ -409,6 +423,67 @@ public Response createProject(Project jsonProject) { jsonProject.setParent(parent); } + Principal principal = getPrincipal(); + + final List chosenTeams = requireNonNullElseGet( + jsonProject.getAccessTeams(), Collections::emptyList); + jsonProject.setAccessTeams(null); + + for (final Team chosenTeam : chosenTeams) { + if (chosenTeam.getUuid() == null && chosenTeam.getName() == null) { + throw new ClientErrorException(Response + .status(Response.Status.BAD_REQUEST) + .entity(""" + accessTeams must either specify a UUID or a name,\ + but the team at index %d has neither.\ + """.formatted(chosenTeams.indexOf(chosenTeam))) + .build()); + } + } + + if (!chosenTeams.isEmpty()) { + List userTeams; + if (principal instanceof final UserPrincipal userPrincipal) { + userTeams = userPrincipal.getTeams(); + } else if (principal instanceof final ApiKey apiKey) { + userTeams = apiKey.getTeams(); + } else { + userTeams = Collections.emptyList(); + } + + boolean isAdmin = qm.hasAccessManagementPermission(principal); + List visibleTeams = isAdmin ? qm.getTeams() : userTeams; + final var visibleTeamByUuid = new HashMap(visibleTeams.size()); + final var visibleTeamByName = new HashMap(visibleTeams.size()); + for (final Team visibleTeam : visibleTeams) { + visibleTeamByUuid.put(visibleTeam.getUuid(), visibleTeam); + visibleTeamByName.put(visibleTeam.getName(), visibleTeam); + } + + for (Team chosenTeam : chosenTeams) { + Team visibleTeam = visibleTeamByUuid.getOrDefault( + chosenTeam.getUuid(), + visibleTeamByName.get(chosenTeam.getName())); + if (visibleTeam == null) { + throw new ClientErrorException(Response + .status(Response.Status.BAD_REQUEST) + .entity(""" + The team with %s can not be assigned because it does not exist, \ + or is not accessible to the authenticated principal.\ + """.formatted(chosenTeam.getUuid() != null + ? "UUID " + chosenTeam.getUuid() + : "name " + chosenTeam.getName())) + .build()); + } + if (!isPersistent(visibleTeam)) { + // Teams sourced from the principal will not be in persistent state + // and need to be attached to the persistence context. + visibleTeam = qm.getObjectById(Team.class, visibleTeam.getId()); + } + jsonProject.addAccessTeam(visibleTeam); + } + } + final Project project; try { project = qm.createProject(jsonProject, jsonProject.getTags(), true); @@ -429,8 +504,6 @@ public Response createProject(Project jsonProject) { LOGGER.error("Failed to create project %s".formatted(jsonProject), e); throw new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR); } - - Principal principal = getPrincipal(); qm.updateNewProjectACL(project, principal); return project; }); diff --git a/src/main/java/org/dependencytrack/util/PersistenceUtil.java b/src/main/java/org/dependencytrack/util/PersistenceUtil.java index 580d8ef73..67d2dba38 100644 --- a/src/main/java/org/dependencytrack/util/PersistenceUtil.java +++ b/src/main/java/org/dependencytrack/util/PersistenceUtil.java @@ -159,7 +159,7 @@ public static void assertNonPersistent(final Object object, final String message } } - private static boolean isPersistent(final Object object) { + public static boolean isPersistent(final Object object) { final ObjectState objectState = JDOHelper.getObjectState(object); return objectState == PERSISTENT_CLEAN || objectState == PERSISTENT_DIRTY diff --git a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java index 6f57aaa49..afcc702bf 100644 --- a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java @@ -21,6 +21,9 @@ import alpine.common.util.UuidUtil; import alpine.event.framework.EventService; import alpine.model.IConfigProperty.PropertyType; +import alpine.model.ManagedUser; +import alpine.model.Team; +import alpine.server.auth.JsonWebToken; import alpine.server.filters.ApiFilter; import alpine.server.filters.AuthenticationFilter; import jakarta.json.Json; @@ -34,6 +37,7 @@ import org.datanucleus.store.types.wrappers.Date; import org.dependencytrack.JerseyTestRule; import org.dependencytrack.ResourceTest; +import org.dependencytrack.auth.Permissions; import org.dependencytrack.event.CloneProjectEvent; import org.dependencytrack.event.kafka.KafkaTopics; import org.dependencytrack.model.Analysis; @@ -44,6 +48,7 @@ import org.dependencytrack.model.Classifier; import org.dependencytrack.model.Component; import org.dependencytrack.model.ComponentIdentity; +import org.dependencytrack.model.ConfigPropertyConstants; import org.dependencytrack.model.ExternalReference; import org.dependencytrack.model.OrganizationalContact; import org.dependencytrack.model.OrganizationalEntity; @@ -2582,4 +2587,324 @@ public void issue3883RegressionTest() { } """); } + + @Test + public void createProjectAsUserWithAclEnabledAndExistingTeamByUuidTest() { + qm.createConfigProperty( + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(), + "true", + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription()); + + final ManagedUser testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH); + qm.addUserToTeam(testUser, team); + + final String userJwt = new JsonWebToken().createToken(testUser); + + final Response response = jersey.target(V1_PROJECT) + .request() + .header("Authorization", "Bearer " + userJwt) + .put(Entity.json(/* language=JSON */ """ + { + "name": "acme-app", + "accessTeams": [ + { + "uuid": "%s" + } + ] + } + """.formatted(team.getUuid()))); + assertThat(response.getStatus()).isEqualTo(201); + assertThatJson(getPlainTextBody(response)) + .isEqualTo(/* language=JSON */ """ + { + "uuid": "${json-unit.any-string}", + "name": "acme-app", + "classifier": "APPLICATION", + "children": [], + "properties": [], + "tags": [], + "active": true + } + """); + + assertThat(qm.getAllProjects()).satisfiesExactly(project -> + assertThat(project.getAccessTeams()).extracting(Team::getName).containsOnly(team.getName())); + } + + @Test + public void createProjectAsUserWithAclEnabledAndExistingTeamByNameTest() { + qm.createConfigProperty( + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(), + "true", + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription()); + + final ManagedUser testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH); + qm.addUserToTeam(testUser, team); + + final String userJwt = new JsonWebToken().createToken(testUser); + + final Response response = jersey.target(V1_PROJECT) + .request() + .header("Authorization", "Bearer " + userJwt) + .put(Entity.json(/* language=JSON */ """ + { + "name": "acme-app", + "accessTeams": [ + { + "name": "%s" + } + ] + } + """.formatted(team.getName()))); + assertThat(response.getStatus()).isEqualTo(201); + assertThatJson(getPlainTextBody(response)) + .isEqualTo(/* language=JSON */ """ + { + "uuid": "${json-unit.any-string}", + "name": "acme-app", + "classifier": "APPLICATION", + "children": [], + "properties": [], + "tags": [], + "active": true + } + """); + + assertThat(qm.getAllProjects()).satisfiesExactly(project -> + assertThat(project.getAccessTeams()).extracting(Team::getName).containsOnly(team.getName())); + } + + @Test + public void createProjectAsUserWithAclEnabledAndWithoutTeamTest() { + qm.createConfigProperty( + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(), + "true", + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription()); + + final ManagedUser testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH); + qm.addUserToTeam(testUser, team); + + final String userJwt = new JsonWebToken().createToken(testUser); + + final Response response = jersey.target(V1_PROJECT) + .request() + .header("Authorization", "Bearer " + userJwt) + .put(Entity.json(/* language=JSON */ """ + { + "name": "acme-app" + } + """)); + assertThat(response.getStatus()).isEqualTo(201); + assertThatJson(getPlainTextBody(response)) + .isEqualTo(/* language=JSON */ """ + { + "uuid": "${json-unit.any-string}", + "name": "acme-app", + "classifier": "APPLICATION", + "children": [], + "properties": [], + "tags": [], + "active": true + } + """); + + assertThat(qm.getAllProjects()).satisfiesExactly(project -> + assertThat(project.getAccessTeams()).isEmpty()); + } + + @Test + public void createProjectAsUserWithNotAllowedExistingTeamTest() { + qm.createConfigProperty( + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(), + "true", + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription()); + + final ManagedUser testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH); + + final String userJwt = new JsonWebToken().createToken(testUser); + + final Response response = jersey.target(V1_PROJECT) + .request() + .header("Authorization", "Bearer " + userJwt) + .put(Entity.json(/* language=JSON */ """ + { + "name": "acme-app", + "accessTeams": [ + { + "uuid": "%s" + } + ] + } + """.formatted(team.getUuid()))); + assertThat(response.getStatus()).isEqualTo(400); + assertThat(getPlainTextBody(response)).isEqualTo(""" + The team with UUID %s can not be assigned because it does not exist, \ + or is not accessible to the authenticated principal.""", team.getUuid()); + } + + @Test + public void createProjectAsUserWithAclEnabledAndNotMemberOfTeamAdminTest() { + qm.createConfigProperty( + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(), + "true", + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription()); + + initializeWithPermissions(Permissions.ACCESS_MANAGEMENT); + + final ManagedUser testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH); + qm.addUserToTeam(testUser, team); + + final String userJwt = new JsonWebToken().createToken(testUser); + + final Team otherTeam = qm.createTeam("otherTeam", false); + + final Response response = jersey.target(V1_PROJECT) + .request() + .header("Authorization", "Bearer " + userJwt) + .put(Entity.json(/* language=JSON */ """ + { + "name": "acme-app", + "accessTeams": [ + { + "uuid": "%s" + } + ] + } + """.formatted(otherTeam.getUuid()))); + assertThat(response.getStatus()).isEqualTo(201); + assertThatJson(getPlainTextBody(response)) + .isEqualTo(/* language=JSON */ """ + { + "uuid": "${json-unit.any-string}", + "name": "acme-app", + "classifier": "APPLICATION", + "children": [], + "properties": [], + "tags": [], + "active": true + } + """); + + assertThat(qm.getAllProjects()).satisfiesExactly(project -> + assertThat(project.getAccessTeams()).extracting(Team::getName).containsOnly("otherTeam")); + } + + @Test + public void createProjectAsUserWithAclEnabledAndTeamNotExistingNoAdminTest() { + qm.createConfigProperty( + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(), + "true", + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription()); + + final ManagedUser testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH); + + final String userJwt = new JsonWebToken().createToken(testUser); + + final Response response = jersey.target(V1_PROJECT) + .request() + .header("Authorization", "Bearer " + userJwt) + .put(Entity.json(/* language=JSON */ """ + { + "name": "acme-app", + "accessTeams": [ + { + "uuid": "419c32eb-5a30-47d5-8a9a-fc0cda651314" + } + ] + } + """)); + assertThat(response.getStatus()).isEqualTo(400); + assertThat(getPlainTextBody(response)).isEqualTo(""" + The team with UUID 419c32eb-5a30-47d5-8a9a-fc0cda651314 \ + can not be assigned because it does not exist, or is not \ + accessible to the authenticated principal."""); + } + + @Test + public void createProjectAsUserWithAclEnabledAndTeamNotExistingAdminTest() { + qm.createConfigProperty( + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(), + "true", + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription()); + + initializeWithPermissions(Permissions.ACCESS_MANAGEMENT); + + final ManagedUser testUser = qm.createManagedUser("testuser", TEST_USER_PASSWORD_HASH); + qm.addUserToTeam(testUser, team); + + final String userJwt = new JsonWebToken().createToken(testUser); + + final Response response = jersey.target(V1_PROJECT) + .request() + .header("Authorization", "Bearer " + userJwt) + .put(Entity.json(/* language=JSON */ """ + { + "name": "acme-app", + "accessTeams": [ + { + "uuid": "419c32eb-5a30-47d5-8a9a-fc0cda651314" + } + ] + } + """)); + assertThat(response.getStatus()).isEqualTo(400); + assertThat(getPlainTextBody(response)).isEqualTo(""" + The team with UUID 419c32eb-5a30-47d5-8a9a-fc0cda651314 \ + can not be assigned because it does not exist, or is not \ + accessible to the authenticated principal."""); + } + + @Test + public void createProjectAsApiKeyWithAclEnabledAndWithExistentTeamTest() { + qm.createConfigProperty( + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(), + "true", + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyType(), + ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getDescription()); + + final Response response = jersey.target(V1_PROJECT) + .request() + .header(X_API_KEY, apiKey) + .put(Entity.json(/* language=JSON */ """ + { + "name": "acme-app", + "accessTeams": [ + { + "uuid": "%s" + } + ] + } + """.formatted(team.getUuid()))); + assertThat(response.getStatus()).isEqualTo(201); + assertThatJson(getPlainTextBody(response)) + .isEqualTo(/* language=JSON */ """ + { + "uuid": "${json-unit.any-string}", + "name": "acme-app", + "classifier": "APPLICATION", + "children": [], + "properties": [], + "tags": [], + "active": true + } + """); + + assertThat(qm.getAllProjects()).satisfiesExactly(project -> + assertThat(project.getAccessTeams()).extracting(Team::getName).containsOnly(team.getName())); + } } diff --git a/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java index 2e02bae4a..409f4ffbe 100644 --- a/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/TeamResourceTest.java @@ -54,12 +54,30 @@ public class TeamResourceTest extends ResourceTest { + private String jwt; + private Team userNotPartof; + @ClassRule public static JerseyTestRule jersey = new JerseyTestRule( new ResourceConfig(TeamResource.class) .register(ApiFilter.class) .register(AuthenticationFilter.class)); + 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); + 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++) { @@ -332,36 +350,63 @@ public void getVisibleNonApiKeyTeams() { } @Test - public void getVisibleNotAdminApiKeyTeams() { - qm.createTeam("foo", true); + 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(X_API_KEY, apiKey) .get(); Assert.assertEquals(200, response.getStatus(), 0); JsonArray teams = parseJsonArray(response); - Assert.assertEquals(1, teams.size()); + 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 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); + public void getVisibleAdminTeams() { + setUpUser(true); + Response response = jersey.target(V1_TEAM + "/visible") + .request() + .header("Authorization", "Bearer " + jwt) + .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(userNotPartof.getUuid().toString(), teams.get(1).asJsonObject().getString("uuid")); + } + + @Test + public void getVisibleNotAdminTeams() { + setUpUser(false); + Response response = jersey.target(V1_TEAM + "/visible") + .request() + .header("Authorization", "Bearer " + jwt) + .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 getVisibleNotAdminApiKeyTeams() { 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(1, teams.size()); Assert.assertEquals(this.team.getUuid().toString(), teams.getFirst().asJsonObject().getString("uuid")); - Assert.assertEquals(user.getUuid().toString(), teams.get(1).asJsonObject().getString("uuid")); } + }