From 49718115f5c3487cf821767cba17d5552ed278ab Mon Sep 17 00:00:00 2001 From: Aurelien Poscia Date: Tue, 7 Mar 2023 17:36:32 +0100 Subject: [PATCH] SONAR-18656 Identify managed users and groups --- .../DelegatingManagedInstanceService.java | 37 +++++ .../management/ManagedInstanceService.java | 8 + .../sonar/server/management/package-info.java | 20 +++ .../DelegatingManagedInstanceServiceTest.java | 140 ++++++++++++++++-- .../server/permission/ws/GroupsActionIT.java | 98 +++++++++--- .../server/permission/ws/UsersActionIT.java | 22 ++- .../sonar/server/user/ws/SearchActionIT.java | 50 ++++++- .../server/usergroups/ws/SearchActionIT.java | 78 ++++++++-- .../server/usergroups/ws/UsersActionIT.java | 50 ++++++- .../permission/ws/UsersActionIT/users.json | 6 +- .../server/permission/ws/GroupsAction.java | 21 ++- .../server/permission/ws/UsersAction.java | 20 ++- .../sonar/server/user/ws/SearchAction.java | 27 +++- .../server/usergroups/ws/SearchAction.java | 23 ++- .../server/usergroups/ws/UsersAction.java | 22 ++- .../server/permission/ws/groups-example.json | 6 +- .../server/permission/ws/users-example.json | 9 +- .../sonar/server/user/ws/search-example.json | 6 +- .../server/usergroups/ws/search-example.json | 6 +- .../server/usergroups/ws/users-example.json | 6 +- .../org/sonar/server/permission/ws/test.json | 17 +++ .../src/main/protobuf/ws-permissions.proto | 2 + .../src/main/protobuf/ws-user_groups.proto | 1 + sonar-ws/src/main/protobuf/ws-users.proto | 1 + 24 files changed, 592 insertions(+), 84 deletions(-) create mode 100644 server/sonar-server-common/src/main/java/org/sonar/server/management/package-info.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/test.json diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedInstanceService.java b/server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedInstanceService.java index 6fa3c9782af..9bb07da8b31 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedInstanceService.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedInstanceService.java @@ -19,9 +19,18 @@ */ package org.sonar.server.management; +import com.google.common.collect.MoreCollectors; +import java.util.Map; +import java.util.Optional; import java.util.Set; import javax.annotation.Priority; import org.sonar.api.server.ServerSide; +import org.sonar.db.DbSession; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; +import static org.sonar.api.utils.Preconditions.checkState; @ServerSide @Priority(ManagedInstanceService.DELEGATING_INSTANCE_PRIORITY) @@ -36,4 +45,32 @@ public class DelegatingManagedInstanceService implements ManagedInstanceService public final boolean isInstanceExternallyManaged() { return delegates.stream().anyMatch(ManagedInstanceService::isInstanceExternallyManaged); } + + @Override + public Map getUserUuidToManaged(DbSession dbSession, Set userUuids) { + return findManagedInstanceService() + .map(managedInstanceService -> managedInstanceService.getUserUuidToManaged(dbSession, userUuids)) + .orElse(returnNonManagedForAllGroups(userUuids)); + } + + @Override + public Map getGroupUuidToManaged(DbSession dbSession, Set groupUuids) { + return findManagedInstanceService() + .map(managedInstanceService -> managedInstanceService.getGroupUuidToManaged(dbSession, groupUuids)) + .orElse(returnNonManagedForAllGroups(groupUuids)); + } + + private Optional findManagedInstanceService() { + Set managedInstanceServices = delegates.stream() + .filter(ManagedInstanceService::isInstanceExternallyManaged) + .collect(toSet()); + + checkState(managedInstanceServices.size() < 2, + "The instance can't be managed by more than one identity provider and %s were found.", managedInstanceServices.size()); + return managedInstanceServices.stream().collect(MoreCollectors.toOptional()); + } + + private static Map returnNonManagedForAllGroups(Set resourcesUuid) { + return resourcesUuid.stream().collect(toMap(identity(), any -> false)); + } } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedInstanceService.java b/server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedInstanceService.java index 00add360ad1..581fe04e34f 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedInstanceService.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedInstanceService.java @@ -19,9 +19,17 @@ */ package org.sonar.server.management; +import java.util.Map; +import java.util.Set; +import org.sonar.db.DbSession; + public interface ManagedInstanceService { int DELEGATING_INSTANCE_PRIORITY = 1; boolean isInstanceExternallyManaged(); + + Map getUserUuidToManaged(DbSession dbSession, Set userUuids); + + Map getGroupUuidToManaged(DbSession dbSession, Set groupUuids); } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/management/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/management/package-info.java new file mode 100644 index 00000000000..6c1aabdcb2d --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/management/package-info.java @@ -0,0 +1,20 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.management; diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedInstanceServiceTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedInstanceServiceTest.java index a772a261002..542074300b5 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedInstanceServiceTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedInstanceServiceTest.java @@ -19,34 +19,154 @@ */ package org.sonar.server.management; -import java.util.Collections; +import java.util.Map; import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.sonar.db.DbSession; +import static java.util.Collections.emptySet; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class DelegatingManagedInstanceServiceTest { + @Mock + private DbSession dbSession; + + @Test + public void isInstanceExternallyManaged_whenNoManagedInstanceService_returnsFalse() { + DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(emptySet()); + assertThat(managedInstanceService.isInstanceExternallyManaged()).isFalse(); + } + + @Test + public void isInstanceExternallyManaged_whenAllManagedInstanceServiceReturnsFalse_returnsFalse() { + Set delegates = Set.of(new NeverManagedInstanceService(), new NeverManagedInstanceService()); + DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(delegates); + + assertThat(managedInstanceService.isInstanceExternallyManaged()).isFalse(); + } + + @Test + public void isInstanceExternallyManaged_whenOneManagedInstanceServiceReturnsTrue_returnsTrue() { + Set delegates = Set.of(new NeverManagedInstanceService(), new AlwaysManagedInstanceService()); + DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(delegates); + + assertThat(managedInstanceService.isInstanceExternallyManaged()).isTrue(); + } + + @Test + public void getUserUuidToManaged_whenNoDelegates_setAllUsersAsNonManaged() { + Set userUuids = Set.of("a", "b"); + DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(emptySet()); + + Map userUuidToManaged = managedInstanceService.getUserUuidToManaged(dbSession, userUuids); + + assertThat(userUuidToManaged).containsExactlyInAnyOrderEntriesOf(Map.of("a", false, "b", false)); + } + + @Test + public void getUserUuidToManaged_delegatesToRightService_andPropagateAnswer() { + Set userUuids = Set.of("a", "b"); + Map serviceResponse = Map.of("a", false, "b", true); + + ManagedInstanceService anotherManagedInstanceService = getManagedInstanceService(userUuids, serviceResponse); + DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(Set.of(new NeverManagedInstanceService(), anotherManagedInstanceService)); + + Map userUuidToManaged = managedInstanceService.getUserUuidToManaged(dbSession, userUuids); + + assertThat(userUuidToManaged).containsExactlyInAnyOrderEntriesOf(serviceResponse); + } + @Test - public void isInstanceExternallyManaged_whenNoChecksDefined_returnsFalse() { - DelegatingManagedInstanceService checker = new DelegatingManagedInstanceService(Collections.emptySet()); - assertThat(checker.isInstanceExternallyManaged()).isFalse(); + public void getGroupUuidToManaged_whenNoDelegates_setAllUsersAsNonManaged() { + Set groupUuids = Set.of("a", "b"); + DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(emptySet()); + + Map groupUuidToManaged = managedInstanceService.getGroupUuidToManaged(dbSession, groupUuids); + + assertThat(groupUuidToManaged).containsExactlyInAnyOrderEntriesOf(Map.of("a", false, "b", false)); } @Test - public void isInstanceExternallyManaged_whenAllChecksReturnFalse_returnsFalse() { + public void getGroupUuidToManaged_delegatesToRightService_andPropagateAnswer() { + Set groupUuids = Set.of("a", "b"); + Map serviceResponse = Map.of("a", false, "b", true); + + ManagedInstanceService anotherManagedInstanceService = getManagedInstanceService(groupUuids, serviceResponse); + DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(Set.of(new NeverManagedInstanceService(), anotherManagedInstanceService)); + + Map groupUuidToManaged = managedInstanceService.getGroupUuidToManaged(dbSession, groupUuids); - DelegatingManagedInstanceService checker = new DelegatingManagedInstanceService(Set.of(() -> false, () -> false)); - assertThat(checker.isInstanceExternallyManaged()).isFalse(); + assertThat(groupUuidToManaged).containsExactlyInAnyOrderEntriesOf(serviceResponse); } @Test - public void isInstanceExternallyManaged_whenOneCheckReturnsTrue_returnsTrue() { - DelegatingManagedInstanceService checker = new DelegatingManagedInstanceService(Set.of(() -> false, () -> true, () -> false)); - assertThat(checker.isInstanceExternallyManaged()).isTrue(); + public void getGroupUuidToManaged_ifMoreThanOneDelegatesActivated_throws() { + Set managedInstanceServices = Set.of(new AlwaysManagedInstanceService(), new AlwaysManagedInstanceService()); + DelegatingManagedInstanceService delegatingManagedInstanceService = new DelegatingManagedInstanceService(managedInstanceServices); + assertThatIllegalStateException() + .isThrownBy(() -> delegatingManagedInstanceService.getGroupUuidToManaged(dbSession, Set.of("a"))) + .withMessage("The instance can't be managed by more than one identity provider and 2 were found."); + } + + @Test + public void getUserUuidToManaged_ifMoreThanOneDelegatesActivated_throws() { + Set managedInstanceServices = Set.of(new AlwaysManagedInstanceService(), new AlwaysManagedInstanceService()); + DelegatingManagedInstanceService delegatingManagedInstanceService = new DelegatingManagedInstanceService(managedInstanceServices); + assertThatIllegalStateException() + .isThrownBy(() -> delegatingManagedInstanceService.getUserUuidToManaged(dbSession, Set.of("a"))) + .withMessage("The instance can't be managed by more than one identity provider and 2 were found."); + } + + private ManagedInstanceService getManagedInstanceService(Set userUuids, Map uuidToManaged) { + ManagedInstanceService anotherManagedInstanceService = mock(ManagedInstanceService.class); + when(anotherManagedInstanceService.isInstanceExternallyManaged()).thenReturn(true); + when(anotherManagedInstanceService.getGroupUuidToManaged(dbSession, userUuids)).thenReturn(uuidToManaged); + when(anotherManagedInstanceService.getUserUuidToManaged(dbSession, userUuids)).thenReturn(uuidToManaged); + return anotherManagedInstanceService; + } + + private static class NeverManagedInstanceService implements ManagedInstanceService { + + @Override + public boolean isInstanceExternallyManaged() { + return false; + } + + @Override + public Map getUserUuidToManaged(DbSession dbSession, Set userUuids) { + return null; + } + + @Override + public Map getGroupUuidToManaged(DbSession dbSession, Set groupUuids) { + return null; + } + } + + private static class AlwaysManagedInstanceService implements ManagedInstanceService { + + @Override + public boolean isInstanceExternallyManaged() { + return true; + } + + @Override + public Map getUserUuidToManaged(DbSession dbSession, Set userUuids) { + return null; + } + + @Override + public Map getGroupUuidToManaged(DbSession dbSession, Set groupUuids) { + return null; + } } } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/GroupsActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/GroupsActionIT.java index 3ec82983cad..e89826e7f44 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/GroupsActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/GroupsActionIT.java @@ -19,6 +19,7 @@ */ package org.sonar.server.permission.ws; +import java.util.Set; import org.junit.Before; import org.junit.Test; import org.sonar.api.resources.Qualifiers; @@ -36,16 +37,23 @@ import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.management.ManagedInstanceService; import org.sonar.server.permission.PermissionService; import org.sonar.server.permission.PermissionServiceImpl; import static java.lang.String.format; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.sonar.api.server.ws.WebService.Param.PAGE; import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; +import static org.sonar.api.web.UserRole.ISSUE_ADMIN; import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; import static org.sonar.test.JsonAssert.assertJson; import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PERMISSION; @@ -59,13 +67,14 @@ public class GroupsActionIT extends BasePermissionWsIT { private final ResourceTypes resourceTypes = new ResourceTypesRule().setRootQualifiers(Qualifiers.PROJECT); private final PermissionService permissionService = new PermissionServiceImpl(resourceTypes); private final WsParameters wsParameters = new WsParameters(permissionService); + private final ManagedInstanceService managedInstanceService = mock(ManagedInstanceService.class); @Override protected GroupsAction buildWsAction() { return new GroupsAction( db.getDbClient(), userSession, - newPermissionWsSupport(), wsParameters); + newPermissionWsSupport(), wsParameters, managedInstanceService); } @Before @@ -88,6 +97,7 @@ public class GroupsActionIT extends BasePermissionWsIT { assertThat(wsDef.since()).isEqualTo("5.2"); assertThat(wsDef.isPost()).isFalse(); assertThat(wsDef.changelog()).extracting(Change::getVersion, Change::getDescription).containsExactlyInAnyOrder( + tuple("10.0", "Response includes 'managed' field."), tuple("8.4", "Field 'id' in the response is deprecated. Format changes from integer to string."), tuple("7.4", "The response list is returning all groups even those without permissions, the groups with permission are at the top of the list.")); } @@ -118,14 +128,16 @@ public class GroupsActionIT extends BasePermissionWsIT { " \"description\": \"" + group1.getDescription() + "\",\n" + " \"permissions\": [\n" + " \"scan\"\n" + - " ]\n" + + " ],\n" + + " \"managed\": false\n" + " },\n" + " {\n" + " \"name\": \"group-2-name\",\n" + " \"description\": \"" + group2.getDescription() + "\",\n" + " \"permissions\": [\n" + " \"scan\"\n" + - " ]\n" + + " ],\n" + + " \"managed\": false\n" + " }\n" + " ]\n" + "}\n"); @@ -175,17 +187,17 @@ public class GroupsActionIT extends BasePermissionWsIT { public void search_groups_with_project_permissions() { ComponentDto project = db.components().insertPrivateProject(); GroupDto group = db.users().insertGroup("project-group-name"); - db.users().insertProjectPermissionOnGroup(group, UserRole.ISSUE_ADMIN, project); + db.users().insertProjectPermissionOnGroup(group, ISSUE_ADMIN, project); ComponentDto anotherProject = db.components().insertPrivateProject(); GroupDto anotherGroup = db.users().insertGroup("another-project-group-name"); - db.users().insertProjectPermissionOnGroup(anotherGroup, UserRole.ISSUE_ADMIN, anotherProject); + db.users().insertProjectPermissionOnGroup(anotherGroup, ISSUE_ADMIN, anotherProject); GroupDto groupWithoutPermission = db.users().insertGroup("group-without-permission"); userSession.logIn().addProjectPermission(UserRole.ADMIN, project); String result = newRequest() - .setParam(PARAM_PERMISSION, UserRole.ISSUE_ADMIN) + .setParam(PARAM_PERMISSION, ISSUE_ADMIN) .setParam(PARAM_PROJECT_ID, project.uuid()) .execute() .getInput(); @@ -199,14 +211,14 @@ public class GroupsActionIT extends BasePermissionWsIT { public void return_also_groups_without_permission_when_search_query() { ComponentDto project = db.components().insertPrivateProject(); GroupDto group = db.users().insertGroup("group-with-permission"); - db.users().insertProjectPermissionOnGroup(group, UserRole.ISSUE_ADMIN, project); + db.users().insertProjectPermissionOnGroup(group, ISSUE_ADMIN, project); GroupDto groupWithoutPermission = db.users().insertGroup("group-without-permission"); GroupDto anotherGroup = db.users().insertGroup("another-group"); loginAsAdmin(); String result = newRequest() - .setParam(PARAM_PERMISSION, UserRole.ISSUE_ADMIN) + .setParam(PARAM_PERMISSION, ISSUE_ADMIN) .setParam(PARAM_PROJECT_ID, project.uuid()) .setParam(TEXT_QUERY, "group-with") .execute() @@ -221,13 +233,13 @@ public class GroupsActionIT extends BasePermissionWsIT { public void return_only_groups_with_permission_when_no_search_query() { ComponentDto project = db.components().insertComponent(newPrivateProjectDto("project-uuid")); GroupDto group = db.users().insertGroup("project-group-name"); - db.users().insertProjectPermissionOnGroup(group, UserRole.ISSUE_ADMIN, project); + db.users().insertProjectPermissionOnGroup(group, ISSUE_ADMIN, project); GroupDto groupWithoutPermission = db.users().insertGroup("group-without-permission"); loginAsAdmin(); String result = newRequest() - .setParam(PARAM_PERMISSION, UserRole.ISSUE_ADMIN) + .setParam(PARAM_PERMISSION, ISSUE_ADMIN) .setParam(PARAM_PROJECT_ID, project.uuid()) .execute() .getInput(); @@ -240,7 +252,7 @@ public class GroupsActionIT extends BasePermissionWsIT { public void return_anyone_group_when_search_query_and_no_param_permission() { ComponentDto project = db.components().insertPrivateProject(); GroupDto group = db.users().insertGroup("group-with-permission"); - db.users().insertProjectPermissionOnGroup(group, UserRole.ISSUE_ADMIN, project); + db.users().insertProjectPermissionOnGroup(group, ISSUE_ADMIN, project); loginAsAdmin(); String result = newRequest() @@ -256,11 +268,11 @@ public class GroupsActionIT extends BasePermissionWsIT { public void search_groups_on_views() { ComponentDto view = db.components().insertComponent(ComponentTesting.newPortfolio("view-uuid").setKey("view-key")); GroupDto group = db.users().insertGroup("project-group-name"); - db.users().insertProjectPermissionOnGroup(group, UserRole.ISSUE_ADMIN, view); + db.users().insertProjectPermissionOnGroup(group, ISSUE_ADMIN, view); loginAsAdmin(); String result = newRequest() - .setParam(PARAM_PERMISSION, UserRole.ISSUE_ADMIN) + .setParam(PARAM_PERMISSION, ISSUE_ADMIN) .setParam(PARAM_PROJECT_ID, "view-uuid") .execute() .getInput(); @@ -271,9 +283,46 @@ public class GroupsActionIT extends BasePermissionWsIT { .doesNotContain("group-3"); } + @Test + public void return_isManaged() { + ComponentDto view = db.components().insertComponent(ComponentTesting.newPortfolio("view-uuid").setKey("view-key")); + GroupDto managedGroup = db.users().insertGroup("managed-group"); + GroupDto localGroup = db.users().insertGroup("local-group"); + db.users().insertProjectPermissionOnGroup(managedGroup, ISSUE_ADMIN, view); + db.users().insertProjectPermissionOnGroup(localGroup, ISSUE_ADMIN, view); + mockGroupsAsManaged(managedGroup.getUuid()); + + loginAsAdmin(); + String result = newRequest() + .setParam(PARAM_PERMISSION, ISSUE_ADMIN) + .setParam(PARAM_PROJECT_ID, "view-uuid") + .execute() + .getInput(); + + assertJson(result).isSimilarTo( + "{\n" + + " \"paging\": {\n" + + " \"pageIndex\": 1,\n" + + " \"pageSize\": 20,\n" + + " \"total\": 2\n" + + " },\n" + + " \"groups\": [\n" + + " {\n" + + " \"name\": \"local-group\",\n" + + " \"managed\": false\n" + + " },\n" + + " {\n" + + " \"name\": \"managed-group\",\n" + + " \"managed\": true\n" + + " }\n" + + " ]\n" + + "}" + ); + } + @Test public void fail_if_not_logged_in() { - assertThatThrownBy(() -> { + assertThatThrownBy(() -> { userSession.anonymous(); newRequest() @@ -285,7 +334,7 @@ public class GroupsActionIT extends BasePermissionWsIT { @Test public void fail_if_insufficient_privileges() { - assertThatThrownBy(() -> { + assertThatThrownBy(() -> { userSession.logIn("login"); newRequest() .setParam(PARAM_PERMISSION, GlobalPermission.SCAN.getKey()) @@ -298,7 +347,7 @@ public class GroupsActionIT extends BasePermissionWsIT { public void fail_if_project_uuid_and_project_key_are_provided() { ComponentDto project = db.components().insertPrivateProject(); - assertThatThrownBy(() -> { + assertThatThrownBy(() -> { loginAsAdmin(); newRequest() .setParam(PARAM_PERMISSION, GlobalPermission.SCAN.getKey()) @@ -314,16 +363,27 @@ public class GroupsActionIT extends BasePermissionWsIT { ComponentDto project = db.components().insertPublicProject(); ComponentDto branch = db.components().insertProjectBranch(project); GroupDto group = db.users().insertGroup(); - db.users().insertProjectPermissionOnGroup(group, UserRole.ISSUE_ADMIN, project); + db.users().insertProjectPermissionOnGroup(group, ISSUE_ADMIN, project); loginAsAdmin(); - assertThatThrownBy(() -> { + assertThatThrownBy(() -> { newRequest() - .setParam(PARAM_PERMISSION, UserRole.ISSUE_ADMIN) + .setParam(PARAM_PERMISSION, ISSUE_ADMIN) .setParam(PARAM_PROJECT_ID, branch.uuid()) .execute(); }) .isInstanceOf(NotFoundException.class) .hasMessage(format("Project id '%s' not found", branch.uuid())); } + + private void mockGroupsAsManaged(String... groupUuids) { + when(managedInstanceService.getGroupUuidToManaged(any(), any())).thenAnswer(invocation -> + { + Set allGroupUuids = invocation.getArgument(1, Set.class); + return allGroupUuids.stream() + .map(groupUuid -> (String) groupUuid) + .collect(toMap(identity(), userUuid -> Set.of(groupUuids).contains(userUuid))); + } + ); + } } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/UsersActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/UsersActionIT.java index 4c48136cc2a..c9a9eecf57d 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/UsersActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/UsersActionIT.java @@ -19,6 +19,7 @@ */ package org.sonar.server.permission.ws; +import java.util.Set; import org.junit.Test; import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.ResourceTypes; @@ -34,14 +35,20 @@ import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.issue.AvatarResolverImpl; +import org.sonar.server.management.ManagedInstanceService; import org.sonar.server.permission.PermissionService; import org.sonar.server.permission.PermissionServiceImpl; import org.sonar.server.permission.RequestValidator; import static java.lang.String.format; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; import static org.apache.commons.lang.StringUtils.countMatches; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.sonar.api.server.ws.WebService.Param.PAGE; import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; @@ -58,10 +65,11 @@ public class UsersActionIT extends BasePermissionWsIT { private PermissionService permissionService = new PermissionServiceImpl(resourceTypes); private WsParameters wsParameters = new WsParameters(permissionService); private RequestValidator requestValidator = new RequestValidator(permissionService); + private ManagedInstanceService managedInstanceService = mock(ManagedInstanceService.class); @Override protected UsersAction buildWsAction() { - return new UsersAction(db.getDbClient(), userSession, newPermissionWsSupport(), new AvatarResolverImpl(), wsParameters, requestValidator); + return new UsersAction(db.getDbClient(), userSession, newPermissionWsSupport(), new AvatarResolverImpl(), wsParameters, requestValidator, managedInstanceService); } @Test @@ -73,6 +81,7 @@ public class UsersActionIT extends BasePermissionWsIT { db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.ADMINISTER); db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.ADMINISTER_QUALITY_GATES); db.users().insertGlobalPermissionOnUser(user3, GlobalPermission.SCAN); + mockUsersAsManaged(user3.getUuid()); loginAsAdmin(); String result = newRequest().execute().getInput(); @@ -346,6 +355,17 @@ public class UsersActionIT extends BasePermissionWsIT { db.users().insertGlobalPermissionOnUser(user1, GlobalPermission.SCAN); db.users().insertGlobalPermissionOnUser(user2, GlobalPermission.SCAN); db.users().insertGlobalPermissionOnUser(user3, GlobalPermission.ADMINISTER); + mockUsersAsManaged(user1.getUuid()); } + private void mockUsersAsManaged(String... userUuids) { + when(managedInstanceService.getUserUuidToManaged(any(), any())).thenAnswer(invocation -> + { + Set allUsersUuids = invocation.getArgument(1, Set.class); + return allUsersUuids.stream() + .map(userUuid -> (String) userUuid) + .collect(toMap(identity(), userUuid -> Set.of(userUuids).contains(userUuid))); + } + ); + } } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/SearchActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/SearchActionIT.java index d549d956e2b..c932f86ce34 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/SearchActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/SearchActionIT.java @@ -19,6 +19,7 @@ */ package org.sonar.server.user.ws; +import java.util.Set; import java.util.stream.IntStream; import org.junit.Rule; import org.junit.Test; @@ -30,6 +31,7 @@ import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; import org.sonar.server.es.EsTester; import org.sonar.server.issue.AvatarResolverImpl; +import org.sonar.server.management.ManagedInstanceService; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.user.index.UserIndex; import org.sonar.server.user.index.UserIndexer; @@ -41,8 +43,13 @@ import org.sonarqube.ws.Users.SearchWsResponse.User; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.test.JsonAssert.assertJson; @@ -57,9 +64,10 @@ public class SearchActionIT { @Rule public DbTester db = DbTester.create(); + private ManagedInstanceService managedInstanceService = mock(ManagedInstanceService.class); private UserIndex index = new UserIndex(es.client(), System2.INSTANCE); private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client()); - private WsActionTester ws = new WsActionTester(new SearchAction(userSession, index, db.getDbClient(), new AvatarResolverImpl())); + private WsActionTester ws = new WsActionTester(new SearchAction(userSession, index, db.getDbClient(), new AvatarResolverImpl(), managedInstanceService)); @Test public void search_for_all_active_users() { @@ -139,6 +147,25 @@ public class SearchActionIT { .containsExactlyInAnyOrder(tuple(user.getLogin(), "6a6c19fea4a3676970167ce51f39e6ee")); } + @Test + public void return_isManaged() { + UserDto nonManagedUser = db.users().insertUser(u -> u.setEmail("john@doe.com")); + UserDto managedUser = db.users().insertUser(u -> u.setEmail("externalUser@doe.com")); + mockUsersAsManaged(managedUser.getUuid()); + userIndexer.indexAll(); + userSession.logIn().setSystemAdministrator(); + + SearchWsResponse response = ws.newRequest() + .executeProtobuf(SearchWsResponse.class); + + assertThat(response.getUsersList()) + .extracting(User::getLogin, User::getManaged) + .containsExactlyInAnyOrder( + tuple(managedUser.getLogin(), true), + tuple(nonManagedUser.getLogin(), false) + ); + } + @Test public void return_scm_accounts() { UserDto user = db.users().insertUser(u -> u.setScmAccounts(asList("john1", "john2"))); @@ -273,8 +300,8 @@ public class SearchActionIT { .executeProtobuf(SearchWsResponse.class); assertThat(response.getUsersList()) - .extracting(User::getLogin, User::getName, User::hasTokensCount, User::hasScmAccounts, User::hasAvatar, User::hasGroups) - .containsExactlyInAnyOrder(tuple(user.getLogin(), user.getName(), false, false, false, false)); + .extracting(User::getLogin, User::getName, User::hasTokensCount, User::hasScmAccounts, User::hasAvatar, User::hasGroups, User::hasManaged) + .containsExactlyInAnyOrder(tuple(user.getLogin(), user.getName(), false, false, false, false, false)); } @Test @@ -310,9 +337,9 @@ public class SearchActionIT { assertThat(ws.newRequest().setParam("q", user.getLogin()) .executeProtobuf(SearchWsResponse.class).getUsersList()) .extracting(User::getLogin, User::getName, User::getEmail, User::getExternalIdentity, User::getExternalProvider, - User::hasScmAccounts, User::hasAvatar, User::hasGroups, User::getTokensCount, User::hasLastConnectionDate) + User::hasScmAccounts, User::hasAvatar, User::hasGroups, User::getTokensCount, User::hasLastConnectionDate, User::hasManaged) .containsExactlyInAnyOrder( - tuple(user.getLogin(), user.getName(), user.getEmail(), user.getExternalLogin(), user.getExternalIdentityProvider(), true, true, true, 2, true)); + tuple(user.getLogin(), user.getName(), user.getEmail(), user.getExternalLogin(), user.getExternalIdentityProvider(), true, true, true, 2, true, true)); userSession.logIn(otherUser); assertThat(ws.newRequest().setParam("q", user.getLogin()) @@ -374,6 +401,8 @@ public class SearchActionIT { .setExternalLogin("sbrandhof@ldap.com") .setExternalIdentityProvider("sonarqube") .setScmAccounts(asList("simon.brandhof", "s.brandhof@company.tld"))); + mockUsersAsManaged(simon.getUuid()); + GroupDto sonarUsers = db.users().insertGroup("sonar-users"); GroupDto sonarAdministrators = db.users().insertGroup("sonar-administrators"); db.users().insertMember(sonarUsers, simon); @@ -400,4 +429,15 @@ public class SearchActionIT { assertThat(action.params()).hasSize(4); } + private void mockUsersAsManaged(String... userUuids) { + when(managedInstanceService.getUserUuidToManaged(any(), any())).thenAnswer(invocation -> + { + Set allUsersUuids = invocation.getArgument(1, Set.class); + return allUsersUuids.stream() + .map(userUuid -> (String) userUuid) + .collect(toMap(identity(), userUuid -> Set.of(userUuids).contains(userUuid))); + } + ); + } + } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/SearchActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/SearchActionIT.java index 928433dd6e8..b3d06a4113a 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/SearchActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/SearchActionIT.java @@ -19,6 +19,7 @@ */ package org.sonar.server.usergroups.ws; +import java.util.Set; import org.junit.Rule; import org.junit.Test; import org.sonar.api.server.ws.Change; @@ -28,6 +29,7 @@ import org.sonar.db.DbTester; import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; import org.sonar.server.exceptions.UnauthorizedException; +import org.sonar.server.management.ManagedInstanceService; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.usergroups.DefaultGroupFinder; import org.sonar.server.ws.TestRequest; @@ -35,10 +37,15 @@ import org.sonar.server.ws.WsActionTester; import org.sonarqube.ws.Common.Paging; import org.sonarqube.ws.MediaTypes; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; import static org.apache.commons.lang.StringUtils.capitalize; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.sonar.api.server.ws.WebService.Param.FIELDS; import static org.sonar.api.server.ws.WebService.Param.PAGE; import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; @@ -57,9 +64,10 @@ public class SearchActionIT { @Rule public UserSessionRule userSession = UserSessionRule.standalone(); + private final ManagedInstanceService managedInstanceService = mock(ManagedInstanceService.class); private final WsActionTester ws = new WsActionTester(new SearchAction(db.getDbClient(), userSession, - new DefaultGroupFinder(db.getDbClient()))); + new DefaultGroupFinder(db.getDbClient()), managedInstanceService)); @Test public void define_search_action() { @@ -69,6 +77,7 @@ public class SearchActionIT { assertThat(action.responseExampleAsString()).isNotEmpty(); assertThat(action.params()).hasSize(4); assertThat(action.changelog()).extracting(Change::getVersion, Change::getDescription).containsOnly( + tuple("10.0", "Response includes 'managed' field."), tuple("8.4", "Field 'id' in the response is deprecated. Format changes from integer to string."), tuple("6.4", "Paging response fields moved to a Paging object"), tuple("6.4", "'default' response field has been added")); @@ -93,6 +102,26 @@ public class SearchActionIT { tuple("sonar-users", "Users", 0)); } + @Test + public void search_returnsCorrectlyIsManagedFlag() { + insertDefaultGroup(0); + insertGroup("admins", 0); + insertGroup("customer1", 0); + GroupDto customer2group = insertGroup("customer2", 0); + GroupDto customer3group = insertGroup("customer3", 0); + mockGroupAsManaged(customer2group.getUuid(), customer3group.getUuid()); + loginAsAdmin(); + + SearchWsResponse response = call(ws.newRequest()); + + assertThat(response.getGroupsList()).extracting(Group::getName, Group::getManaged).containsOnly( + tuple("admins", false), + tuple("customer1", false), + tuple("customer2", true), + tuple("customer3", true), + tuple("sonar-users", false)); + } + @Test public void search_with_members() { insertDefaultGroup(5); @@ -161,16 +190,24 @@ public class SearchActionIT { insertDefaultGroup(0); loginAsAdmin(); - assertThat(call(ws.newRequest()).getGroupsList()).extracting(Group::hasId, Group::hasName, Group::hasDescription, Group::hasMembersCount) - .containsOnly(tuple(true, true, true, true)); - assertThat(call(ws.newRequest().setParam(FIELDS, "")).getGroupsList()).extracting(Group::hasId, Group::hasName, Group::hasDescription, Group::hasMembersCount) - .containsOnly(tuple(true, true, true, true)); - assertThat(call(ws.newRequest().setParam(FIELDS, "name")).getGroupsList()).extracting(Group::hasId, Group::hasName, Group::hasDescription, Group::hasMembersCount) - .containsOnly(tuple(true, true, false, false)); - assertThat(call(ws.newRequest().setParam(FIELDS, "description")).getGroupsList()).extracting(Group::hasId, Group::hasName, Group::hasDescription, Group::hasMembersCount) - .containsOnly(tuple(true, false, true, false)); - assertThat(call(ws.newRequest().setParam(FIELDS, "membersCount")).getGroupsList()).extracting(Group::hasId, Group::hasName, Group::hasDescription, Group::hasMembersCount) - .containsOnly(tuple(true, false, false, true)); + assertThat(call(ws.newRequest()).getGroupsList()) + .extracting(Group::hasId, Group::hasName, Group::hasDescription, Group::hasMembersCount, Group::hasManaged) + .containsOnly(tuple(true, true, true, true, true)); + assertThat(call(ws.newRequest().setParam(FIELDS, "")).getGroupsList()) + .extracting(Group::hasId, Group::hasName, Group::hasDescription, Group::hasMembersCount, Group::hasManaged) + .containsOnly(tuple(true, true, true, true, true)); + assertThat(call(ws.newRequest().setParam(FIELDS, "name")).getGroupsList()) + .extracting(Group::hasId, Group::hasName, Group::hasDescription, Group::hasMembersCount, Group::hasManaged) + .containsOnly(tuple(true, true, false, false, false)); + assertThat(call(ws.newRequest().setParam(FIELDS, "description")).getGroupsList()) + .extracting(Group::hasId, Group::hasName, Group::hasDescription, Group::hasMembersCount, Group::hasManaged) + .containsOnly(tuple(true, false, true, false, false)); + assertThat(call(ws.newRequest().setParam(FIELDS, "membersCount")).getGroupsList()) + .extracting(Group::hasId, Group::hasName, Group::hasDescription, Group::hasMembersCount, Group::hasManaged) + .containsOnly(tuple(true, false, false, true, false)); + assertThat(call(ws.newRequest().setParam(FIELDS, "managed")).getGroupsList()) + .extracting(Group::hasId, Group::hasName, Group::hasDescription, Group::hasMembersCount, Group::hasManaged) + .containsOnly(tuple(true, false, false, false, true)); } @Test @@ -196,7 +233,8 @@ public class SearchActionIT { @Test public void test_json_example() { insertDefaultGroup(17); - insertGroup("administrators", 2); + GroupDto groupDto = insertGroup("administrators", 2); + mockGroupAsManaged(groupDto.getUuid()); loginAsAdmin(); String response = ws.newRequest().setMediaType(MediaTypes.JSON).execute().getInput(); @@ -215,7 +253,7 @@ public class SearchActionIT { assertThat(action.params()).extracting(WebService.Param::key).containsOnly("p", "q", "ps", "f"); - assertThat(action.param("f").possibleValues()).containsOnly("name", "description", "membersCount"); + assertThat(action.param("f").possibleValues()).containsOnly("name", "description", "membersCount", "managed"); } private SearchWsResponse call(TestRequest request) { @@ -227,10 +265,22 @@ public class SearchActionIT { addMembers(group, numberOfMembers); } - private void insertGroup(String name, int numberOfMembers) { + private GroupDto insertGroup(String name, int numberOfMembers) { GroupDto group = newGroupDto().setName(name).setDescription(capitalize(name)); db.users().insertGroup(group); addMembers(group, numberOfMembers); + return group; + } + + private void mockGroupAsManaged(String... groupUuids) { + when(managedInstanceService.getGroupUuidToManaged(any(), any())).thenAnswer(invocation -> + { + @SuppressWarnings("unchecked") + Set allGroupUuids = (Set) invocation.getArgument(1, Set.class); + return allGroupUuids.stream() + .collect(toMap(identity(), userUuid -> Set.of(groupUuids).contains(userUuid))); + } + ); } private void addMembers(GroupDto group, int numberOfMembers) { diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/UsersActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/UsersActionIT.java index 5d7bd14305d..3888e144b20 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/UsersActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/UsersActionIT.java @@ -19,6 +19,7 @@ */ package org.sonar.server.usergroups.ws; +import java.util.Set; import org.junit.Rule; import org.junit.Test; import org.sonar.api.server.ws.Change; @@ -31,14 +32,20 @@ import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.management.ManagedInstanceService; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.usergroups.DefaultGroupFinder; import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.WsActionTester; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.sonar.db.permission.GlobalPermission.ADMINISTER; import static org.sonar.db.user.UserTesting.newUserDto; import static org.sonar.server.usergroups.ws.GroupWsSupport.PARAM_GROUP_NAME; @@ -50,8 +57,11 @@ public class UsersActionIT { public DbTester db = DbTester.create(System2.INSTANCE); @Rule public UserSessionRule userSession = UserSessionRule.standalone(); + + private final ManagedInstanceService managedInstanceService = mock(ManagedInstanceService.class); + private final WsActionTester ws = new WsActionTester( - new UsersAction(db.getDbClient(), userSession, new GroupWsSupport(db.getDbClient(), new DefaultGroupFinder(db.getDbClient())))); + new UsersAction(db.getDbClient(), userSession, managedInstanceService, new GroupWsSupport(db.getDbClient(), new DefaultGroupFinder(db.getDbClient())))); @Test public void verify_definition() { @@ -61,6 +71,7 @@ public class UsersActionIT { assertThat(wsDef.since()).isEqualTo("5.2"); assertThat(wsDef.isPost()).isFalse(); assertThat(wsDef.changelog()).extracting(Change::getVersion, Change::getDescription).containsOnly( + tuple("10.0", "Field 'managed' added to the payload."), tuple("10.0", "Parameter 'id' is removed. Use 'name' instead."), tuple("9.8", "response fields 'total', 's', 'ps' have been deprecated, please use 'paging' object instead."), tuple("9.8", "The field 'paging' has been added to the response."), @@ -138,6 +149,31 @@ public class UsersActionIT { """); } + @Test + public void test_isManagedFlag() { + GroupDto group = db.users().insertGroup(); + UserDto lovelace = db.users().insertUser(newUserDto().setLogin("ada.login").setName("Ada Lovelace")); + UserDto hopper = db.users().insertUser(newUserDto().setLogin("grace").setName("Grace Hopper")); + mockUsersAsManaged(hopper.getUuid()); + db.users().insertMember(group, hopper); + db.users().insertMember(group, lovelace); + loginAsAdmin(); + + String result = newUsersRequest() + .setParam(PARAM_GROUP_NAME, group.getName()) + .execute() + .getInput(); + + assertJson(result).isSimilarTo(""" + { + "users": [ + {"login": "ada.login", "name": "Ada Lovelace", "managed": false}, + {"login": "grace", "name": "Grace Hopper", "managed": true} + ] + } + """); + } + @Test public void filter_members_by_name() { GroupDto group = db.users().insertGroup("a group"); @@ -327,6 +363,7 @@ public class UsersActionIT { db.users().insertMember(group, admin); UserDto george = db.users().insertUser(newUserDto().setLogin("george.orwell").setName("George Orwell")); db.users().insertMember(group, george); + mockUsersAsManaged(george.getUuid()); loginAsAdmin(); String result = newUsersRequest() @@ -346,4 +383,15 @@ public class UsersActionIT { userSession.logIn().addPermission(ADMINISTER); } + private void mockUsersAsManaged(String... userUuids) { + when(managedInstanceService.getUserUuidToManaged(any(), any())).thenAnswer(invocation -> + { + Set allUsersUuids = invocation.getArgument(1, Set.class); + return allUsersUuids.stream() + .map(userUuid -> (String) userUuid) + .collect(toMap(identity(), userUuid -> Set.of(userUuids).contains(userUuid))); + } + ); + } + } diff --git a/server/sonar-webserver-webapi/src/it/resources/org/sonar/server/permission/ws/UsersActionIT/users.json b/server/sonar-webserver-webapi/src/it/resources/org/sonar/server/permission/ws/UsersActionIT/users.json index 29534a49dc7..f471efc2fdc 100644 --- a/server/sonar-webserver-webapi/src/it/resources/org/sonar/server/permission/ws/UsersActionIT/users.json +++ b/server/sonar-webserver-webapi/src/it/resources/org/sonar/server/permission/ws/UsersActionIT/users.json @@ -11,7 +11,8 @@ "email": "email-1", "permissions": [ "scan" - ] + ], + "managed": true }, { "login": "login-2", @@ -19,7 +20,8 @@ "email": "email-2", "permissions": [ "scan" - ] + ], + "managed": false } ] } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/GroupsAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/GroupsAction.java index a8c5bd3df87..4fe88b265bf 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/GroupsAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/GroupsAction.java @@ -24,7 +24,9 @@ import com.google.common.collect.Ordering; import com.google.common.collect.TreeMultimap; import com.google.common.io.Resources; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.annotation.Nullable; import org.sonar.api.security.DefaultGroups; import org.sonar.api.server.ws.Change; @@ -40,12 +42,14 @@ import org.sonar.db.component.ComponentDto; import org.sonar.db.permission.GroupPermissionDto; import org.sonar.db.permission.PermissionQuery; import org.sonar.db.user.GroupDto; +import org.sonar.server.management.ManagedInstanceService; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Permissions.Group; import org.sonarqube.ws.Permissions.WsGroupsResponse; import static java.util.Collections.emptyList; import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toSet; import static org.sonar.db.permission.PermissionQuery.DEFAULT_PAGE_SIZE; import static org.sonar.db.permission.PermissionQuery.RESULTS_MAX_SIZE; import static org.sonar.db.permission.PermissionQuery.SEARCH_QUERY_MIN_LENGTH; @@ -58,12 +62,15 @@ public class GroupsAction implements PermissionsWsAction { private final UserSession userSession; private final PermissionWsSupport wsSupport; private final WsParameters wsParameters; + private final ManagedInstanceService managedInstanceService; - public GroupsAction(DbClient dbClient, UserSession userSession, PermissionWsSupport wsSupport, WsParameters wsParameters) { + public GroupsAction(DbClient dbClient, UserSession userSession, PermissionWsSupport wsSupport, WsParameters wsParameters, + ManagedInstanceService managedInstanceService) { this.dbClient = dbClient; this.userSession = userSession; this.wsSupport = wsSupport; this.wsParameters = wsParameters; + this.managedInstanceService = managedInstanceService; } @Override @@ -81,6 +88,7 @@ public class GroupsAction implements PermissionsWsAction { "") .addPagingParams(DEFAULT_PAGE_SIZE, RESULTS_MAX_SIZE) .setChangelog( + new Change("10.0", "Response includes 'managed' field."), new Change("8.4", "Field 'id' in the response is deprecated. Format changes from integer to string."), new Change("7.4", "The response list is returning all groups even those without permissions, the groups with permission are at the top of the list.")) .setResponseExample(Resources.getResource(getClass(), "groups-example.json")) @@ -104,12 +112,17 @@ public class GroupsAction implements PermissionsWsAction { List groups = findGroups(dbSession, query); int total = dbClient.groupPermissionDao().countGroupsByQuery(dbSession, query); List groupsWithPermission = findGroupPermissions(dbSession, groups, project.orElse(null)); + Map groupUuidToIsManaged = managedInstanceService.getGroupUuidToManaged(dbSession, getUserUuids(groups)); Paging paging = Paging.forPageIndex(request.mandatoryParamAsInt(Param.PAGE)).withPageSize(query.getPageSize()).andTotal(total); - WsGroupsResponse groupsResponse = buildResponse(groups, groupsWithPermission, paging); + WsGroupsResponse groupsResponse = buildResponse(groups, groupsWithPermission, groupUuidToIsManaged, paging); writeProtobuf(groupsResponse, request, response); } } + private static Set getUserUuids(List groups) { + return groups.stream().map(GroupDto::getUuid).collect(toSet()); + } + private static PermissionQuery buildPermissionQuery(Request request, @Nullable ComponentDto project) { String textQuery = request.param(Param.TEXT_QUERY); PermissionQuery.Builder permissionQuery = PermissionQuery.builder() @@ -123,7 +136,8 @@ public class GroupsAction implements PermissionsWsAction { return permissionQuery.build(); } - private static WsGroupsResponse buildResponse(List groups, List groupPermissions, Paging paging) { + private static WsGroupsResponse buildResponse(List groups, List groupPermissions, + Map groupUuidToIsManaged, Paging paging) { Multimap permissionsByGroupUuid = TreeMultimap.create(); groupPermissions.forEach(groupPermission -> permissionsByGroupUuid.put(groupPermission.getGroupUuid(), groupPermission.getRole())); WsGroupsResponse.Builder response = WsGroupsResponse.newBuilder(); @@ -136,6 +150,7 @@ public class GroupsAction implements PermissionsWsAction { } ofNullable(group.getDescription()).ifPresent(wsGroup::setDescription); wsGroup.addAllPermissions(permissionsByGroupUuid.get(group.getUuid())); + wsGroup.setManaged(groupUuidToIsManaged.getOrDefault(group.getUuid(), false)); }); response.getPagingBuilder() diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/UsersAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/UsersAction.java index 12ba2fab6eb..8656986b1ee 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/UsersAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/UsersAction.java @@ -23,7 +23,9 @@ import com.google.common.collect.Multimap; import com.google.common.collect.Ordering; import com.google.common.collect.TreeMultimap; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; import javax.annotation.Nullable; import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; @@ -38,6 +40,7 @@ import org.sonar.db.permission.PermissionQuery; import org.sonar.db.permission.UserPermissionDto; import org.sonar.db.user.UserDto; import org.sonar.server.issue.AvatarResolver; +import org.sonar.server.management.ManagedInstanceService; import org.sonar.server.permission.RequestValidator; import org.sonar.server.user.UserSession; import org.sonarqube.ws.Permissions; @@ -46,6 +49,7 @@ import org.sonarqube.ws.Permissions.UsersWsResponse; import static com.google.common.base.Strings.emptyToNull; import static java.util.Collections.emptyList; import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toSet; import static org.sonar.db.permission.PermissionQuery.DEFAULT_PAGE_SIZE; import static org.sonar.db.permission.PermissionQuery.RESULTS_MAX_SIZE; import static org.sonar.db.permission.PermissionQuery.SEARCH_QUERY_MIN_LENGTH; @@ -62,15 +66,17 @@ public class UsersAction implements PermissionsWsAction { private final AvatarResolver avatarResolver; private final WsParameters wsParameters; private final RequestValidator requestValidator; + private final ManagedInstanceService managedInstanceService; public UsersAction(DbClient dbClient, UserSession userSession, PermissionWsSupport wsSupport, AvatarResolver avatarResolver, WsParameters wsParameters, - RequestValidator requestValidator) { + RequestValidator requestValidator, ManagedInstanceService managedInstanceService) { this.dbClient = dbClient; this.userSession = userSession; this.wsSupport = wsSupport; this.avatarResolver = avatarResolver; this.wsParameters = wsParameters; this.requestValidator = requestValidator; + this.managedInstanceService = managedInstanceService; } @Override @@ -87,6 +93,7 @@ public class UsersAction implements PermissionsWsAction { "") .addPagingParams(DEFAULT_PAGE_SIZE, RESULTS_MAX_SIZE) .setChangelog( + new Change("10.0", "Response includes 'managed' field."), new Change("7.4", "The response list is returning all users even those without permissions, the users with permission are at the top of the list.")) .setInternal(true) .setResponseExample(getClass().getResource("users-example.json")) @@ -112,11 +119,16 @@ public class UsersAction implements PermissionsWsAction { int total = dbClient.userPermissionDao().countUsersByQuery(dbSession, query); List userPermissions = findUserPermissions(dbSession, users, project.orElse(null)); Paging paging = Paging.forPageIndex(request.mandatoryParamAsInt(Param.PAGE)).withPageSize(query.getPageSize()).andTotal(total); - UsersWsResponse usersWsResponse = buildResponse(users, userPermissions, paging); + Map userUuidToIsManaged = managedInstanceService.getUserUuidToManaged(dbSession, getUserUuids(users)); + UsersWsResponse usersWsResponse = buildResponse(users, userPermissions, userUuidToIsManaged, paging); writeProtobuf(usersWsResponse, request, response); } } + private static Set getUserUuids(List users) { + return users.stream().map(UserDto::getUuid).collect(toSet()); + } + private PermissionQuery buildPermissionQuery(Request request, @Nullable ComponentDto project) { String textQuery = request.param(Param.TEXT_QUERY); String permission = request.param(PARAM_PERMISSION); @@ -141,7 +153,8 @@ public class UsersAction implements PermissionsWsAction { return permissionQuery.build(); } - private UsersWsResponse buildResponse(List users, List userPermissions, Paging paging) { + private UsersWsResponse buildResponse(List users, List userPermissions, Map userUuidToIsManaged, + Paging paging) { Multimap permissionsByUserUuid = TreeMultimap.create(); userPermissions.forEach(userPermission -> permissionsByUserUuid.put(userPermission.getUserUuid(), userPermission.getPermission())); @@ -153,6 +166,7 @@ public class UsersAction implements PermissionsWsAction { ofNullable(user.getEmail()).ifPresent(userResponse::setEmail); ofNullable(emptyToNull(user.getEmail())).ifPresent(u -> userResponse.setAvatar(avatarResolver.create(user))); ofNullable(user.getName()).ifPresent(userResponse::setName); + ofNullable(userUuidToIsManaged.get(user.getUuid())).ifPresent(userResponse::setManaged); }); response.getPagingBuilder() diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchAction.java index 42b3cc92b57..571e794f1f9 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchAction.java @@ -24,6 +24,8 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.server.ws.Change; @@ -37,6 +39,7 @@ import org.sonar.db.user.UserDto; import org.sonar.server.es.SearchOptions; import org.sonar.server.es.SearchResult; import org.sonar.server.issue.AvatarResolver; +import org.sonar.server.management.ManagedInstanceService; import org.sonar.server.user.UserSession; import org.sonar.server.user.index.UserDoc; import org.sonar.server.user.index.UserIndex; @@ -47,6 +50,7 @@ import org.sonarqube.ws.Users.SearchWsResponse; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.emptyToNull; +import static java.lang.Boolean.TRUE; import static java.util.Optional.ofNullable; import static org.sonar.api.server.ws.WebService.Param.PAGE; import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; @@ -68,12 +72,15 @@ public class SearchAction implements UsersWsAction { private final UserIndex userIndex; private final DbClient dbClient; private final AvatarResolver avatarResolver; + private final ManagedInstanceService managedInstanceService; - public SearchAction(UserSession userSession, UserIndex userIndex, DbClient dbClient, AvatarResolver avatarResolver) { + public SearchAction(UserSession userSession, UserIndex userIndex, DbClient dbClient, AvatarResolver avatarResolver, + ManagedInstanceService managedInstanceService) { this.userSession = userSession; this.userIndex = userIndex; this.dbClient = dbClient; this.avatarResolver = avatarResolver; + this.managedInstanceService = managedInstanceService; } @Override @@ -92,6 +99,7 @@ public class SearchAction implements UsersWsAction { "Field 'lastConnectionDate' is only updated every hour, so it may not be accurate, for instance when a user authenticates many times in less than one hour.") .setSince("3.6") .setChangelog( + new Change("10.0", "Response includes 'managed' field."), new Change("9.7", "New parameter 'deactivated' to optionally search for deactivated users"), new Change("7.7", "New field 'lastConnectionDate' is added to response"), new Change("7.4", "External identity is only returned to system administrators"), @@ -139,14 +147,22 @@ public class SearchAction implements UsersWsAction { Multimap groupsByLogin = dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, logins); List users = dbClient.userDao().selectByOrderedLogins(dbSession, logins); Map tokenCountsByLogin = dbClient.userTokenDao().countTokensByUsers(dbSession, users); + Map userUuidToIsManaged = managedInstanceService.getUserUuidToManaged(dbSession, getUserUuids(users)); Paging paging = forPageIndex(request.getPage()).withPageSize(request.getPageSize()).andTotal((int) result.getTotal()); - return buildResponse(users, groupsByLogin, tokenCountsByLogin, paging); + return buildResponse(users, groupsByLogin, tokenCountsByLogin, userUuidToIsManaged, paging); } } - private SearchWsResponse buildResponse(List users, Multimap groupsByLogin, Map tokenCountsByLogin, Paging paging) { + private static Set getUserUuids(List users) { + return users.stream().map(UserDto::getUuid).collect(Collectors.toSet()); + } + + private SearchWsResponse buildResponse(List users, Multimap groupsByLogin, Map tokenCountsByLogin, + Map userUuidToIsManaged, Paging paging) { SearchWsResponse.Builder responseBuilder = newBuilder(); - users.forEach(user -> responseBuilder.addUsers(towsUser(user, firstNonNull(tokenCountsByLogin.get(user.getUuid()), 0), groupsByLogin.get(user.getLogin())))); + users.forEach(user -> responseBuilder.addUsers( + towsUser(user, firstNonNull(tokenCountsByLogin.get(user.getUuid()), 0), groupsByLogin.get(user.getLogin()), userUuidToIsManaged.get(user.getUuid())) + )); responseBuilder.getPagingBuilder() .setPageIndex(paging.pageIndex()) .setPageSize(paging.pageSize()) @@ -155,7 +171,7 @@ public class SearchAction implements UsersWsAction { return responseBuilder.build(); } - private User towsUser(UserDto user, @Nullable Integer tokensCount, Collection groups) { + private User towsUser(UserDto user, @Nullable Integer tokensCount, Collection groups, Boolean isManaged) { User.Builder userBuilder = User.newBuilder().setLogin(user.getLogin()); ofNullable(user.getName()).ifPresent(userBuilder::setName); if (userSession.isLoggedIn()) { @@ -175,6 +191,7 @@ public class SearchAction implements UsersWsAction { ofNullable(user.getExternalLogin()).ifPresent(userBuilder::setExternalIdentity); ofNullable(tokensCount).ifPresent(userBuilder::setTokensCount); ofNullable(user.getLastConnectionDate()).ifPresent(date -> userBuilder.setLastConnectionDate(formatDateTime(date))); + userBuilder.setManaged(TRUE.equals(isManaged)); } return userBuilder.build(); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/SearchAction.java index 7592a3d54d8..19db3bd741f 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/SearchAction.java @@ -35,9 +35,11 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.user.GroupDto; import org.sonar.server.es.SearchOptions; +import org.sonar.server.management.ManagedInstanceService; import org.sonar.server.user.UserSession; import org.sonar.server.usergroups.DefaultGroupFinder; +import static java.lang.Boolean.TRUE; import static java.util.Optional.ofNullable; import static org.apache.commons.lang.StringUtils.defaultIfBlank; import static org.sonar.api.utils.Paging.forPageIndex; @@ -52,16 +54,19 @@ public class SearchAction implements UserGroupsWsAction { private static final String FIELD_NAME = "name"; private static final String FIELD_DESCRIPTION = "description"; private static final String FIELD_MEMBERS_COUNT = "membersCount"; - private static final List ALL_FIELDS = Arrays.asList(FIELD_NAME, FIELD_DESCRIPTION, FIELD_MEMBERS_COUNT); + private static final String FIELD_IS_MANAGED = "managed"; + private static final List ALL_FIELDS = Arrays.asList(FIELD_NAME, FIELD_DESCRIPTION, FIELD_MEMBERS_COUNT, FIELD_IS_MANAGED); private final DbClient dbClient; private final UserSession userSession; private final DefaultGroupFinder defaultGroupFinder; + private final ManagedInstanceService managedInstanceService; - public SearchAction(DbClient dbClient, UserSession userSession, DefaultGroupFinder defaultGroupFinder) { + public SearchAction(DbClient dbClient, UserSession userSession, DefaultGroupFinder defaultGroupFinder, ManagedInstanceService managedInstanceService) { this.dbClient = dbClient; this.userSession = userSession; this.defaultGroupFinder = defaultGroupFinder; + this.managedInstanceService = managedInstanceService; } @Override @@ -76,6 +81,7 @@ public class SearchAction implements UserGroupsWsAction { .addPagingParams(100, MAX_PAGE_SIZE) .addSearchQuery("sonar-users", "names") .setChangelog( + new Change("10.0", "Response includes 'managed' field."), new Change("8.4", "Field 'id' in the response is deprecated. Format changes from integer to string."), new Change("6.4", "Paging response fields moved to a Paging object"), new Change("6.4", "'default' response field has been added")); @@ -99,8 +105,9 @@ public class SearchAction implements UserGroupsWsAction { Paging paging = forPageIndex(page).withPageSize(pageSize).andTotal(limit); List groups = dbClient.groupDao().selectByQuery(dbSession, query, options.getOffset(), pageSize); List groupUuids = groups.stream().map(GroupDto::getUuid).collect(MoreCollectors.toList(groups.size())); + Map groupUuidToIsManaged = managedInstanceService.getGroupUuidToManaged(dbSession, new HashSet<>(groupUuids)); Map userCountByGroup = dbClient.groupMembershipDao().countUsersByGroups(dbSession, groupUuids); - writeProtobuf(buildResponse(groups, userCountByGroup, fields, paging, defaultGroup), request, response); + writeProtobuf(buildResponse(groups, userCountByGroup, groupUuidToIsManaged, fields, paging, defaultGroup), request, response); } } @@ -115,10 +122,11 @@ public class SearchAction implements UserGroupsWsAction { return fields; } - private static SearchWsResponse buildResponse(List groups, Map userCountByGroup, Set fields, Paging paging, GroupDto defaultGroup) { + private static SearchWsResponse buildResponse(List groups, Map userCountByGroup, + Map groupUuidToIsManaged, Set fields, Paging paging, GroupDto defaultGroup) { SearchWsResponse.Builder responseBuilder = SearchWsResponse.newBuilder(); groups.forEach(group -> responseBuilder - .addGroups(toWsGroup(group, userCountByGroup.get(group.getName()), fields, defaultGroup.getUuid().equals(group.getUuid())))); + .addGroups(toWsGroup(group, userCountByGroup.get(group.getName()), groupUuidToIsManaged.get(group.getUuid()), fields, defaultGroup.getUuid().equals(group.getUuid())))); responseBuilder.getPagingBuilder() .setPageIndex(paging.pageIndex()) .setPageSize(paging.pageSize()) @@ -127,7 +135,7 @@ public class SearchAction implements UserGroupsWsAction { return responseBuilder.build(); } - private static Group toWsGroup(GroupDto group, Integer memberCount, Set fields, boolean isDefault) { + private static Group toWsGroup(GroupDto group, Integer memberCount, Boolean isManaged, Set fields, boolean isDefault) { Group.Builder groupBuilder = Group.newBuilder() .setId(group.getUuid()) .setDefault(isDefault); @@ -140,6 +148,9 @@ public class SearchAction implements UserGroupsWsAction { if (fields.contains(FIELD_MEMBERS_COUNT)) { groupBuilder.setMembersCount(memberCount); } + if (fields.contains(FIELD_IS_MANAGED)) { + groupBuilder.setManaged(TRUE.equals(isManaged)); + } return groupBuilder.build(); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/UsersAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/UsersAction.java index 29aa783f329..01fc9477cfd 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/UsersAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/UsersAction.java @@ -20,6 +20,8 @@ package org.sonar.server.usergroups.ws; import java.util.List; +import java.util.Map; +import java.util.Set; import org.sonar.api.server.ws.Change; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; @@ -34,9 +36,12 @@ import org.sonar.db.DbSession; import org.sonar.db.permission.GlobalPermission; import org.sonar.db.user.UserMembershipDto; import org.sonar.db.user.UserMembershipQuery; +import org.sonar.server.management.ManagedInstanceService; import org.sonar.server.permission.GroupUuid; import org.sonar.server.user.UserSession; +import static java.lang.Boolean.TRUE; +import static java.util.stream.Collectors.toSet; import static org.sonar.api.utils.Paging.forPageIndex; import static org.sonar.server.usergroups.ws.GroupWsSupport.defineGroupWsParameters; @@ -45,14 +50,17 @@ public class UsersAction implements UserGroupsWsAction { private static final String FIELD_SELECTED = "selected"; private static final String FIELD_NAME = "name"; private static final String FIELD_LOGIN = "login"; + private static final String FIELD_MANAGED = "managed"; private final DbClient dbClient; private final UserSession userSession; + private final ManagedInstanceService managedInstanceService; private final GroupWsSupport support; - public UsersAction(DbClient dbClient, UserSession userSession, GroupWsSupport support) { + public UsersAction(DbClient dbClient, UserSession userSession, ManagedInstanceService managedInstanceService, GroupWsSupport support) { this.dbClient = dbClient; this.userSession = userSession; + this.managedInstanceService = managedInstanceService; this.support = support; } @@ -68,6 +76,7 @@ public class UsersAction implements UserGroupsWsAction { .addSearchQuery("freddy", "names", "logins") .addPagingParams(25) .setChangelog( + new Change("10.0", "Field 'managed' added to the payload."), new Change("10.0", "Parameter 'id' is removed. Use 'name' instead."), new Change("9.8", "response fields 'total', 's', 'ps' have been deprecated, please use 'paging' object instead."), new Change("9.8", "The field 'paging' has been added to the response."), @@ -97,10 +106,10 @@ public class UsersAction implements UserGroupsWsAction { int total = dbClient.groupMembershipDao().countMembers(dbSession, query); Paging paging = forPageIndex(page).withPageSize(pageSize).andTotal(total); List users = dbClient.groupMembershipDao().selectMembers(dbSession, query, paging.offset(), paging.pageSize()); - + Map userUuidToIsManaged = managedInstanceService.getUserUuidToManaged(dbSession, getUserUuids(users)); try (JsonWriter json = response.newJsonWriter()) { json.beginObject(); - writeMembers(json, users); + writeMembers(json, users, userUuidToIsManaged); writePaging(json, paging); json.name("paging").beginObject() .prop("pageIndex", page) @@ -112,13 +121,18 @@ public class UsersAction implements UserGroupsWsAction { } } - private static void writeMembers(JsonWriter json, List users) { + private static Set getUserUuids(List users) { + return users.stream().map(UserMembershipDto::getUuid).collect(toSet()); + } + + private static void writeMembers(JsonWriter json, List users, Map userUuidToIsManaged) { json.name("users").beginArray(); for (UserMembershipDto user : users) { json.beginObject() .prop(FIELD_LOGIN, user.getLogin()) .prop(FIELD_NAME, user.getName()) .prop(FIELD_SELECTED, user.getGroupUuid() != null) + .prop(FIELD_MANAGED, TRUE.equals(userUuidToIsManaged.get(user.getUuid()))) .endObject(); } json.endArray(); diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/groups-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/groups-example.json index 5e69f5b688f..d06a93bc37c 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/groups-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/groups-example.json @@ -13,13 +13,15 @@ "id": "AU-Tpxb--iU5OvuD2FLy", "name": "sonar-administrators", "description": "System administrators", - "permissions": [] + "permissions": [], + "managed": false }, { "id": "AU-Tpxb--iU5OvuD2FLz", "name": "sonar-users", "description": "Every authenticated user automatically belongs to this group", - "permissions": [] + "permissions": [], + "managed": true } ] } diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/users-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/users-example.json index 3c4e357f176..9d6718d69f6 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/users-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/users-example.json @@ -10,21 +10,24 @@ "name": "Administrator", "email": "admin@admin.com", "avatar": "64e1b8d34f425d19e1ee2ea7236d3028", - "permissions": ["admin", "gateadmin", "profileadmin"] + "permissions": ["admin", "gateadmin", "profileadmin"], + "managed": false }, { "login": "george.orwell", "name": "George Orwell", "email": "george.orwell@1984.net", "avatar": "583af86a274c1027ef078cada831babf", - "permissions": ["scan"] + "permissions": ["scan"], + "managed": true }, { "login": "adam.west", "name": "Adam West", "email": "adamwest@adamwest.com", "avatar": "9b55aba24cc5ee533294334bd20abb34", - "permissions": [] + "permissions": [], + "managed": false } ] } diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/user/ws/search-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/user/ws/search-example.json index 60705dbb5ac..84e141b731f 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/user/ws/search-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/user/ws/search-example.json @@ -18,7 +18,8 @@ "local": true, "externalIdentity": "fmallet", "externalProvider": "sonarqube", - "avatar": "2f9dff586d3f74f825b059e3798a3bbb" + "avatar": "2f9dff586d3f74f825b059e3798a3bbb", + "managed": false }, { "login": "sbrandhof", @@ -36,7 +37,8 @@ "local": false, "externalIdentity": "sbrandhof@ldap.com", "externalProvider": "sonarqube", - "avatar": "3930ad855bc7fe48db8e9a663174cdd3" + "avatar": "3930ad855bc7fe48db8e9a663174cdd3", + "managed": true } ] } diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/usergroups/ws/search-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/usergroups/ws/search-example.json index 6cf71371022..a2bd06211c4 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/usergroups/ws/search-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/usergroups/ws/search-example.json @@ -10,14 +10,16 @@ "name": "sonar-users", "description": "Users", "membersCount": 17, - "default": true + "default": true, + "managed": false }, { "id": "AU-Tpxb--iU5OvuD2FLz", "name": "administrators", "description": "Administrators", "membersCount": 2, - "default": false + "default": false, + "managed": true } ] } diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/usergroups/ws/users-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/usergroups/ws/users-example.json index 9b38c59393e..4d2243c463f 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/usergroups/ws/users-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/usergroups/ws/users-example.json @@ -3,12 +3,14 @@ { "login": "admin", "name": "Administrator", - "selected": true + "selected": true, + "managed": false }, { "login": "george.orwell", "name": "George Orwell", - "selected": true + "selected": true, + "managed": true } ], "paging": { diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/test.json b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/test.json new file mode 100644 index 00000000000..54eb990492d --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/test.json @@ -0,0 +1,17 @@ +{ + "paging": { + "pageIndex": 1, + "pageSize": 20, + "total": 2 + }, + "groups": [ + { + "name": "local-group", + "managed": false + }, + { + "name": "managed-group", + "managed": true + } + ] +} diff --git a/sonar-ws/src/main/protobuf/ws-permissions.proto b/sonar-ws/src/main/protobuf/ws-permissions.proto index 53acf4d8ec4..d236247354c 100644 --- a/sonar-ws/src/main/protobuf/ws-permissions.proto +++ b/sonar-ws/src/main/protobuf/ws-permissions.proto @@ -121,6 +121,7 @@ message User { optional string email = 3; repeated string permissions = 4; optional string avatar = 5; + optional bool managed = 6; } message OldGroup { @@ -135,4 +136,5 @@ message Group { optional string name = 2; optional string description = 3; repeated string permissions = 4; + optional bool managed = 5; } diff --git a/sonar-ws/src/main/protobuf/ws-user_groups.proto b/sonar-ws/src/main/protobuf/ws-user_groups.proto index bb5e07a7921..87846edf58d 100644 --- a/sonar-ws/src/main/protobuf/ws-user_groups.proto +++ b/sonar-ws/src/main/protobuf/ws-user_groups.proto @@ -49,4 +49,5 @@ message Group { optional string description = 4; optional int32 membersCount = 5; optional bool default = 6; + optional bool managed = 7; } diff --git a/sonar-ws/src/main/protobuf/ws-users.proto b/sonar-ws/src/main/protobuf/ws-users.proto index cac3168f8d6..d8ba5a43868 100644 --- a/sonar-ws/src/main/protobuf/ws-users.proto +++ b/sonar-ws/src/main/protobuf/ws-users.proto @@ -44,6 +44,7 @@ message SearchWsResponse { optional string externalProvider = 10; optional string avatar = 11; optional string lastConnectionDate = 12; + optional bool managed = 13; } message Groups { -- 2.39.5