diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-management/gravitee-apim-rest-api-management-rest/src/main/java/io/gravitee/rest/api/management/rest/resource/organization/UserResource.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-management/gravitee-apim-rest-api-management-rest/src/main/java/io/gravitee/rest/api/management/rest/resource/organization/UserResource.java index 494d73af861..9b16631560a 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-management/gravitee-apim-rest-api-management-rest/src/main/java/io/gravitee/rest/api/management/rest/resource/organization/UserResource.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-management/gravitee-apim-rest-api-management-rest/src/main/java/io/gravitee/rest/api/management/rest/resource/organization/UserResource.java @@ -47,6 +47,8 @@ import java.util.HashMap; import java.util.List; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Defines the REST resources to manage Users. @@ -60,6 +62,8 @@ @Tag(name = "Users") public class UserResource extends AbstractResource { + private static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class); + @Context private ResourceContext resourceContext; @@ -120,31 +124,31 @@ public Response deleteUser() { @ApiResponse(responseCode = "404", description = "User not found") @ApiResponse(responseCode = "500", description = "Internal server error") @Permissions(@Permission(value = RolePermission.ORGANIZATION_USERS, acls = RolePermissionAction.READ)) - public List getUserGroups() { + public List getUserGroups(@QueryParam("environmentId") String environmentId) { // Check that user belongs to current organization userService.findById(GraviteeContext.getExecutionContext(), userId); + LOGGER.debug("Search groups for user: {} with query: {}", userId, environmentId); + Set groups = groupService.findByUserAndEnvironment(userId, environmentId); + List result = new ArrayList<>(); + groups.forEach(groupEntity -> { + UserGroupEntity userGroupEntity = new UserGroupEntity(); + userGroupEntity.setId(groupEntity.getId()); + userGroupEntity.setName(groupEntity.getName()); + userGroupEntity.setRoles(new HashMap<>()); + userGroupEntity.setEnvironmentId(groupEntity.getEnvironmentId()); + Set roles = membershipService.getRoles( + MembershipReferenceType.GROUP, + groupEntity.getId(), + MembershipMemberType.USER, + userId + ); + if (!roles.isEmpty()) { + roles.forEach(role -> userGroupEntity.getRoles().put(role.getScope().name(), role.getName())); + } + result.add(userGroupEntity); + }); - List groups = new ArrayList<>(); - groupService - .findByUser(userId) - .forEach(groupEntity -> { - UserGroupEntity userGroupEntity = new UserGroupEntity(); - userGroupEntity.setId(groupEntity.getId()); - userGroupEntity.setName(groupEntity.getName()); - userGroupEntity.setRoles(new HashMap<>()); - Set roles = membershipService.getRoles( - MembershipReferenceType.GROUP, - groupEntity.getId(), - MembershipMemberType.USER, - userId - ); - if (!roles.isEmpty()) { - roles.forEach(role -> userGroupEntity.getRoles().put(role.getScope().name(), role.getName())); - } - groups.add(userGroupEntity); - }); - - return groups; + return result; } @GET diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-management/gravitee-apim-rest-api-management-rest/src/test/java/io/gravitee/rest/api/management/rest/resource/UserResourceTest.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-management/gravitee-apim-rest-api-management-rest/src/test/java/io/gravitee/rest/api/management/rest/resource/UserResourceTest.java new file mode 100644 index 00000000000..9e35b8b98d1 --- /dev/null +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-management/gravitee-apim-rest-api-management-rest/src/test/java/io/gravitee/rest/api/management/rest/resource/UserResourceTest.java @@ -0,0 +1,104 @@ +/* + * Copyright © 2015 The Gravitee team (http://gravitee.io) + * + * 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. + */ +package io.gravitee.rest.api.management.rest.resource; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import io.gravitee.common.http.HttpStatusCode; +import io.gravitee.rest.api.model.*; +import jakarta.ws.rs.core.GenericType; +import jakarta.ws.rs.core.Response; +import java.util.*; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * Test for UserResource.getUserGroups() method + */ +public class UserResourceTest extends AbstractResourceTest { + + private static final String USER_ID = "test-user"; + private static final String ENV_ID = "test-env"; + + @Before + public void resetMocks() { + Mockito.reset(groupService, membershipService); + } + + @Override + protected String contextPath() { + // Matches the REST path you’re testing, e.g. /users/{userId}/groups?environmentId=ENV_ID + return "users/" + USER_ID + "/groups/"; + } + + @Test + public void shouldReturnAllGroupsIncludingEnvGroups() { + GroupEntity group1 = new GroupEntity(); + group1.setId("group1"); + group1.setName("Global Group"); + GroupEntity group2 = new GroupEntity(); + group2.setId("group2"); + group2.setName("Env Group"); + + when(groupService.findByUserAndEnvironment(USER_ID, null)).thenReturn(Set.of(group1, group2)); + + final Response response = orgTarget().request().get(); + assertEquals(HttpStatusCode.OK_200, response.getStatus()); + + List result = response.readEntity(new GenericType<>() {}); + assertNotNull(result); + assertEquals(2, result.size()); + assertTrue(result.stream().map(UserGroupEntity::getId).toList().containsAll(List.of("group1", "group2"))); + + verify(groupService, atLeastOnce()).findByUserAndEnvironment(USER_ID, null); + } + + @Test + public void shouldReturnGroupsByEnvironment() { + GroupEntity group = new GroupEntity(); + group.setId("env-group"); + group.setName("Env Group"); + when(groupService.findByUserAndEnvironment(USER_ID, ENV_ID)).thenReturn(Set.of(group)); + + final Response response = orgTarget().queryParam("environmentId", ENV_ID).request().get(); + assertEquals(HttpStatusCode.OK_200, response.getStatus()); + + List result = response.readEntity(new GenericType<>() {}); + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals("env-group", result.get(0).getId()); + assertEquals("Env Group", result.get(0).getName()); + + verify(groupService, atLeastOnce()).findByUserAndEnvironment(USER_ID, ENV_ID); + } + + @Test + public void shouldReturnEmptyListWhenNoGroups() { + when(groupService.findByUserAndEnvironment(USER_ID, null)).thenReturn(Collections.emptySet()); + + final Response response = orgTarget().request().get(); + assertEquals(HttpStatusCode.OK_200, response.getStatus()); + + List result = response.readEntity(new GenericType<>() {}); + assertNotNull(result); + assertTrue(result.isEmpty()); + + verify(groupService, atLeastOnce()).findByUserAndEnvironment(USER_ID, null); + verify(membershipService, never()).getRoles(any(), any(), any(), any()); + } +} diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-model/src/main/java/io/gravitee/rest/api/model/GroupEntity.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-model/src/main/java/io/gravitee/rest/api/model/GroupEntity.java index d251837c282..183f337d89f 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-model/src/main/java/io/gravitee/rest/api/model/GroupEntity.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-model/src/main/java/io/gravitee/rest/api/model/GroupEntity.java @@ -83,4 +83,6 @@ public class GroupEntity { private boolean primaryOwner; private String origin = OriginContext.Origin.MANAGEMENT.name(); + + private String environmentId; } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-model/src/main/java/io/gravitee/rest/api/model/UserGroupEntity.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-model/src/main/java/io/gravitee/rest/api/model/UserGroupEntity.java index 1df95565e45..e5c5fe1aa3b 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-model/src/main/java/io/gravitee/rest/api/model/UserGroupEntity.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-model/src/main/java/io/gravitee/rest/api/model/UserGroupEntity.java @@ -26,6 +26,7 @@ public class UserGroupEntity { private String id; private String name; private Map roles; + private String environmentId; public String getId() { return id; @@ -50,4 +51,12 @@ public Map getRoles() { public void setRoles(Map roles) { this.roles = roles; } + + public String getEnvironmentId() { + return environmentId; + } + + public void setEnvironmentId(String environmentId) { + this.environmentId = environmentId; + } } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/GroupService.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/GroupService.java index 20d7df1b563..3b1e53a5763 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/GroupService.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/GroupService.java @@ -49,6 +49,7 @@ public interface GroupService { List findByName(final String environmentId, String name); Set findByUser(String username); + Set findByUserAndEnvironment(String username, String environmentId); List getApis(final String environmentId, String groupId); List getApplications(String groupId); int getNumberOfMembers(ExecutionContext executionContext, String groupId); diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/GroupServiceImpl.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/GroupServiceImpl.java index 72a6625dd92..730c6139547 100644 --- a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/GroupServiceImpl.java +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/rest/api/service/impl/GroupServiceImpl.java @@ -907,6 +907,34 @@ public Set findByUser(String user) { } } + @Override + public Set findByUserAndEnvironment(String user, String environmentId) { + Set userGroups = membershipService + .getMembershipsByMemberAndReference(MembershipMemberType.USER, user, MembershipReferenceType.GROUP) + .stream() + .map(MembershipEntity::getReferenceId) + .collect(Collectors.toSet()); + + try { + Set allGroups = groupRepository.findByIds(userGroups); + Set filteredGroups = allGroups + .stream() + .filter(group -> environmentId == null || environmentId.equals(group.getEnvironmentId())) + .map(this::map) + .collect(Collectors.toSet()); + + logger.debug( + "After filtering by environment '{}': {} groups remain: {}", + environmentId, + filteredGroups.size(), + filteredGroups.stream().map(GroupEntity::getName).collect(Collectors.toList()) + ); + return filteredGroups; + } catch (TechnicalException ex) { + throw new TechnicalManagementException("An error occurs while trying to find user groups for environment " + environmentId, ex); + } + } + @Override public List getApis(final String environmentId, String groupId) { return apiRepository @@ -1060,6 +1088,7 @@ private GroupEntity map(ExecutionContext executionContext, Group group) { entity.setEmailInvitation(group.isEmailInvitation()); entity.setDisableMembershipNotifications(group.isDisableMembershipNotifications()); entity.setOrigin(group.getOrigin()); + entity.setEnvironmentId(group.getEnvironmentId()); return entity; } diff --git a/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/rest/api/service/impl/GroupService_FindByUserAndEnvironmentTest.java b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/rest/api/service/impl/GroupService_FindByUserAndEnvironmentTest.java new file mode 100644 index 00000000000..94b42b9f7bd --- /dev/null +++ b/gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/test/java/io/gravitee/rest/api/service/impl/GroupService_FindByUserAndEnvironmentTest.java @@ -0,0 +1,123 @@ +/* + * Copyright © 2015 The Gravitee team (http://gravitee.io) + * + * 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. + */ +package io.gravitee.rest.api.service.impl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import io.gravitee.repository.exceptions.TechnicalException; +import io.gravitee.repository.management.api.GroupRepository; +import io.gravitee.repository.management.model.Group; +import io.gravitee.rest.api.model.GroupEntity; +import io.gravitee.rest.api.model.MembershipEntity; +import io.gravitee.rest.api.model.MembershipMemberType; +import io.gravitee.rest.api.model.MembershipReferenceType; +import io.gravitee.rest.api.service.MembershipService; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class GroupService_FindByUserAndEnvironmentTest { + + @InjectMocks + private final GroupServiceImpl groupService = new GroupServiceImpl(); + + @Mock + private GroupRepository groupRepository; + + @Mock + private MembershipService membershipService; + + @Test + public void shouldReturnGroupsForUserInEnvironment() throws TechnicalException { + String userId = "user1"; + String envId = "env1"; + Group group1 = Group.builder().id("g1").environmentId(envId).name("Group1").build(); + Group group2 = Group.builder().id("g2").environmentId("env2").name("Group2").build(); + Group group3 = Group.builder().id("g3").environmentId(envId).name("Group3").build(); + Set userGroups = new HashSet<>(Arrays.asList("g1", "g2", "g3")); + when( + membershipService.getMembershipsByMemberAndReference(MembershipMemberType.USER, userId, MembershipReferenceType.GROUP) + ).thenReturn( + new HashSet<>( + Arrays.asList( + MembershipEntity.builder().id("m1").referenceId("g1").build(), + MembershipEntity.builder().id("m2").referenceId("g2").build(), + MembershipEntity.builder().id("m3").referenceId("g3").build() + ) + ) + ); + when(groupRepository.findByIds(userGroups)).thenReturn(Set.of(group1, group2, group3)); + Set result = groupService.findByUserAndEnvironment(userId, envId); + assertThat(result).hasSize(2); + assertThat(result.stream().map(GroupEntity::getName)).containsExactlyInAnyOrder("Group1", "Group3"); + } + + @Test + public void shouldReturnEmptyForUserWithNoGroupsInEnv() throws TechnicalException { + String userId = "user2"; + String envId = "env1"; + Group group1 = Group.builder().id("g1").environmentId("env2").name("Group2").build(); + Set userGroups = Collections.singleton("g1"); + when( + membershipService.getMembershipsByMemberAndReference(MembershipMemberType.USER, userId, MembershipReferenceType.GROUP) + ).thenReturn(new HashSet<>(Collections.singletonList(MembershipEntity.builder().id("m4").referenceId("g1").build()))); + when(groupRepository.findByIds(userGroups)).thenReturn(Set.of(group1)); + Set result = groupService.findByUserAndEnvironment(userId, envId); + assertThat(result).isEmpty(); + } + + @Test + public void shouldReturnEmptyForNonExistentUserOrEnv() throws TechnicalException { + String userId = "nouser"; + String envId = "noenv"; + when( + membershipService.getMembershipsByMemberAndReference(MembershipMemberType.USER, userId, MembershipReferenceType.GROUP) + ).thenReturn(new HashSet<>()); + when(groupRepository.findByIds(Collections.emptySet())).thenReturn(Set.of()); + Set result = groupService.findByUserAndEnvironment(userId, envId); + assertThat(result).isEmpty(); + } + + @Test + public void shouldReturnAllGroupsIfEnvIdIsNull() throws TechnicalException { + String userId = "user3"; + Group group1 = Group.builder().id("g1").environmentId("env1").name("Group1").build(); + Group group2 = Group.builder().id("g2").environmentId("env2").name("Group2").build(); + Set userGroups = new HashSet<>(Arrays.asList("g1", "g2")); + when( + membershipService.getMembershipsByMemberAndReference(MembershipMemberType.USER, userId, MembershipReferenceType.GROUP) + ).thenReturn( + new HashSet<>( + Arrays.asList( + MembershipEntity.builder().id("m5").referenceId("g1").build(), + MembershipEntity.builder().id("m6").referenceId("g2").build() + ) + ) + ); + when(groupRepository.findByIds(userGroups)).thenReturn(Set.of(group1, group2)); + Set result = groupService.findByUserAndEnvironment(userId, null); + assertThat(result).hasSize(2); + assertThat(result.stream().map(GroupEntity::getName)).containsExactlyInAnyOrder("Group1", "Group2"); + } +}