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 = """
- 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, 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()));
}
}