diff --git a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java index 0939715ec..c66745698 100644 --- a/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/ProjectResource.java @@ -56,7 +56,6 @@ 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.WorkflowState; @@ -73,9 +72,9 @@ import javax.jdo.FetchGroup; import java.security.Principal; -import java.util.ArrayList; import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -84,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; /** @@ -373,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 = { @@ -381,17 +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 = "403", description = "You don't have the permission to assign this team to a project."), @ApiResponse(responseCode = "409", description = """ """), - @ApiResponse(responseCode = "422", description = "You need to specify at least one team to which the project should belong"), + """) }) @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"), @@ -415,44 +422,65 @@ public Response createProject(Project jsonProject) { Project parent = qm.getObjectByUuid(Project.class, jsonProject.getParent().getUuid()); jsonProject.setParent(parent); } - final List chosenTeams = jsonProject.getAccessTeams() == null ? new ArrayList<>() - : jsonProject.getAccessTeams(); - boolean required = qm.isEnabled(ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED); - if (required && chosenTeams.isEmpty()) { - throw new ClientErrorException(Response - .status(422) - .entity("You need to specify at least one team to which the project should belong") - .build()); - } + 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 = new ArrayList<>(); + 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; - 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())) { - if (isAdmin) { - throw new ClientErrorException(Response - .status(Response.Status.NOT_FOUND) - .entity("This team does not exist!") - .build()); - } else { - throw new ClientErrorException(Response - .status(Response.Status.FORBIDDEN) - .entity("You don't have the permission to assign this team to a project.") - .build()); - } + 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()); } - Team ormTeam = qm.getObjectByUuid(Team.class, choosenTeam.getUuid()); - jsonProject.addAccessTeam(ormTeam); + jsonProject.addAccessTeam(visibleTeam); } } 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 a5b05e08e..afcc702bf 100644 --- a/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/ProjectResourceTest.java @@ -22,7 +22,6 @@ import alpine.event.framework.EventService; import alpine.model.IConfigProperty.PropertyType; import alpine.model.ManagedUser; -import alpine.model.Permission; import alpine.model.Team; import alpine.server.auth.JsonWebToken; import alpine.server.filters.ApiFilter; @@ -38,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; @@ -62,7 +62,6 @@ import org.dependencytrack.model.WorkflowStatus; import org.dependencytrack.model.WorkflowStep; import org.dependencytrack.notification.NotificationConstants; -import org.dependencytrack.persistence.DefaultObjectGenerator; import org.dependencytrack.persistence.jdbi.VulnerabilityPolicyDao; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicy; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyAnalysis; @@ -108,9 +107,6 @@ public class ProjectResourceTest extends ResourceTest { - private ManagedUser testUser; - private String jwt; - @ClassRule public static JerseyTestRule jersey = new JerseyTestRule( new ResourceConfig(ProjectResource.class) @@ -124,38 +120,6 @@ public void after() { super.after(); } - 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); - 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); - } - if (isRequired) { - qm.createConfigProperty( - ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getGroupName(), - ConfigPropertyConstants.ACCESS_MANAGEMENT_ACL_ENABLED.getPropertyName(), - "true", - 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 public void getProjectsDefaultRequestTest() { for (int i = 0; i < 1000; i++) { @@ -2625,86 +2589,322 @@ public void issue3883RegressionTest() { } @Test - public void createProjectWithExistingTeamRequiredTest() { - Team AllowedTeam = qm.createTeam("AllowedTeam", false); - final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(false, true, "ProjectWithExistingTeamRequired", AllowedTeam); - qm.addUserToTeam(testUser, AllowedTeam); - Response response = jersey.target(V1_PROJECT) + 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 " + jwt) - .put(Entity.json(requestBodyBuilder.build().toString())); - Assert.assertEquals(201, response.getStatus()); - JsonObject returnedProject = parseJsonObject(response); + .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 createProjectWithoutExistingTeamRequiredTest() { - final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(false, true, "ProjectWithoutExistingTeamRequired", null); - Response response = jersey.target(V1_PROJECT) + 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 " + jwt) - .put(Entity.json(requestBodyBuilder.build().toString())); - Assert.assertEquals(422, response.getStatus(), 0); + .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 createProjectWithNotAllowedExistingTeamTest() { - Team notAllowedTeam = qm.createTeam("NotAllowedTeam", false); - final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(false, true, "ProjectWithNotAllowedExistingTeam", notAllowedTeam); - Response response = jersey.target(V1_PROJECT) + 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 " + jwt) - .put(Entity.json(requestBodyBuilder.build().toString())); - Assert.assertEquals(403, response.getStatus()); + .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 createProjectWithNotAllowedExistingTeamAdminTest() { - Team AllowedTeam = qm.createTeam("NotAllowedTeam", false); - final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(false, true, "ProjectWithNotAllowedExistingTeam", AllowedTeam); - qm.addUserToTeam(testUser, AllowedTeam); - Response response = jersey.target(V1_PROJECT) + 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 " + jwt) - .put(Entity.json(requestBodyBuilder.build().toString())); - Assert.assertEquals(201, response.getStatus()); - JsonObject returnedProject = parseJsonObject(response); + .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 createProjectWithNotExistingTeamNoAdminTest() { - Team notAllowedTeam = new Team(); - notAllowedTeam.setUuid(new UUID(1, 1)); - notAllowedTeam.setName("NotAllowedTeam"); - final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(false, true, "ProjectWithNotAllowedExistingTeam", notAllowedTeam); - Response response = jersey.target(V1_PROJECT) + 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 " + jwt) - .put(Entity.json(requestBodyBuilder.build().toString())); - Assert.assertEquals(403, response.getStatus()); + .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 createProjectWithNotExistingTeamTest() { - Team notAllowedTeam = new Team(); - notAllowedTeam.setUuid(new UUID(1, 1)); - notAllowedTeam.setName("NotAllowedTeam"); - final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(true, true, "ProjectWithNotAllowedExistingTeam", notAllowedTeam); - Response response = jersey.target(V1_PROJECT) + 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 " + jwt) - .put(Entity.json(requestBodyBuilder.build().toString())); - Assert.assertEquals(404, response.getStatus()); + .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 createProjectWithApiKeyTest() { - final JsonObjectBuilder requestBodyBuilder = setUpEnvironment(false, true, "ProjectWithNotAllowedExistingTeam", team); - Response response = jersey.target(V1_PROJECT) + 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(requestBodyBuilder.build().toString())); - Assert.assertEquals(201, response.getStatus()); - JsonObject returnedProject = parseJsonObject(response); + .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())); } }