]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18656 Identify managed users and groups
authorAurelien Poscia <aurelien.poscia@sonarsource.com>
Tue, 7 Mar 2023 16:36:32 +0000 (17:36 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 22 Mar 2023 20:04:06 +0000 (20:04 +0000)
24 files changed:
server/sonar-server-common/src/main/java/org/sonar/server/management/DelegatingManagedInstanceService.java
server/sonar-server-common/src/main/java/org/sonar/server/management/ManagedInstanceService.java
server/sonar-server-common/src/main/java/org/sonar/server/management/package-info.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/management/DelegatingManagedInstanceServiceTest.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/GroupsActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/permission/ws/UsersActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/SearchActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/SearchActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/usergroups/ws/UsersActionIT.java
server/sonar-webserver-webapi/src/it/resources/org/sonar/server/permission/ws/UsersActionIT/users.json
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/GroupsAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/permission/ws/UsersAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/SearchAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/SearchAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/usergroups/ws/UsersAction.java
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/groups-example.json
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/permission/ws/users-example.json
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/user/ws/search-example.json
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/usergroups/ws/search-example.json
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/usergroups/ws/users-example.json
server/sonar-webserver-webapi/src/test/java/org/sonar/server/permission/ws/test.json [new file with mode: 0644]
sonar-ws/src/main/protobuf/ws-permissions.proto
sonar-ws/src/main/protobuf/ws-user_groups.proto
sonar-ws/src/main/protobuf/ws-users.proto

index 6fa3c9782af73ac9d80f7714361106691cdbd4bc..9bb07da8b31a8abdaa84f4f783b86fd696030239 100644 (file)
  */
 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<String, Boolean> getUserUuidToManaged(DbSession dbSession, Set<String> userUuids) {
+    return findManagedInstanceService()
+      .map(managedInstanceService -> managedInstanceService.getUserUuidToManaged(dbSession, userUuids))
+      .orElse(returnNonManagedForAllGroups(userUuids));
+  }
+
+  @Override
+  public Map<String, Boolean> getGroupUuidToManaged(DbSession dbSession, Set<String> groupUuids) {
+    return findManagedInstanceService()
+      .map(managedInstanceService -> managedInstanceService.getGroupUuidToManaged(dbSession, groupUuids))
+      .orElse(returnNonManagedForAllGroups(groupUuids));
+  }
+
+  private Optional<ManagedInstanceService> findManagedInstanceService() {
+    Set<ManagedInstanceService> 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<String, Boolean> returnNonManagedForAllGroups(Set<String> resourcesUuid) {
+    return resourcesUuid.stream().collect(toMap(identity(), any -> false));
+  }
 }
index 00add360ad1d66dc7ec77e7e35dc469cb243a936..581fe04e34f6f2e9a0e0be12263fc502f932ff39 100644 (file)
  */
 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<String, Boolean> getUserUuidToManaged(DbSession dbSession, Set<String> userUuids);
+
+  Map<String, Boolean> getGroupUuidToManaged(DbSession dbSession, Set<String> 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 (file)
index 0000000..6c1aabd
--- /dev/null
@@ -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;
index a772a261002e9aa21ea033939401773cc2fe8fb7..542074300b574d19c7a159f7325339eb8b74ae39 100644 (file)
  */
 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<ManagedInstanceService> delegates = Set.of(new NeverManagedInstanceService(), new NeverManagedInstanceService());
+    DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(delegates);
+
+    assertThat(managedInstanceService.isInstanceExternallyManaged()).isFalse();
+  }
+
+  @Test
+  public void isInstanceExternallyManaged_whenOneManagedInstanceServiceReturnsTrue_returnsTrue() {
+    Set<ManagedInstanceService> delegates = Set.of(new NeverManagedInstanceService(), new AlwaysManagedInstanceService());
+    DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(delegates);
+
+    assertThat(managedInstanceService.isInstanceExternallyManaged()).isTrue();
+  }
+
+  @Test
+  public void getUserUuidToManaged_whenNoDelegates_setAllUsersAsNonManaged() {
+    Set<String> userUuids = Set.of("a", "b");
+    DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(emptySet());
+
+    Map<String, Boolean> userUuidToManaged = managedInstanceService.getUserUuidToManaged(dbSession, userUuids);
+
+    assertThat(userUuidToManaged).containsExactlyInAnyOrderEntriesOf(Map.of("a", false, "b", false));
+  }
+
+  @Test
+  public void getUserUuidToManaged_delegatesToRightService_andPropagateAnswer() {
+    Set<String> userUuids = Set.of("a", "b");
+    Map<String, Boolean> serviceResponse = Map.of("a", false, "b", true);
+
+    ManagedInstanceService anotherManagedInstanceService = getManagedInstanceService(userUuids, serviceResponse);
+    DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(Set.of(new NeverManagedInstanceService(), anotherManagedInstanceService));
+
+    Map<String, Boolean> 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<String> groupUuids = Set.of("a", "b");
+    DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(emptySet());
+
+    Map<String, Boolean> 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<String> groupUuids = Set.of("a", "b");
+    Map<String, Boolean> serviceResponse = Map.of("a", false, "b", true);
+
+    ManagedInstanceService anotherManagedInstanceService = getManagedInstanceService(groupUuids, serviceResponse);
+    DelegatingManagedInstanceService managedInstanceService = new DelegatingManagedInstanceService(Set.of(new NeverManagedInstanceService(), anotherManagedInstanceService));
+
+    Map<String, Boolean> 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<ManagedInstanceService> 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<ManagedInstanceService> 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<String> userUuids, Map<String, Boolean> 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<String, Boolean> getUserUuidToManaged(DbSession dbSession, Set<String> userUuids) {
+      return null;
+    }
+
+    @Override
+    public Map<String, Boolean> getGroupUuidToManaged(DbSession dbSession, Set<String> groupUuids) {
+      return null;
+    }
+  }
+
+  private static class AlwaysManagedInstanceService implements ManagedInstanceService {
+
+    @Override
+    public boolean isInstanceExternallyManaged() {
+      return true;
+    }
+
+    @Override
+    public Map<String, Boolean> getUserUuidToManaged(DbSession dbSession, Set<String> userUuids) {
+      return null;
+    }
+
+    @Override
+    public Map<String, Boolean> getGroupUuidToManaged(DbSession dbSession, Set<String> groupUuids) {
+      return null;
+    }
   }
 
 }
index 3ec82983cad3c3276a6513bec135baa797435e80..e89826e7f446ab0efeb64897b72a206941f3e2d7 100644 (file)
@@ -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<GroupsAction> {
   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<GroupsAction> {
     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<GroupsAction> {
       "      \"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<GroupsAction> {
   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<GroupsAction> {
   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<GroupsAction> {
   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<GroupsAction> {
   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<GroupsAction> {
   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<GroupsAction> {
       .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<GroupsAction> {
 
   @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<GroupsAction> {
   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<GroupsAction> {
     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)));
+      }
+    );
+  }
 }
index 4c48136cc2a8782b8ecac7ce7c6e8fb0b15bc705..c9a9eecf57d1772633f0b8bf25b85b857dc24d2d 100644 (file)
@@ -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<UsersAction> {
   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<UsersAction> {
     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<UsersAction> {
     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)));
+      }
+    );
+  }
 }
index d549d956e2b9ebb81586b0127e72a18c4a43de41..c932f86ce340039f538807882b0770def57e3c0e 100644 (file)
@@ -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)));
+      }
+    );
+  }
+
 }
index 928433dd6e84f2d1f9889a3c33e40ff08e2615dc..b3d06a4113ac032ef3aed35b9cfdab5458fbe4da 100644 (file)
@@ -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<String> allGroupUuids = (Set<String>) invocation.getArgument(1, Set.class);
+        return allGroupUuids.stream()
+          .collect(toMap(identity(), userUuid -> Set.of(groupUuids).contains(userUuid)));
+      }
+    );
   }
 
   private void addMembers(GroupDto group, int numberOfMembers) {
index 5d7bd14305dd38b77c963c41affcaf38fcd40d27..3888e144b20cc0656a09ed3ce2d1f9bd5e0bea9e 100644 (file)
@@ -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)));
+      }
+    );
+  }
+
 }
index 29534a49dc77641f2ccb99d750787ea1235d7a9b..f471efc2fdc04002c34af872571031d086f784de 100644 (file)
@@ -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
     }
   ]
 }
index a8c5bd3df87bcc9f1e42368f0f5aaba37a512e54..4fe88b265bfb818bf88ff5d2569997f36be6e44a 100644 (file)
@@ -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 {
         "</ul>")
       .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<GroupDto> groups = findGroups(dbSession, query);
       int total = dbClient.groupPermissionDao().countGroupsByQuery(dbSession, query);
       List<GroupPermissionDto> groupsWithPermission = findGroupPermissions(dbSession, groups, project.orElse(null));
+      Map<String, Boolean> 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<String> getUserUuids(List<GroupDto> 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<GroupDto> groups, List<GroupPermissionDto> groupPermissions, Paging paging) {
+  private static WsGroupsResponse buildResponse(List<GroupDto> groups, List<GroupPermissionDto> groupPermissions,
+    Map<String, Boolean> groupUuidToIsManaged, Paging paging) {
     Multimap<String, String> 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()
index 12ba2fab6ebc28cc6ef867ea595401e937f06a56..8656986b1ee0c204c296219743a81fc743d0a83b 100644 (file)
@@ -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 {
         "</ul>")
       .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<UserPermissionDto> 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<String, Boolean> userUuidToIsManaged = managedInstanceService.getUserUuidToManaged(dbSession, getUserUuids(users));
+      UsersWsResponse usersWsResponse = buildResponse(users, userPermissions, userUuidToIsManaged, paging);
       writeProtobuf(usersWsResponse, request, response);
     }
   }
 
+  private static Set<String> getUserUuids(List<UserDto> 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<UserDto> users, List<UserPermissionDto> userPermissions, Paging paging) {
+  private UsersWsResponse buildResponse(List<UserDto> users, List<UserPermissionDto> userPermissions, Map<String, Boolean> userUuidToIsManaged,
+    Paging paging) {
     Multimap<String, String> 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()
index 42b3cc92b57d57758b68114d9db275c3651e3fcf..571e794f1f919a1cdd4b23143e8b6fdd7813056c 100644 (file)
@@ -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<String, String> groupsByLogin = dbClient.groupMembershipDao().selectGroupsByLogins(dbSession, logins);
       List<UserDto> users = dbClient.userDao().selectByOrderedLogins(dbSession, logins);
       Map<String, Integer> tokenCountsByLogin = dbClient.userTokenDao().countTokensByUsers(dbSession, users);
+      Map<String, Boolean> 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<UserDto> users, Multimap<String, String> groupsByLogin, Map<String, Integer> tokenCountsByLogin, Paging paging) {
+  private static Set<String> getUserUuids(List<UserDto> users) {
+    return users.stream().map(UserDto::getUuid).collect(Collectors.toSet());
+  }
+
+  private SearchWsResponse buildResponse(List<UserDto> users, Multimap<String, String> groupsByLogin, Map<String, Integer> tokenCountsByLogin,
+    Map<String, Boolean> 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<String> groups) {
+  private User towsUser(UserDto user, @Nullable Integer tokensCount, Collection<String> 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();
   }
index 7592a3d54d8b4ed1d3879231a9efcf5e7e12c913..19db3bd741f031afc236817fd561722f3c138665 100644 (file)
@@ -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<String> ALL_FIELDS = Arrays.asList(FIELD_NAME, FIELD_DESCRIPTION, FIELD_MEMBERS_COUNT);
+  private static final String FIELD_IS_MANAGED = "managed";
+  private static final List<String> 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<GroupDto> groups = dbClient.groupDao().selectByQuery(dbSession, query, options.getOffset(), pageSize);
       List<String> groupUuids = groups.stream().map(GroupDto::getUuid).collect(MoreCollectors.toList(groups.size()));
+      Map<String, Boolean> groupUuidToIsManaged = managedInstanceService.getGroupUuidToManaged(dbSession, new HashSet<>(groupUuids));
       Map<String, Integer> 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<GroupDto> groups, Map<String, Integer> userCountByGroup, Set<String> fields, Paging paging, GroupDto defaultGroup) {
+  private static SearchWsResponse buildResponse(List<GroupDto> groups, Map<String, Integer> userCountByGroup,
+    Map<String, Boolean> groupUuidToIsManaged, Set<String> 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<String> fields, boolean isDefault) {
+  private static Group toWsGroup(GroupDto group, Integer memberCount, Boolean isManaged, Set<String> 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();
   }
 
index 29aa783f329cb192a912b4b334418279158a695b..01fc9477cfd907dd08fa3af468abce734004096f 100644 (file)
@@ -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<UserMembershipDto> users = dbClient.groupMembershipDao().selectMembers(dbSession, query, paging.offset(), paging.pageSize());
-
+      Map<String, Boolean> 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<UserMembershipDto> users) {
+  private static Set<String> getUserUuids(List<UserMembershipDto> users) {
+    return users.stream().map(UserMembershipDto::getUuid).collect(toSet());
+  }
+
+  private static void writeMembers(JsonWriter json, List<UserMembershipDto> users, Map<String, Boolean> 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();
index 5e69f5b688fecbe212543b7ecfc1c56cbcb156ed..d06a93bc37cefe9cf9e5af52645845b36ea82cac 100644 (file)
       "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
     }
   ]
 }
index 3c4e357f1768f212b10dfc571bcb366deccf6d12..9d6718d69f6ec7b63485e37c995782f3f72d3e90 100644 (file)
       "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
     }
   ]
 }
index 60705dbb5acca9da52419233fef64ba5a1ee4f92..84e141b731f35b79eaf0011dc3e081fc52d082c5 100644 (file)
@@ -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
     }
   ]
 }
index 6cf71371022047e28029993fae30f48116e92509..a2bd06211c48689c078549f345939bac3b2d484b 100644 (file)
       "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
     }
   ]
 }
index 9b38c59393ebe2c12ac11152a7ae460fd9a1c74e..4d2243c463f52aad51d97f7e01b3bf96d17465ed 100644 (file)
@@ -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 (file)
index 0000000..54eb990
--- /dev/null
@@ -0,0 +1,17 @@
+{
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 20,
+    "total": 2
+  },
+  "groups": [
+    {
+      "name": "local-group",
+      "managed": false
+    },
+    {
+      "name": "managed-group",
+      "managed": true
+    }
+  ]
+}
index 53acf4d8ec4b9c012fe6e6bcfb35df1a317eb9f9..d236247354c4464fced7cbcee92cd379f1c57780 100644 (file)
@@ -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;
 }
index bb5e07a7921ddeb88821c5a0f7befc171f2a42a9..87846edf58d8d19af0c7b12668453554509b0836 100644 (file)
@@ -49,4 +49,5 @@ message Group {
   optional string description = 4;
   optional int32 membersCount = 5;
   optional bool default = 6;
+  optional bool managed = 7;
 }
index cac3168f8d67dba7bbd3f7417a1825eaf9fa793e..d8ba5a43868b78a80f91e42f30246f3838476adb 100644 (file)
@@ -44,6 +44,7 @@ message SearchWsResponse {
     optional string externalProvider = 10;
     optional string avatar = 11;
     optional string lastConnectionDate = 12;
+    optional bool managed = 13;
   }
 
   message Groups {