]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8192 set/unset root with admin permission change though WS
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 14 Oct 2016 15:48:19 +0000 (17:48 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Wed, 19 Oct 2016 12:45:13 +0000 (14:45 +0200)
this applies only to the admin permission of the default organization

23 files changed:
server/sonar-server/src/main/java/org/sonar/server/permission/GroupPermissionChanger.java
server/sonar-server/src/main/java/org/sonar/server/permission/UserPermissionChanger.java
server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/AddUserAction.java
server/sonar-server/src/main/java/org/sonar/server/usergroups/ws/RemoveUserAction.java
server/sonar-server/src/test/java/org/sonar/server/permission/GroupPermissionChangerTest.java
server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddGroupActionTest.java
server/sonar-server/src/test/java/org/sonar/server/permission/ws/AddUserActionTest.java
server/sonar-server/src/test/java/org/sonar/server/permission/ws/BasePermissionWsTest.java
server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java
server/sonar-server/src/test/java/org/sonar/server/permission/ws/RemoveUserActionTest.java
server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/AddUserActionTest.java
server/sonar-server/src/test/java/org/sonar/server/usergroups/ws/RemoveUserActionTest.java
sonar-db/src/main/java/org/sonar/db/user/GroupDao.java
sonar-db/src/main/java/org/sonar/db/user/GroupMapper.java
sonar-db/src/main/java/org/sonar/db/user/UserDao.java
sonar-db/src/main/java/org/sonar/db/user/UserMapper.java
sonar-db/src/main/resources/org/sonar/db/user/GroupMapper.xml
sonar-db/src/main/resources/org/sonar/db/user/UserMapper.xml
sonar-db/src/test/java/org/sonar/db/DbTester.java
sonar-db/src/test/java/org/sonar/db/user/GroupDaoTest.java
sonar-db/src/test/java/org/sonar/db/user/RootFlagAssertions.java [new file with mode: 0644]
sonar-db/src/test/java/org/sonar/db/user/UserDaoTest.java
sonar-db/src/test/java/org/sonar/db/user/UserDbTester.java

index 37a58cbed9ac4901530529cb78ab5912e8504157..6d31338c376983ea85beea79db7259a9c2eff124 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.permission.GroupPermissionDto;
 import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.organization.DefaultOrganizationProvider;
 
 import static java.lang.String.format;
 import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
@@ -33,9 +34,11 @@ import static org.sonar.server.permission.ws.PermissionRequestValidator.validate
 public class GroupPermissionChanger {
 
   private final DbClient dbClient;
+  private final DefaultOrganizationProvider defaultOrganizationProvider;
 
-  public GroupPermissionChanger(DbClient dbClient) {
+  public GroupPermissionChanger(DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider) {
     this.dbClient = dbClient;
+    this.defaultOrganizationProvider = defaultOrganizationProvider;
   }
 
   public boolean apply(DbSession dbSession, GroupPermissionChange change) {
@@ -61,6 +64,7 @@ public class GroupPermissionChanger {
       .setGroupId(change.getGroupIdOrAnyone().getId())
       .setResourceId(change.getNullableProjectId());
     dbClient.groupPermissionDao().insert(dbSession, addedDto);
+    updateRootFlag(dbSession, change);
     return true;
   }
 
@@ -74,9 +78,16 @@ public class GroupPermissionChanger {
       change.getOrganizationUuid(),
       change.getGroupIdOrAnyone().getId(),
       change.getNullableProjectId());
+    updateRootFlag(dbSession, change);
     return true;
   }
 
+  private void updateRootFlag(DbSession dbSession, GroupPermissionChange change) {
+    if (SYSTEM_ADMIN.equals(change.getPermission()) && !change.getGroupIdOrAnyone().isAnyone() &&  !change.getProjectId().isPresent()) {
+      dbClient.groupDao().updateRootFlagOfUsersInGroupFromPermissions(dbSession, change.getGroupIdOrAnyone().getId(), defaultOrganizationProvider.get().getUuid());
+    }
+  }
+
   private List<String> loadExistingPermissions(DbSession dbSession, GroupPermissionChange change) {
     Optional<ProjectId> projectId = change.getProjectId();
     if (projectId.isPresent()) {
index 5de1bd1521a0f572edbf525af7399e24c09860b4..782d2c503f96290d27d87da66d68e388449386ca 100644 (file)
@@ -21,19 +21,23 @@ package org.sonar.server.permission;
 
 import java.util.Optional;
 import java.util.Set;
-import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.permission.UserPermissionDto;
 import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.permission.PermissionChange.Operation;
 
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
+
 public class UserPermissionChanger {
 
   private final DbClient dbClient;
+  private final DefaultOrganizationProvider defaultOrganizationProvider;
 
-  public UserPermissionChanger(DbClient dbClient) {
+  public UserPermissionChanger(DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider) {
     this.dbClient = dbClient;
+    this.defaultOrganizationProvider = defaultOrganizationProvider;
   }
 
   public boolean apply(DbSession dbSession, UserPermissionChange change) {
@@ -58,6 +62,9 @@ public class UserPermissionChanger {
       default:
         throw new UnsupportedOperationException("Unsupported permission change: " + change.getOperation());
     }
+    if (SYSTEM_ADMIN.equals(change.getPermission()) && !change.getProjectId().isPresent()) {
+      dbClient.userDao().updateRootFlagFromPermissions(dbSession, change.getUserId().getId(), defaultOrganizationProvider.get().getUuid());
+    }
     return true;
   }
 
@@ -68,10 +75,10 @@ public class UserPermissionChanger {
   }
 
   private void checkOtherAdminUsersExist(DbSession session, PermissionChange change) {
-    if (GlobalPermissions.SYSTEM_ADMIN.equals(change.getPermission()) &&
+    if (SYSTEM_ADMIN.equals(change.getPermission()) &&
       !change.getProjectId().isPresent() &&
       dbClient.roleDao().countUserPermissions(session, change.getPermission(), null) <= 1) {
-      throw new BadRequestException(String.format("Last user with '%s' permission. Permission cannot be removed.", GlobalPermissions.SYSTEM_ADMIN));
+      throw new BadRequestException(String.format("Last user with '%s' permission. Permission cannot be removed.", SYSTEM_ADMIN));
     }
   }
 }
index bed079f51b37ca21bb0a4e71131b3c6d035f50b6..0d4c5ca07fe620508fecc6aee6ecdadfd83dfd5b 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.user.UserDto;
 import org.sonar.db.user.UserGroupDto;
+import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.user.UserSession;
 
 import static java.lang.String.format;
@@ -43,11 +44,13 @@ public class AddUserAction implements UserGroupsWsAction {
   private final DbClient dbClient;
   private final UserSession userSession;
   private final GroupWsSupport support;
+  private final DefaultOrganizationProvider defaultOrganizationProvider;
 
-  public AddUserAction(DbClient dbClient, UserSession userSession, GroupWsSupport support) {
+  public AddUserAction(DbClient dbClient, UserSession userSession, GroupWsSupport support, DefaultOrganizationProvider defaultOrganizationProvider) {
     this.dbClient = dbClient;
     this.userSession = userSession;
     this.support = support;
+    this.defaultOrganizationProvider = defaultOrganizationProvider;
   }
 
   @Override
@@ -77,6 +80,7 @@ public class AddUserAction implements UserGroupsWsAction {
       if (!isMemberOf(dbSession, user, groupId)) {
         UserGroupDto membershipDto = new UserGroupDto().setGroupId(groupId.getId()).setUserId(user.getId());
         dbClient.userGroupDao().insert(dbSession, membershipDto);
+        dbClient.userDao().updateRootFlagFromPermissions(dbSession, user.getId(), defaultOrganizationProvider.get().getUuid());
         dbSession.commit();
       }
 
index 995961517dd1fe80233af05ca29c1aa441bfc0ca..c36155099037d3a865e10f09f551f13b06b375ea 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.user.UserDto;
+import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.user.UserSession;
 
 import static java.lang.String.format;
@@ -42,11 +43,13 @@ public class RemoveUserAction implements UserGroupsWsAction {
   private final DbClient dbClient;
   private final UserSession userSession;
   private final GroupWsSupport support;
+  private final DefaultOrganizationProvider defaultOrganizationProvider;
 
-  public RemoveUserAction(DbClient dbClient, UserSession userSession, GroupWsSupport support) {
+  public RemoveUserAction(DbClient dbClient, UserSession userSession, GroupWsSupport support, DefaultOrganizationProvider defaultOrganizationProvider) {
     this.dbClient = dbClient;
     this.userSession = userSession;
     this.support = support;
+    this.defaultOrganizationProvider = defaultOrganizationProvider;
   }
 
   @Override
@@ -73,6 +76,7 @@ public class RemoveUserAction implements UserGroupsWsAction {
       UserDto user = getUser(dbSession, login);
 
       dbClient.userGroupDao().delete(dbSession, group.getId(), user.getId());
+      dbClient.userDao().updateRootFlagFromPermissions(dbSession, user.getId(), defaultOrganizationProvider.get().getUuid());
       dbSession.commit();
 
       response.noContent();
index d695b207f9bccf22c856003ffd31813fe51f6dbf..d807db7531ea0f38424d8f34a55b6aeb35cec53c 100644 (file)
@@ -34,6 +34,7 @@ import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.organization.TestDefaultOrganizationProvider;
 import org.sonar.server.usergroups.ws.GroupIdOrAnyone;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -42,11 +43,10 @@ public class GroupPermissionChangerTest {
 
   @Rule
   public DbTester db = DbTester.create(System2.INSTANCE);
-
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  private GroupPermissionChanger underTest = new GroupPermissionChanger(db.getDbClient());
+  private GroupPermissionChanger underTest = new GroupPermissionChanger(db.getDbClient(), TestDefaultOrganizationProvider.from(db));
   private OrganizationDto org;
   private GroupDto group;
   private ComponentDto project;
index 485b1dca342915f521d6bd938909259553c4c807..4c8948cb13033baf82b06db0d66d5db9a82f3582 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
@@ -165,10 +166,7 @@ public class AddGroupActionTest extends BasePermissionWsTest<AddGroupAction> {
 
     expectedException.expect(BadRequestException.class);
 
-    newRequest()
-      .setParam(PARAM_GROUP_NAME, group.getName())
-      .setParam(PARAM_PERMISSION, UserRole.ISSUE_ADMIN)
-      .execute();
+    executeRequest(group, UserRole.ISSUE_ADMIN);
   }
 
   @Test
@@ -231,8 +229,8 @@ public class AddGroupActionTest extends BasePermissionWsTest<AddGroupAction> {
     expectedException.expect(IllegalArgumentException.class);
 
     newRequest()
-        .setParam(PARAM_GROUP_NAME, group.getName())
-        .execute();
+      .setParam(PARAM_GROUP_NAME, group.getName())
+      .execute();
   }
 
   @Test
@@ -314,6 +312,76 @@ public class AddGroupActionTest extends BasePermissionWsTest<AddGroupAction> {
     assertThat(db.users().selectGroupPermissions(group, project)).containsOnly(ISSUE_ADMIN);
   }
 
+  @Test
+  public void set_root_flag_to_true_on_all_users_in_group_when_admin_permission_to_group_of_default_organization_without_org_param() throws Exception {
+    GroupDto group = db.users().insertGroup(db.getDefaultOrganization());
+    UserDto rootByUserPermissionUser = db.users().insertRootByUserPermission();
+    UserDto rootByGroupPermissionUser = db.users().insertRootByGroupPermission();
+    UserDto notRootUser = db.users().insertUser();
+    UserDto notInGroupUser = db.users().insertUser();
+    db.users().insertMembers(group, rootByUserPermissionUser, rootByGroupPermissionUser, notRootUser);
+    loginAsAdminOnDefaultOrganization();
+
+    executeRequest(group, SYSTEM_ADMIN);
+
+    db.rootFlag().verify(rootByUserPermissionUser, true);
+    db.rootFlag().verify(rootByGroupPermissionUser, true);
+    db.rootFlag().verify(notRootUser, true);
+    db.rootFlag().verifyUnchanged(notInGroupUser);
+  }
+
+  @Test
+  public void set_root_flag_to_true_on_all_users_in_group_when_admin_permission_to_group_of_default_organization_with_org_param() throws Exception {
+    GroupDto group = db.users().insertGroup(db.getDefaultOrganization());
+    UserDto rootByUserPermissionUser = db.users().insertRootByUserPermission();
+    UserDto rootByGroupPermissionUser = db.users().insertRootByGroupPermission();
+    UserDto notRootUser = db.users().insertUser();
+    UserDto notInGroupUser = db.users().insertUser();
+    db.users().insertMembers(group, rootByUserPermissionUser, rootByGroupPermissionUser, notRootUser);
+    loginAsAdminOnDefaultOrganization();
+
+    executeRequest(group, db.getDefaultOrganization(), SYSTEM_ADMIN);
+
+    db.rootFlag().verify(rootByUserPermissionUser, true);
+    db.rootFlag().verify(rootByGroupPermissionUser, true);
+    db.rootFlag().verify(notRootUser, true);
+    db.rootFlag().verifyUnchanged(notInGroupUser);
+  }
+
+  @Test
+  public void does_not_set_root_flag_to_true_on_all_users_in_group_when_admin_permission_to_group_of_default_organization() throws Exception {
+    OrganizationDto otherOrganization = db.organizations().insert();
+    GroupDto group = db.users().insertGroup(otherOrganization);
+    UserDto rootByUserPermissionUser = db.users().insertRootByUserPermission();
+    UserDto rootByGroupPermissionUser = db.users().insertRootByGroupPermission();
+    UserDto notRootUser = db.users().insertUser();
+    UserDto notInGroupUser = db.users().insertUser();
+    db.users().insertMembers(group, rootByUserPermissionUser, rootByGroupPermissionUser, notRootUser);
+    loginAsAdmin(otherOrganization);
+
+    executeRequest(group, otherOrganization, SYSTEM_ADMIN);
+
+    db.rootFlag().verify(rootByUserPermissionUser, true);
+    db.rootFlag().verify(rootByGroupPermissionUser, true);
+    db.rootFlag().verify(notRootUser, false);
+    db.rootFlag().verifyUnchanged(notInGroupUser);
+  }
+
+  private void executeRequest(GroupDto groupDto, OrganizationDto organizationDto, String permission) throws Exception {
+    newRequest()
+      .setParam(PARAM_GROUP_NAME, groupDto.getName())
+      .setParam(PARAM_PERMISSION, permission)
+      .setParam(PARAM_ORGANIZATION_KEY, organizationDto.getKey())
+      .execute();
+  }
+
+  private void executeRequest(GroupDto groupDto, String permission) throws Exception {
+    newRequest()
+      .setParam(PARAM_GROUP_NAME, groupDto.getName())
+      .setParam(PARAM_PERMISSION, permission)
+      .execute();
+  }
+
   private WsTester.TestRequest newRequest() {
     return wsTester.newPostRequest(CONTROLLER, ACTION);
   }
index 0cea454d95687206a915b66745c6aedb19b1729f..c51a3c2a508fbcabc3fb28f703a7017055fbab58 100644 (file)
  */
 package org.sonar.server.permission.ws;
 
+import javax.annotation.Nullable;
 import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.web.UserRole;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.exceptions.ServerException;
+import org.sonar.server.ws.WsTester;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
@@ -37,6 +40,7 @@ import static org.sonar.db.component.ComponentTesting.newProjectDto;
 import static org.sonar.db.component.ComponentTesting.newView;
 import static org.sonar.server.permission.ws.AddUserAction.ACTION;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.CONTROLLER;
+import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_ORGANIZATION_KEY;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PERMISSION;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_ID;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_KEY;
@@ -244,4 +248,89 @@ public class AddUserActionTest extends BasePermissionWsTest<AddUserAction> {
 
     assertThat(db.users().selectUserPermissions(user, project)).containsOnly(ISSUE_ADMIN);
   }
+
+  @Test
+  public void sets_root_flag_to_true_when_adding_user_admin_permission_without_org_parameter() throws Exception {
+    UserDto rootByUserPermissionUser = db.users().insertRootByUserPermission();
+    UserDto rootByGroupPermissionUser = db.users().insertRootByGroupPermission();
+    UserDto notRootUser = db.users().insertUser();
+    loginAsAdminOnDefaultOrganization();
+
+    executeRequest(notRootUser, SYSTEM_ADMIN);
+    db.rootFlag().verify(notRootUser, true);
+    db.rootFlag().verifyUnchanged(rootByUserPermissionUser);
+    db.rootFlag().verifyUnchanged(rootByGroupPermissionUser);
+
+    executeRequest(rootByUserPermissionUser, SYSTEM_ADMIN);
+    db.rootFlag().verify(notRootUser, true);
+    db.rootFlag().verifyUnchanged(rootByUserPermissionUser); // because already has specified user permission
+    db.rootFlag().verifyUnchanged(rootByGroupPermissionUser);
+
+    executeRequest(rootByGroupPermissionUser, SYSTEM_ADMIN);
+    db.rootFlag().verify(notRootUser, true);
+    db.rootFlag().verifyUnchanged(rootByUserPermissionUser);
+    db.rootFlag().verify(rootByGroupPermissionUser, true);
+  }
+
+  @Test
+  public void sets_root_flag_to_true_when_adding_user_admin_permission_with_default_organization_uuid() throws Exception {
+    UserDto rootByUserPermissionUser = db.users().insertRootByUserPermission();
+    UserDto rootByGroupPermissionUser = db.users().insertRootByGroupPermission();
+    UserDto notRootUser = db.users().insertUser();
+    loginAsAdminOnDefaultOrganization();
+
+    executeRequest(notRootUser, SYSTEM_ADMIN, db.getDefaultOrganization());
+    db.rootFlag().verify(notRootUser, true);
+    db.rootFlag().verifyUnchanged(rootByUserPermissionUser);
+    db.rootFlag().verifyUnchanged(rootByGroupPermissionUser);
+
+    executeRequest(rootByUserPermissionUser, SYSTEM_ADMIN, db.getDefaultOrganization());
+    db.rootFlag().verify(notRootUser, true);
+    db.rootFlag().verifyUnchanged(rootByUserPermissionUser); // because already has specified user permission
+    db.rootFlag().verifyUnchanged(rootByGroupPermissionUser);
+
+    executeRequest(rootByGroupPermissionUser, SYSTEM_ADMIN, db.getDefaultOrganization());
+    db.rootFlag().verify(notRootUser, true);
+    db.rootFlag().verifyUnchanged(rootByUserPermissionUser);
+    db.rootFlag().verify(rootByGroupPermissionUser, true);
+  }
+
+  @Test
+  public void does_not_set_root_flag_when_adding_user_admin_permission_with_other_organization_uuid() throws Exception {
+    OrganizationDto otherOrganization = db.organizations().insert();
+    UserDto rootByUserPermissionUser = db.users().insertRootByUserPermission();
+    UserDto rootByGroupPermissionUser = db.users().insertRootByGroupPermission();
+    UserDto notRootUser = db.users().insertUser();
+    loginAsAdmin(otherOrganization);
+
+    executeRequest(notRootUser, SYSTEM_ADMIN, otherOrganization);
+    db.rootFlag().verify(notRootUser, false);
+    db.rootFlag().verifyUnchanged(rootByUserPermissionUser);
+    db.rootFlag().verifyUnchanged(rootByGroupPermissionUser);
+
+    executeRequest(rootByUserPermissionUser, SYSTEM_ADMIN, otherOrganization);
+    db.rootFlag().verify(notRootUser, false);
+    db.rootFlag().verifyUnchanged(rootByUserPermissionUser); // because already has specified permission
+    db.rootFlag().verifyUnchanged(rootByGroupPermissionUser);
+
+    executeRequest(rootByGroupPermissionUser, SYSTEM_ADMIN, otherOrganization);
+    db.rootFlag().verify(notRootUser, false);
+    db.rootFlag().verifyUnchanged(rootByUserPermissionUser);
+    db.rootFlag().verify(rootByGroupPermissionUser, true);
+  }
+
+  private void executeRequest(UserDto userDto, String permission) throws Exception {
+    executeRequest(userDto, permission, null);
+  }
+
+  private void executeRequest(UserDto userDto, String permission, @Nullable OrganizationDto organizationDto) throws Exception {
+    WsTester.TestRequest request = wsTester.newPostRequest(CONTROLLER, ACTION)
+      .setParam(PARAM_USER_LOGIN, userDto.getLogin())
+      .setParam(PARAM_PERMISSION, permission);
+    if (organizationDto != null) {
+      request.setParam(PARAM_ORGANIZATION_KEY, organizationDto.getKey());
+    }
+    request.execute();
+  }
+
 }
index 972e517d7fc3606d07ddc4d1036f3fb78d92ca78..a1060ed43fa6fa757f68e321820e0c4ef5f0e5bb 100644 (file)
@@ -78,8 +78,8 @@ public abstract class BasePermissionWsTest<A extends PermissionsWsAction> {
   protected PermissionUpdater newPermissionUpdater() {
     return new PermissionUpdater(db.getDbClient(),
       mock(IssueAuthorizationIndexer.class),
-      new UserPermissionChanger(db.getDbClient()),
-      new GroupPermissionChanger(db.getDbClient()));
+      new UserPermissionChanger(db.getDbClient(), defaultOrganizationProvider),
+      new GroupPermissionChanger(db.getDbClient(), defaultOrganizationProvider));
   }
 
   protected PermissionTemplateDto insertTemplate() {
index 44b9fe62373930f2092318e30e6ee2348ab17ecd..bd4b7f6f1cb4d6db1a0f76d3267750234e459c04 100644 (file)
@@ -23,7 +23,9 @@ import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.web.UserRole;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDto;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
@@ -43,6 +45,7 @@ import static org.sonar.server.permission.ws.RemoveGroupAction.ACTION;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.CONTROLLER;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_GROUP_ID;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_GROUP_NAME;
+import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_ORGANIZATION_KEY;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PERMISSION;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_ID;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_KEY;
@@ -152,10 +155,7 @@ public class RemoveGroupActionTest extends BasePermissionWsTest<RemoveGroupActio
     expectedException.expect(BadRequestException.class);
     expectedException.expectMessage("Last group with permission 'admin'. Permission cannot be removed.");
 
-    newRequest()
-      .setParam(PARAM_GROUP_NAME, aGroup.getName())
-      .setParam(PARAM_PERMISSION, SYSTEM_ADMIN)
-      .execute();
+    executeRequest(aGroup, SYSTEM_ADMIN);
   }
 
   @Test
@@ -179,10 +179,7 @@ public class RemoveGroupActionTest extends BasePermissionWsTest<RemoveGroupActio
     expectedException.expect(BadRequestException.class);
     expectedException.expectMessage("Invalid global permission 'issueadmin'. Valid values are [admin, profileadmin, gateadmin, scan, provisioning]");
 
-    newRequest()
-      .setParam(PARAM_GROUP_NAME, aGroup.getName())
-      .setParam(PARAM_PERMISSION, ISSUE_ADMIN)
-      .execute();
+    executeRequest(aGroup, ISSUE_ADMIN);
   }
 
   @Test
@@ -266,6 +263,72 @@ public class RemoveGroupActionTest extends BasePermissionWsTest<RemoveGroupActio
       .execute();
   }
 
+  @Test
+  public void sets_root_flag_to_false_on_all_users_in_group_when_removing_admin_permission_from_group_of_default_organization_without_org_param() throws Exception {
+    UserDto lastAdminUser = db.users().insertRootByUserPermission();
+    GroupDto adminGroup = db.users().insertAdminGroup();
+    UserDto user1 = db.users().insertRootByGroupPermission("user1", adminGroup);
+    UserDto user2 = db.users().insertRootByGroupPermission("user2", adminGroup);
+    loginAsAdminOnDefaultOrganization();
+
+    executeRequest(adminGroup, SYSTEM_ADMIN);
+
+    db.rootFlag().verify(user1, false);
+    db.rootFlag().verify(user2, false);
+    db.rootFlag().verifyUnchanged(lastAdminUser);
+  }
+
+  @Test
+  public void sets_root_flag_to_false_on_all_users_in_group_when_removing_admin_permission_from_group_of_default_organization_with_org_param() throws Exception {
+    UserDto lastAdminUser = db.users().insertRootByUserPermission();
+    GroupDto adminGroup = db.users().insertAdminGroup();
+    UserDto user1 = db.users().insertRootByGroupPermission("user1", adminGroup);
+    UserDto user2 = db.users().insertRootByGroupPermission("user2", adminGroup);
+    loginAsAdminOnDefaultOrganization();
+
+    executeRequest(adminGroup, db.getDefaultOrganization(), SYSTEM_ADMIN);
+
+    db.rootFlag().verify(user1, false);
+    db.rootFlag().verify(user2, false);
+    db.rootFlag().verifyUnchanged(lastAdminUser);
+  }
+
+  @Test
+  public void does_not_set_root_flag_to_false_on_all_users_in_group_when_removing_admin_permission_from_group_of_other_organization() throws Exception {
+    OrganizationDto otherOrganization = db.organizations().insert();
+    UserDto lastAdmin = db.users().insertUser();
+    db.users().insertPermissionOnUser(otherOrganization, lastAdmin, SYSTEM_ADMIN);
+    GroupDto adminGroup = db.users().insertAdminGroup(otherOrganization);
+    UserDto rootByUserPermissionUser = db.users().insertRootByUserPermission();
+    UserDto rootByGroupPermissionUser = db.users().insertRootByGroupPermission();
+    UserDto inAdminGroupUser = db.users().insertUser();
+    UserDto notInGroupUser = db.users().insertUser();
+    db.users().insertMembers(adminGroup, rootByUserPermissionUser, rootByGroupPermissionUser, inAdminGroupUser);
+    loginAsAdmin(otherOrganization);
+
+    executeRequest(adminGroup, otherOrganization, SYSTEM_ADMIN);
+
+    db.rootFlag().verify(rootByUserPermissionUser, true);
+    db.rootFlag().verify(rootByGroupPermissionUser, true);
+    db.rootFlag().verify(inAdminGroupUser, false);
+    db.rootFlag().verifyUnchanged(notInGroupUser);
+  }
+
+  private void executeRequest(GroupDto groupDto, String permission) throws Exception {
+    newRequest()
+        .setParam(PARAM_GROUP_NAME, groupDto.getName())
+        .setParam(PARAM_PERMISSION, permission)
+        .execute();
+  }
+
+  private void executeRequest(GroupDto groupDto, OrganizationDto organizationDto, String permission) throws Exception {
+    newRequest()
+        .setParam(PARAM_GROUP_NAME, groupDto.getName())
+        .setParam(PARAM_PERMISSION, permission)
+        .setParam(PARAM_ORGANIZATION_KEY, organizationDto.getKey())
+        .execute();
+  }
+
   @Test
   public void removing_global_permission_fails_if_not_administrator_of_organization() throws Exception {
     userSession.login();
index cf4449dac817150217de4813837ea49579223674..5f2efcfedc2dda3d1869580ced402aa8038418ca 100644 (file)
@@ -42,6 +42,7 @@ import static org.sonar.db.component.ComponentTesting.newProjectDto;
 import static org.sonar.db.component.ComponentTesting.newView;
 import static org.sonar.server.permission.ws.RemoveUserAction.ACTION;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.CONTROLLER;
+import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_ORGANIZATION_KEY;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PERMISSION;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_ID;
 import static org.sonarqube.ws.client.permission.PermissionsWsParameters.PARAM_PROJECT_KEY;
@@ -69,7 +70,7 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
   public void remove_permission_from_user() throws Exception {
     db.users().insertPermissionOnUser(user, PROVISIONING);
     db.users().insertPermissionOnUser(user, QUALITY_GATE_ADMIN);
-    loginAsAdmin();
+    loginAsAdminOnDefaultOrganization();
 
     wsTester.newPostRequest(CONTROLLER, ACTION)
       .setParam(PARAM_USER_LOGIN, user.getLogin())
@@ -83,7 +84,7 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
   public void fail_to_remove_admin_permission_if_last_admin() throws Exception {
     db.users().insertPermissionOnUser(user, CODEVIEWER);
     db.users().insertPermissionOnUser(user, ADMIN);
-    loginAsAdmin();
+    loginAsAdminOnDefaultOrganization();
 
     expectedException.expect(BadRequestException.class);
     expectedException.expectMessage("Last user with 'admin' permission. Permission cannot be removed.");
@@ -99,7 +100,7 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
     ComponentDto project = db.components().insertComponent(newProjectDto(A_PROJECT_UUID).setKey(A_PROJECT_KEY));
     db.users().insertProjectPermissionOnUser(user, CODEVIEWER, project);
     db.users().insertProjectPermissionOnUser(user, ISSUE_ADMIN, project);
-    loginAsAdmin();
+    loginAsAdminOnDefaultOrganization();
 
     wsTester.newPostRequest(CONTROLLER, ACTION)
       .setParam(PARAM_USER_LOGIN, user.getLogin())
@@ -115,7 +116,7 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
     ComponentDto project = db.components().insertComponent(newProjectDto(A_PROJECT_UUID).setKey(A_PROJECT_KEY));
     db.users().insertProjectPermissionOnUser(user, ISSUE_ADMIN, project);
     db.users().insertProjectPermissionOnUser(user, CODEVIEWER, project);
-    loginAsAdmin();
+    loginAsAdminOnDefaultOrganization();
 
     wsTester.newPostRequest(CONTROLLER, ACTION)
       .setParam(PARAM_USER_LOGIN, user.getLogin())
@@ -131,7 +132,7 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
     ComponentDto view = db.components().insertComponent(newView("view-uuid").setKey("view-key"));
     db.users().insertProjectPermissionOnUser(user, ISSUE_ADMIN, view);
     db.users().insertProjectPermissionOnUser(user, CODEVIEWER, view);
-    loginAsAdmin();
+    loginAsAdminOnDefaultOrganization();
 
     wsTester.newPostRequest(CONTROLLER, ACTION)
       .setParam(PARAM_USER_LOGIN, user.getLogin())
@@ -144,7 +145,7 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
 
   @Test
   public void fail_when_project_does_not_exist() throws Exception {
-    loginAsAdmin();
+    loginAsAdminOnDefaultOrganization();
 
     expectedException.expect(NotFoundException.class);
 
@@ -157,7 +158,7 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
 
   @Test
   public void fail_when_project_permission_without_permission() throws Exception {
-    loginAsAdmin();
+    loginAsAdminOnDefaultOrganization();
 
     expectedException.expect(BadRequestException.class);
 
@@ -170,7 +171,7 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
   @Test
   public void fail_when_component_is_not_a_project() throws Exception {
     db.components().insertComponent(newFileDto(newProjectDto(), null, "file-uuid"));
-    loginAsAdmin();
+    loginAsAdminOnDefaultOrganization();
 
     expectedException.expect(BadRequestException.class);
 
@@ -183,7 +184,7 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
 
   @Test
   public void fail_when_get_request() throws Exception {
-    loginAsAdmin();
+    loginAsAdminOnDefaultOrganization();
 
     expectedException.expect(ServerException.class);
 
@@ -195,7 +196,7 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
 
   @Test
   public void fail_when_user_login_is_missing() throws Exception {
-    loginAsAdmin();
+    loginAsAdminOnDefaultOrganization();
 
     expectedException.expect(IllegalArgumentException.class);
 
@@ -206,7 +207,7 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
 
   @Test
   public void fail_when_permission_is_missing() throws Exception {
-    loginAsAdmin();
+    loginAsAdminOnDefaultOrganization();
 
     expectedException.expect(IllegalArgumentException.class);
 
@@ -218,7 +219,7 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
   @Test
   public void fail_when_project_uuid_and_project_key_are_provided() throws Exception {
     ComponentDto project = db.components().insertComponent(newProjectDto(A_PROJECT_UUID).setKey(A_PROJECT_KEY));
-    loginAsAdmin();
+    loginAsAdminOnDefaultOrganization();
 
     expectedException.expect(BadRequestException.class);
     expectedException.expectMessage("Project id or project key can be provided, not both.");
@@ -231,6 +232,61 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
       .execute();
   }
 
+  @Test
+  public void sets_root_flag_to_false_when_removing_user_admin_permission_of_default_organization_without_org_parameter() throws Exception {
+    UserDto lastAdminUser = db.users().insertRootByUserPermission();
+    UserDto adminUser = db.users().insertRootByUserPermission();
+    loginAsAdminOnDefaultOrganization();
+
+    executeRequest(adminUser, SYSTEM_ADMIN);
+
+    db.rootFlag().verify(adminUser, false);
+  }
+
+  @Test
+  public void sets_root_flag_to_false_when_removing_user_admin_permission_of_default_organization_with_org_parameter() throws Exception {
+    UserDto lastAdminUser = db.users().insertRootByUserPermission();
+    UserDto adminUser = db.users().insertRootByUserPermission();
+    loginAsAdminOnDefaultOrganization();
+
+    executeRequest(adminUser, db.getDefaultOrganization(), SYSTEM_ADMIN);
+
+    db.rootFlag().verify(adminUser, false);
+  }
+
+  @Test
+  public void does_not_set_root_flag_to_false_when_removing_user_admin_permission_of_other_organization() throws Exception {
+    UserDto rootUser = db.users().insertRootByUserPermission();
+    UserDto notRootUser = db.users().insertUser();
+    OrganizationDto otherOrganization = db.organizations().insert();
+    db.users().insertPermissionOnUser(otherOrganization, rootUser, SYSTEM_ADMIN);
+    db.users().insertPermissionOnUser(otherOrganization, notRootUser, SYSTEM_ADMIN);
+    loginAsAdmin(otherOrganization);
+
+    executeRequest(rootUser, otherOrganization, SYSTEM_ADMIN);
+    db.rootFlag().verify(rootUser, true);
+    db.rootFlag().verifyUnchanged(notRootUser);
+
+    executeRequest(notRootUser, otherOrganization, SYSTEM_ADMIN);
+    db.rootFlag().verify(rootUser, true);
+    db.rootFlag().verify(notRootUser, false);
+  }
+
+  private void executeRequest(UserDto userDto, OrganizationDto organizationDto, String permission) throws Exception {
+    wsTester.newPostRequest(CONTROLLER, ACTION)
+      .setParam(PARAM_USER_LOGIN, userDto.getLogin())
+      .setParam(PARAM_PERMISSION, permission)
+      .setParam(PARAM_ORGANIZATION_KEY, organizationDto.getKey())
+      .execute();
+  }
+
+  private void executeRequest(UserDto userDto, String permission) throws Exception {
+    wsTester.newPostRequest(CONTROLLER, ACTION)
+      .setParam(PARAM_USER_LOGIN, userDto.getLogin())
+      .setParam(PARAM_PERMISSION, permission)
+      .execute();
+  }
+
   @Test
   public void removing_global_permission_fails_if_not_administrator_of_organization() throws Exception {
     userSession.login();
@@ -276,11 +332,4 @@ public class RemoveUserActionTest extends BasePermissionWsTest<RemoveUserAction>
     assertThat(db.users().selectUserPermissions(user, project)).containsOnly(CODEVIEWER);
   }
 
-  private void loginAsAdmin() {
-    loginAsOrganizationAdmin(db.getDefaultOrganization());
-  }
-
-  private void loginAsOrganizationAdmin(OrganizationDto org) {
-    userSession.login().addOrganizationPermission(org.getUuid(), SYSTEM_ADMIN);
-  }
 }
index ab371c4d8616fc755b1295a82b0e034000ea5838..a41172ebf18dcc6d18e4bc7ac0a764570372400f 100644 (file)
  */
 package org.sonar.server.usergroups.ws;
 
+import java.util.List;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.sonar.api.utils.System2;
-import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.db.DbTester;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserMembershipDto;
+import org.sonar.db.user.UserMembershipQuery;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.exceptions.UnauthorizedException;
@@ -37,6 +39,7 @@ import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsTester;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
 import static org.sonar.server.usergroups.ws.GroupWsSupport.PARAM_GROUP_NAME;
 import static org.sonar.server.usergroups.ws.GroupWsSupport.PARAM_LOGIN;
 import static org.sonar.server.usergroups.ws.GroupWsSupport.PARAM_ORGANIZATION_KEY;
@@ -55,7 +58,7 @@ public class AddUserActionTest {
 
   @Before
   public void setUp() {
-    ws = new WsTester(new UserGroupsWs(new AddUserAction(db.getDbClient(), userSession, newGroupWsSupport())));
+    ws = new WsTester(new UserGroupsWs(new AddUserAction(db.getDbClient(), userSession, newGroupWsSupport(), defaultOrganizationProvider)));
   }
 
   @Test
@@ -146,8 +149,8 @@ public class AddUserActionTest {
     UserDto user1 = db.users().insertUser();
     UserDto user2 = db.users().insertUser();
     db.users().insertMember(users, user1);
-
     loginAsAdminOnDefaultOrganization();
+
     newRequest()
       .setParam("id", users.getId().toString())
       .setParam("login", user2.getLogin())
@@ -161,10 +164,10 @@ public class AddUserActionTest {
   @Test
   public void fail_if_group_does_not_exist() throws Exception {
     UserDto user = db.users().insertUser();
+    loginAsAdminOnDefaultOrganization();
 
     expectedException.expect(NotFoundException.class);
 
-    loginAsAdminOnDefaultOrganization();
     newRequest()
       .setParam("id", "42")
       .setParam("login", user.getLogin())
@@ -174,10 +177,10 @@ public class AddUserActionTest {
   @Test
   public void fail_if_user_does_not_exist() throws Exception {
     GroupDto group = db.users().insertGroup(db.getDefaultOrganization(), "admins");
+    loginAsAdminOnDefaultOrganization();
 
     expectedException.expect(NotFoundException.class);
 
-    loginAsAdminOnDefaultOrganization();
     newRequest()
       .setParam("id", group.getId().toString())
       .setParam("login", "my-admin")
@@ -191,9 +194,54 @@ public class AddUserActionTest {
 
     expectedException.expect(UnauthorizedException.class);
 
+    executeRequest(group, user);
+  }
+
+  @Test
+  public void set_root_flag_to_true_when_adding_user_to_group_of_default_organization_with_admin_permission() throws Exception {
+    GroupDto group = db.users().insertAdminGroup();
+    UserDto falselyRootUser = db.users().makeRoot(db.users().insertUser("falselyRootUser"));
+    UserDto notRootUser = db.users().insertUser("notRootUser");
+    loginAsAdminOnDefaultOrganization();
+
+    executeRequest(group, falselyRootUser);
+    verifyUserInGroup(falselyRootUser, group);
+    db.rootFlag().verify(falselyRootUser, true);
+    verifyUserNotInGroup(notRootUser, group);
+    db.rootFlag().verifyUnchanged(notRootUser);
+
+    executeRequest(group, notRootUser);
+    verifyUserInGroup(falselyRootUser, group);
+    db.rootFlag().verify(falselyRootUser, true);
+    verifyUserInGroup(notRootUser, group);
+    db.rootFlag().verify(notRootUser, true);
+  }
+
+  @Test
+  public void does_not_set_root_flag_to_true_when_adding_user_to_group_of_other_organization_with_admin_permission() throws Exception {
+    OrganizationDto otherOrganization = db.organizations().insert();
+    GroupDto group = db.users().insertAdminGroup(otherOrganization);
+    UserDto falselyRootUser = db.users().makeRoot(db.users().insertUser("falselyRootUser"));
+    UserDto notRootUser = db.users().insertUser("notRootUser");
+    loginAsAdmin(otherOrganization);
+
+    executeRequest(group, falselyRootUser);
+    verifyUserInGroup(falselyRootUser, group);
+    db.rootFlag().verify(falselyRootUser, false);
+    verifyUserNotInGroup(notRootUser, group);
+    db.rootFlag().verifyUnchanged(notRootUser);
+
+    executeRequest(group, notRootUser);
+    verifyUserInGroup(falselyRootUser, group);
+    db.rootFlag().verify(falselyRootUser, false);
+    verifyUserInGroup(notRootUser, group);
+    db.rootFlag().verify(notRootUser, false);
+  }
+
+  private void executeRequest(GroupDto groupDto, UserDto userDto) throws Exception {
     newRequest()
-      .setParam("id", group.getId().toString())
-      .setParam("login", user.getLogin())
+      .setParam("id", groupDto.getId().toString())
+      .setParam("login", userDto.getLogin())
       .execute();
   }
 
@@ -223,11 +271,31 @@ public class AddUserActionTest {
   }
 
   private void loginAsAdmin(OrganizationDto org) {
-    userSession.login().addOrganizationPermission(org.getUuid(), GlobalPermissions.SYSTEM_ADMIN);
+    userSession.login().addOrganizationPermission(org.getUuid(), SYSTEM_ADMIN);
   }
 
   private GroupWsSupport newGroupWsSupport() {
     return new GroupWsSupport(db.getDbClient(), defaultOrganizationProvider);
   }
 
+  private void verifyUserInGroup(UserDto userDto, GroupDto groupDto) {
+    assertThat(isUserInGroup(userDto, groupDto))
+      .as("user '%s' is a member of group '%s' of organization '%s'", userDto.getLogin(), groupDto.getName(), groupDto.getOrganizationUuid())
+      .isTrue();
+  }
+
+  private void verifyUserNotInGroup(UserDto userDto, GroupDto groupDto) {
+    assertThat(isUserInGroup(userDto, groupDto))
+      .as("user '%s' is not a member of group '%s' of organization '%s'", userDto.getLogin(), groupDto.getName(), groupDto.getOrganizationUuid())
+      .isFalse();
+  }
+
+  private boolean isUserInGroup(UserDto userDto, GroupDto groupDto) {
+    List<UserMembershipDto> members = db.getDbClient().groupMembershipDao()
+      .selectMembers(db.getSession(), UserMembershipQuery.builder().groupId(groupDto.getId()).membership(UserMembershipQuery.IN).build(), 0, Integer.MAX_VALUE);
+    return members
+      .stream()
+      .anyMatch(dto -> dto.getLogin().equals(userDto.getLogin()));
+  }
+
 }
index c09e10f2b31ac002371f30e8f15b10c4897a39d1..47b73fac5096160254b2204ce0f2271334caf4cb 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.usergroups.ws;
 
+import java.util.List;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -29,6 +30,8 @@ import org.sonar.db.DbTester;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.user.GroupDto;
 import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserMembershipDto;
+import org.sonar.db.user.UserMembershipQuery;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.organization.TestDefaultOrganizationProvider;
 import org.sonar.server.tester.UserSessionRule;
@@ -54,7 +57,7 @@ public class RemoveUserActionTest {
   @Before
   public void setUp() {
     GroupWsSupport groupSupport = new GroupWsSupport(db.getDbClient(), defaultOrganizationProvider);
-    ws = new WsTester(new UserGroupsWs(new RemoveUserAction(db.getDbClient(), userSession, groupSupport)));
+    ws = new WsTester(new UserGroupsWs(new RemoveUserAction(db.getDbClient(), userSession, groupSupport, defaultOrganizationProvider)));
   }
 
   @Test
@@ -167,6 +170,72 @@ public class RemoveUserActionTest {
       .execute();
   }
 
+  @Test
+  public void sets_root_flag_to_false_when_removing_user_from_group_of_default_organization_with_admin_permission() throws Exception {
+    GroupDto adminGroup = db.users().insertAdminGroup();
+    UserDto user1 = db.users().insertRootByGroupPermission(adminGroup);
+    UserDto user2 = db.users().insertRootByGroupPermission(adminGroup);
+    loginAsAdmin();
+
+    executeRequest(adminGroup, user1);
+    verifyUserNotInGroup(user1, adminGroup);
+    verifyRootFlagUpdated(user1, false);
+    verifyUserInGroup(user2, adminGroup);
+    verifyUnchanged(user2);
+
+    executeRequest(adminGroup, user2);
+    verifyUserNotInGroup(user1, adminGroup);
+    verifyRootFlag(user1, false);
+    verifyUserNotInGroup(user2, adminGroup);
+    verifyRootFlagUpdated(user2, false);
+  }
+
+  @Test
+  public void does_not_set_root_flag_to_false_when_removing_user_from_group_of_default_organization_and_user_is_admin_of_default_organization_another_way()
+    throws Exception {
+    GroupDto adminGroup1 = db.users().insertAdminGroup();
+    UserDto adminUserByUserPermission = db.users().insertRootByUserPermission("adminUserByUserPermission");
+    UserDto adminUserByTwoGroups = db.users().insertRootByGroupPermission("adminUserByTwoGroups", adminGroup1);
+    UserDto adminUserBySingleGroup = db.users().insertUser("adminUserBySingleGroup");
+    GroupDto adminGroup2 = db.users().insertAdminGroup();
+    db.users().insertMembers(adminGroup2, adminUserByUserPermission, adminUserByTwoGroups, adminUserBySingleGroup);
+    loginAsAdmin();
+
+    executeRequest(adminGroup2, adminUserByUserPermission);
+    verifyUserNotInGroup(adminUserByUserPermission, adminGroup2);
+    verifyRootFlagUpdated(adminUserByUserPermission, true);
+    verifyUserInGroup(adminUserByTwoGroups, adminGroup2);
+    verifyUserInGroup(adminUserByTwoGroups, adminGroup1);
+    verifyUnchanged(adminUserByTwoGroups);
+    verifyUserInGroup(adminUserBySingleGroup, adminGroup2);
+    verifyUnchanged(adminUserBySingleGroup);
+
+    executeRequest(adminGroup2, adminUserByTwoGroups);
+    verifyUserNotInGroup(adminUserByUserPermission, adminGroup2);
+    verifyRootFlag(adminUserByUserPermission, true);
+    verifyUserNotInGroup(adminUserByTwoGroups, adminGroup2);
+    verifyUserInGroup(adminUserByTwoGroups, adminGroup1);
+    verifyRootFlagUpdated(adminUserByTwoGroups, true);
+    verifyUserInGroup(adminUserBySingleGroup, adminGroup2);
+    verifyUnchanged(adminUserBySingleGroup);
+
+    executeRequest(adminGroup2, adminUserBySingleGroup);
+    verifyUserNotInGroup(adminUserByUserPermission, adminGroup2);
+    verifyRootFlag(adminUserByUserPermission, true);
+    verifyUserNotInGroup(adminUserByTwoGroups, adminGroup2);
+    verifyUserInGroup(adminUserByTwoGroups, adminGroup1);
+    verifyRootFlagUpdated(adminUserByTwoGroups, true);
+    verifyUserNotInGroup(adminUserBySingleGroup, adminGroup2);
+    verifyRootFlagUpdated(adminUserBySingleGroup, false);
+  }
+
+  private void executeRequest(GroupDto group, UserDto user) throws Exception {
+    newRequest()
+      .setParam("id", group.getId().toString())
+      .setParam("login", user.getLogin())
+      .execute();
+  }
+
   private WsTester.TestRequest newRequest() {
     return ws.newPostRequest("api/user_groups", "remove_user");
   }
@@ -175,4 +244,36 @@ public class RemoveUserActionTest {
     userSession.login("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
   }
 
+  private void verifyUnchanged(UserDto user) {
+    db.rootFlag().verifyUnchanged(user);
+  }
+
+  private void verifyRootFlagUpdated(UserDto userDto, boolean root) {
+    db.rootFlag().verify(userDto, root);
+  }
+
+  private void verifyRootFlag(UserDto userDto, boolean root) {
+    db.rootFlag().verify(userDto, root);
+  }
+
+  private void verifyUserInGroup(UserDto userDto, GroupDto groupDto) {
+    assertThat(isUserInGroup(userDto, groupDto))
+      .as("user '%s' is a member of group '%s' of organization '%s'", userDto.getLogin(), groupDto.getName(), groupDto.getOrganizationUuid())
+      .isTrue();
+  }
+
+  private void verifyUserNotInGroup(UserDto userDto, GroupDto groupDto) {
+    assertThat(isUserInGroup(userDto, groupDto))
+      .as("user '%s' is not a member of group '%s' of organization '%s'", userDto.getLogin(), groupDto.getName(), groupDto.getOrganizationUuid())
+      .isFalse();
+  }
+
+  private boolean isUserInGroup(UserDto userDto, GroupDto groupDto) {
+    List<UserMembershipDto> members = db.getDbClient().groupMembershipDao()
+      .selectMembers(db.getSession(), UserMembershipQuery.builder().groupId(groupDto.getId()).membership(UserMembershipQuery.IN).build(), 0, Integer.MAX_VALUE);
+    return members
+      .stream()
+      .anyMatch(dto -> dto.getLogin().equals(userDto.getLogin()));
+  }
+
 }
index 1499703989d7a125bfa1f8481f836af06ec3c247..524597f270b9d79c5fa22d96fe0bd5c860009402 100644 (file)
@@ -120,6 +120,17 @@ public class GroupDao implements Dao {
     return mapper(dbSession).selectByOrganizationUuid(organizationUuid);
   }
 
+  /**
+   * Ensures all users of the specified group have its root flag set or unset depending on whether each of them have the
+   * 'admin' permission in the default organization or not.
+   */
+  public void updateRootFlagOfUsersInGroupFromPermissions(DbSession dbSession, long groupId, String defaultOrganizationUuid) {
+    long now = system.now();
+    GroupMapper mapper = mapper(dbSession);
+    mapper.updateRootUsersOfGroup(groupId, defaultOrganizationUuid, now);
+    mapper.updateNonRootUsersOfGroup(groupId, defaultOrganizationUuid, now);
+  }
+
   private static GroupMapper mapper(DbSession session) {
     return session.getMapper(GroupMapper.class);
   }
index 061f45789f388755aa1d56d9cd58d415f79eb1d3..ddf3097edb155b0a35b54ab738108ff6230ca5cb 100644 (file)
@@ -51,4 +51,12 @@ public interface GroupMapper {
   GroupDto selectByName(@Param("organizationUuid") String organizationUuid, @Param("name") String name);
 
   List<GroupDto> selectByOrganizationUuid(@Param("organizationUuid") String organizationUuid);
+
+  int updateRootUsersOfGroup(@Param("groupId") long groupId,
+    @Param("defaultOrganizationUuid") String defaultOrganizationUuid,
+    @Param("now") long now);
+
+  int updateNonRootUsersOfGroup(@Param("groupId") long groupId,
+    @Param("defaultOrganizationUuid") String defaultOrganizationUuid,
+    @Param("now") long now);
 }
index c4f1dc95e9ed24272ba78eb2f5524e1b8854dd20..b75b14d7ce9d2341ac89f511d5db7797cf60547f 100644 (file)
@@ -57,7 +57,7 @@ public class UserDao implements Dao {
   }
 
   public UserDto selectUserById(DbSession session, long userId) {
-    return getMapper(session).selectUser(userId);
+    return mapper(session).selectUser(userId);
   }
 
   /**
@@ -67,7 +67,7 @@ public class UserDao implements Dao {
    * Used by the Governance plugin
    */
   public List<UserDto> selectByIds(DbSession session, Collection<Long> ids) {
-    return executeLargeInputs(ids, getMapper(session)::selectByIds);
+    return executeLargeInputs(ids, mapper(session)::selectByIds);
   }
 
   /**
@@ -84,7 +84,7 @@ public class UserDao implements Dao {
 
   @CheckForNull
   public UserDto selectActiveUserByLogin(DbSession session, String login) {
-    UserMapper mapper = getMapper(session);
+    UserMapper mapper = mapper(session);
     return mapper.selectUserByLogin(login);
   }
 
@@ -93,7 +93,7 @@ public class UserDao implements Dao {
    * if list of logins is empty, without any db round trips.
    */
   public List<UserDto> selectByLogins(DbSession session, Collection<String> logins) {
-    return executeLargeInputs(logins, getMapper(session)::selectByLogins);
+    return executeLargeInputs(logins, mapper(session)::selectByLogins);
   }
 
   /**
@@ -124,25 +124,25 @@ public class UserDao implements Dao {
   }
 
   public List<UserDto> selectUsers(DbSession dbSession, UserQuery query) {
-    return getMapper(dbSession).selectUsers(query);
+    return mapper(dbSession).selectUsers(query);
   }
 
   public long countRootUsersButLogin(DbSession dbSession, String login) {
-    return getMapper(dbSession).countRootUsersButLogin(login);
+    return mapper(dbSession).countRootUsersButLogin(login);
   }
 
   public UserDto insert(DbSession session, UserDto dto) {
-    getMapper(session).insert(dto);
+    mapper(session).insert(dto);
     return dto;
   }
 
   public UserDto update(DbSession session, UserDto dto) {
-    getMapper(session).update(dto);
+    mapper(session).update(dto);
     return dto;
   }
 
   public void setRoot(DbSession session, String login, boolean root) {
-    getMapper(session).setRoot(login, root, system2.now());
+    mapper(session).setRoot(login, root, system2.now());
   }
 
   /**
@@ -150,7 +150,7 @@ public class UserDao implements Dao {
    * @return false if the user does not exist, true if the existing user has been deactivated
    */
   public boolean deactivateUserByLogin(DbSession dbSession, String login) {
-    UserMapper mapper = getMapper(dbSession);
+    UserMapper mapper = mapper(dbSession);
     UserDto dto = mapper.selectUserByLogin(login);
     if (dto == null) {
       return false;
@@ -173,7 +173,7 @@ public class UserDao implements Dao {
 
   @CheckForNull
   public UserDto selectByLogin(DbSession session, String login) {
-    return getMapper(session).selectByLogin(login);
+    return mapper(session).selectByLogin(login);
   }
 
   public UserDto selectOrFailByLogin(DbSession session, String login) {
@@ -188,7 +188,7 @@ public class UserDao implements Dao {
     String like = new StringBuilder().append("%")
       .append(UserDto.SCM_ACCOUNTS_SEPARATOR).append(scmAccountOrLoginOrEmail)
       .append(UserDto.SCM_ACCOUNTS_SEPARATOR).append("%").toString();
-    return getMapper(session).selectNullableByScmAccountOrLoginOrEmail(scmAccountOrLoginOrEmail, like);
+    return mapper(session).selectNullableByScmAccountOrLoginOrEmail(scmAccountOrLoginOrEmail, like);
   }
 
   /**
@@ -197,10 +197,21 @@ public class UserDao implements Dao {
    * Please note that email is case insensitive, result for searching 'mail@email.com' or 'Mail@Email.com' will be the same
    */
   public boolean doesEmailExist(DbSession dbSession, String email) {
-    return getMapper(dbSession).countByEmail(email.toLowerCase(Locale.ENGLISH)) > 0;
+    return mapper(dbSession).countByEmail(email.toLowerCase(Locale.ENGLISH)) > 0;
   }
 
-  private static UserMapper getMapper(DbSession session) {
+  /**
+   * Ensures the specified user has its root flag set or unset depending on whether the user has the 'admin' permission
+   * in the default organization or not.
+   */
+  public void updateRootFlagFromPermissions(DbSession dbSession, long userId, String defaultOrganizationUuid) {
+    long now = system2.now();
+    UserMapper mapper = mapper(dbSession);
+    mapper.updateRootUser(userId, defaultOrganizationUuid, now);
+    mapper.updateNonRootUser(userId, defaultOrganizationUuid, now);
+  }
+
+  protected UserMapper mapper(DbSession session) {
     return session.getMapper(UserMapper.class);
   }
 
index 517dc24d76dc116a4e8956860c312a9a3f6fa7a9..9088a71b6181d0e9ecf7411078599b10e71e47e4 100644 (file)
@@ -85,4 +85,9 @@ public interface UserMapper {
   void deletePropertiesMatchingLogin(@Param("propertyKeys") List<String> propertyKeys, @Param("login") String login);
 
   void deactivateUser(@Param("id") long userId, @Param("now") long now);
+
+  int updateRootUser(@Param("userId") long userId, @Param("defaultOrganizationUuid") String defaultOrganizationUuid, @Param("now") long now);
+
+  int updateNonRootUser(@Param("userId") long userId, @Param("defaultOrganizationUuid") String defaultOrganizationUuid, @Param("now") long now);
+
 }
index 1a75f8fc7855550f17aa13461e9fe757ad38b246..2898980a517122d451e0cb04efc9853abf826b70 100644 (file)
     order by upper(g.name)
   </select>
 
+  <update id="updateRootUsersOfGroup">
+    update users u set
+      is_root = ${_true},
+      updated_at = #{now,jdbcType=BIGINT}
+    where
+      u.id in (<include refid="userIdsForGroupId"/>)
+      and exists (
+        <include refid="userPermissionAdminInDefaultOrganizationForUser"/>
+        union
+        <include refid="groupPermissionAdminInDefaultOrganizationForUser"/>
+      )
+  </update>
 
+  <update id="updateRootUsersOfGroup" databaseId="mssql">
+    update u set
+      is_root = ${_true},
+      updated_at = #{now,jdbcType=BIGINT}
+    from users u
+    where
+      u.id in (<include refid="userIdsForGroupId"/>)
+      and exists (
+        <include refid="userPermissionAdminInDefaultOrganizationForUser"/>
+        union
+        <include refid="groupPermissionAdminInDefaultOrganizationForUser"/>
+      )
+  </update>
 
+  <update id="updateNonRootUsersOfGroup">
+    update users u set
+      is_root = ${_false},
+      updated_at = #{now,jdbcType=BIGINT}
+    where
+      u.id in (<include refid="userIdsForGroupId"/>)
+      and not exists (
+        <include refid="userPermissionAdminInDefaultOrganizationForUser"/>
+        union
+        <include refid="groupPermissionAdminInDefaultOrganizationForUser"/>
+      )
+  </update>
+
+  <update id="updateNonRootUsersOfGroup" databaseId="mssql">
+    update u set
+      is_root = ${_false},
+      updated_at = #{now,jdbcType=BIGINT}
+    from users u
+    where
+      u.id in (<include refid="userIdsForGroupId"/>)
+      and not exists (
+        <include refid="userPermissionAdminInDefaultOrganizationForUser"/>
+        union
+        <include refid="groupPermissionAdminInDefaultOrganizationForUser"/>
+      )
+  </update>
+
+  <sql id="userIdsForGroupId">
+    select
+      gu.user_id
+    from
+      groups_users gu
+    where
+      gu.group_id = #{groupId,jdbcType=BIGINT}
+  </sql>
+
+  <sql id="userPermissionAdminInDefaultOrganizationForUser">
+    select
+      1
+    from
+      user_roles ur
+    where
+      ur.user_id = u.id
+      and ur.role = 'admin'
+      and ur.resource_id is null
+      and ur.organization_uuid = #{defaultOrganizationUuid,jdbcType=VARCHAR}
+  </sql>
+
+  <sql id="groupPermissionAdminInDefaultOrganizationForUser">
+    select
+      1
+    from
+      groups_users gu,
+      group_roles gr
+    where
+      gu.user_id = u.id
+      and gu.group_id = gr.group_id
+      and gr.role = 'admin'
+      and gr.resource_id is null
+      and gr.organization_uuid = #{defaultOrganizationUuid,jdbcType=VARCHAR}
+  </sql>
 </mapper>
index 8456c8375394bd2339bff3db069aa837e2fb0a26..cde781fd0abe8a4ab271e2169646f0938f22eb0b 100644 (file)
       login = #{login}
   </insert>
 
+  <update id="updateRootUser">
+    update users u set
+      is_root = ${_true},
+      updated_at = #{now,jdbcType=BIGINT}
+    where
+      u.id = #{userId,jdbcType=BIGINT}
+      and exists (
+        <include refid="userPermissionAdminInDefaultOrganizationForUser"/>
+        union
+        <include refid="groupPermissionAdminInDefaultOrganizationForUser"/>
+      )
+  </update>
+
+  <update id="updateRootUser" databaseId="mssql">
+    update u set
+      is_root = ${_true},
+      updated_at = #{now,jdbcType=BIGINT}
+    from users u
+    where
+      u.id = #{userId,jdbcType=BIGINT}
+      and exists (
+        <include refid="userPermissionAdminInDefaultOrganizationForUser"/>
+        union
+        <include refid="groupPermissionAdminInDefaultOrganizationForUser"/>
+      )
+  </update>
+
+  <update id="updateNonRootUser">
+    update users u set
+      is_root = ${_false},
+      updated_at = #{now,jdbcType=BIGINT}
+    where
+      u.id = #{userId,jdbcType=BIGINT}
+      and not exists (
+        <include refid="userPermissionAdminInDefaultOrganizationForUser"/>
+        union
+        <include refid="groupPermissionAdminInDefaultOrganizationForUser"/>
+      )
+  </update>
+
+  <update id="updateNonRootUser" databaseId="mssql">
+    update u set
+      is_root = ${_false},
+      updated_at = #{now,jdbcType=BIGINT}
+    from users u
+    where
+      u.id = #{userId,jdbcType=BIGINT}
+      and not exists (
+        <include refid="userPermissionAdminInDefaultOrganizationForUser"/>
+        union
+        <include refid="groupPermissionAdminInDefaultOrganizationForUser"/>
+      )
+  </update>
+
+  <sql id="userPermissionAdminInDefaultOrganizationForUser">
+    select
+      1
+    from
+      user_roles ur
+    where
+      ur.user_id = u.id
+      and ur.role = 'admin'
+      and ur.resource_id is null
+      and ur.organization_uuid = #{defaultOrganizationUuid,jdbcType=VARCHAR}
+  </sql>
+
+  <sql id="groupPermissionAdminInDefaultOrganizationForUser">
+    select
+      1
+    from
+      groups_users gu,
+      group_roles gr
+    where
+      gu.user_id = u.id
+      and gu.group_id = gr.group_id
+      and gr.role = 'admin'
+      and gr.resource_id is null
+      and gr.organization_uuid = #{defaultOrganizationUuid,jdbcType=VARCHAR}
+  </sql>
+
 </mapper>
index d85f779f9dc4a16bb07a56accff822e57d7a9e28..a8898ae675acd33b881b884d9ab37a1eaf08cd0d 100644 (file)
@@ -68,6 +68,7 @@ import org.sonar.db.component.ComponentDbTester;
 import org.sonar.db.organization.OrganizationDbTester;
 import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.organization.OrganizationTesting;
+import org.sonar.db.user.RootFlagAssertions;
 import org.sonar.db.user.UserDbTester;
 
 import static com.google.common.base.Preconditions.checkArgument;
@@ -97,6 +98,7 @@ public class DbTester extends ExternalResource {
   private final UserDbTester userTester;
   private final ComponentDbTester componentTester;
   private final OrganizationDbTester organizationTester;
+  private final RootFlagAssertions rootFlagAssertions;
 
   private DbTester(System2 system2, @Nullable String schemaPath) {
     this.system2 = system2;
@@ -105,6 +107,7 @@ public class DbTester extends ExternalResource {
     this.userTester = new UserDbTester(this);
     this.componentTester = new ComponentDbTester(this);
     this.organizationTester = new OrganizationDbTester(this);
+    this.rootFlagAssertions = new RootFlagAssertions(this);
   }
 
   public static DbTester create(System2 system2) {
@@ -172,6 +175,10 @@ public class DbTester extends ExternalResource {
     return organizationTester;
   }
 
+  public RootFlagAssertions rootFlag() {
+    return rootFlagAssertions;
+  }
+
   @Override
   protected void after() {
     if (session != null) {
index 2f5ece9251f3071ef20d8cda79c599995744c72b..3a8db595d9f7f0b0d708306fc54f14c455360a64 100644 (file)
@@ -32,10 +32,12 @@ import org.sonar.db.DbTester;
 import org.sonar.db.organization.OrganizationDto;
 
 import static java.util.Arrays.asList;
+import static java.util.Arrays.stream;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
 import static org.sonar.db.user.GroupTesting.newGroupDto;
 
 public class GroupDaoTest {
@@ -45,11 +47,14 @@ public class GroupDaoTest {
     .setKey("an-org")
     .setName("An Org")
     .setUuid("abcde");
+  private static final long DATE_1 = 8_776_543L;
+  private static final long DATE_2 = 4_776_898L;
 
   private System2 system2 = mock(System2.class);
 
   @Rule
   public DbTester db = DbTester.create(system2);
+
   private final DbSession dbSession = db.getSession();
   private GroupDao underTest = new GroupDao(system2);
 
@@ -201,4 +206,195 @@ public class GroupDaoTest {
     assertThat(db.countRowsOfTable(dbSession, "groups")).isEqualTo(0);
   }
 
+  @Test
+  public void updateRootFlagOfUsersInGroupFromPermissions_sets_root_flag_to_false_if_users_have_no_permission_at_all() {
+    UserDto[] usersInGroup1 = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()
+    };
+    UserDto[] usersInGroup2 = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()
+    };
+    UserDto[] usersNotInGroup = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()
+    };
+    GroupDto group1 = db.users().insertGroup();
+    stream(usersInGroup1).forEach(user -> db.users().insertMember(group1, user));
+    GroupDto group2 = db.users().insertGroup();
+    stream(usersInGroup2).forEach(user -> db.users().insertMember(group2, user));
+
+    call_updateRootFlagFromPermissions(group1, DATE_1);
+    stream(usersInGroup1).forEach(user -> db.rootFlag().verify(user, false, DATE_1));
+    stream(usersInGroup2).forEach(db.rootFlag()::verifyUnchanged);
+    stream(usersNotInGroup).forEach(db.rootFlag()::verifyUnchanged);
+
+    call_updateRootFlagFromPermissions(group2, DATE_2);
+    stream(usersInGroup2).forEach(user -> db.rootFlag().verify(user, false, DATE_2));
+    stream(usersInGroup1).forEach(user -> db.rootFlag().verify(user, false, DATE_1));
+    stream(usersNotInGroup).forEach(db.rootFlag()::verifyUnchanged);
+  }
+
+  @Test
+  public void updateRootFlagOfUsersInGroupFromPermissions_sets_root_flag_to_true_if_users_has_admin_user_permission_on_default_organization() {
+    UserDto[] usersWithAdminInGroup1 = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser(),
+    };
+    UserDto[] usersWithoutAdminInGroup1 = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser(),
+    };
+    UserDto[] usersWithAdminInGroup2 = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()
+    };
+    UserDto[] usersWithoutAdminInGroup2 = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()
+    };
+    UserDto[] usersNotInGroup = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()
+    };
+    GroupDto group1 = db.users().insertGroup();
+    stream(usersWithAdminInGroup1).forEach(user -> db.users().insertMember(group1, user));
+    stream(usersWithoutAdminInGroup1).forEach(user -> db.users().insertMember(group1, user));
+    stream(usersWithAdminInGroup1).forEach(user -> db.users().insertPermissionOnUser(db.getDefaultOrganization(), user, SYSTEM_ADMIN));
+    GroupDto group2 = db.users().insertGroup();
+    stream(usersWithAdminInGroup2).forEach(user -> db.users().insertMember(group2, user));
+    stream(usersWithoutAdminInGroup2).forEach(user -> db.users().insertMember(group2, user));
+    stream(usersWithAdminInGroup2).forEach(user -> db.users().insertPermissionOnUser(db.getDefaultOrganization(), user, SYSTEM_ADMIN));
+
+    call_updateRootFlagFromPermissions(group1, DATE_1);
+    stream(usersWithAdminInGroup1).forEach(user -> db.rootFlag().verify(user, true, DATE_1));
+    stream(usersWithoutAdminInGroup1).forEach(user -> db.rootFlag().verify(user, false, DATE_1));
+    stream(usersWithAdminInGroup2).forEach(db.rootFlag()::verifyUnchanged);
+    stream(usersWithoutAdminInGroup2).forEach(db.rootFlag()::verifyUnchanged);
+    stream(usersNotInGroup).forEach(db.rootFlag()::verifyUnchanged);
+
+    call_updateRootFlagFromPermissions(group2, DATE_2);
+    stream(usersWithAdminInGroup1).forEach(user -> db.rootFlag().verify(user, true, DATE_1));
+    stream(usersWithoutAdminInGroup1).forEach(user -> db.rootFlag().verify(user, false, DATE_1));
+    stream(usersWithAdminInGroup2).forEach(user -> db.rootFlag().verify(user, true, DATE_2));
+    stream(usersWithoutAdminInGroup2).forEach(user -> db.rootFlag().verify(user, false, DATE_2));
+    stream(usersNotInGroup).forEach(db.rootFlag()::verifyUnchanged);
+  }
+
+  @Test
+  public void updateRootFlagOfUsersInGroupFromPermissions_ignores_permissions_on_anyone_on_default_organization() {
+    UserDto[] usersWithAdminInGroup1 = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser(),
+    };
+    UserDto[] usersWithoutAdminInGroup1 = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser(),
+    };
+    UserDto[] usersWithAdminInGroup2 = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()
+    };
+    UserDto[] usersWithoutAdminInGroup2 = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()
+    };
+    UserDto[] usersNotInGroup = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()
+    };
+    GroupDto group1 = db.users().insertGroup();
+    stream(usersWithAdminInGroup1).forEach(user -> db.users().insertMember(group1, user));
+    stream(usersWithoutAdminInGroup1).forEach(user -> db.users().insertMember(group1, user));
+    GroupDto group2 = db.users().insertGroup();
+    stream(usersWithAdminInGroup2).forEach(user -> db.users().insertMember(group2, user));
+    stream(usersWithoutAdminInGroup2).forEach(user -> db.users().insertMember(group2, user));
+    db.users().insertPermissionOnAnyone(db.getDefaultOrganization(), SYSTEM_ADMIN);
+
+    call_updateRootFlagFromPermissions(group1, DATE_1);
+    stream(usersWithAdminInGroup1).forEach(user -> db.rootFlag().verify(user, false, DATE_1));
+    stream(usersWithoutAdminInGroup1).forEach(user -> db.rootFlag().verify(user, false, DATE_1));
+    stream(usersWithAdminInGroup2).forEach(db.rootFlag()::verifyUnchanged);
+    stream(usersWithoutAdminInGroup2).forEach(db.rootFlag()::verifyUnchanged);
+    stream(usersNotInGroup).forEach(db.rootFlag()::verifyUnchanged);
+
+    call_updateRootFlagFromPermissions(group2, DATE_2);
+    stream(usersWithAdminInGroup1).forEach(user -> db.rootFlag().verify(user, false, DATE_1));
+    stream(usersWithoutAdminInGroup1).forEach(user -> db.rootFlag().verify(user, false, DATE_1));
+    stream(usersWithAdminInGroup2).forEach(user -> db.rootFlag().verify(user, false, DATE_2));
+    stream(usersWithoutAdminInGroup2).forEach(user -> db.rootFlag().verify(user, false, DATE_2));
+    stream(usersNotInGroup).forEach(db.rootFlag()::verifyUnchanged);
+  }
+
+  @Test
+  public void updateRootFlagOfUsersInGroupFromPermissions_ignores_permissions_on_anyone_on_other_organization() {
+    UserDto[] usersInGroup = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser(),
+    };
+    UserDto[] usersInOtherGroup = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()
+    };
+    UserDto[] usersNotInGroup = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()
+    };
+    GroupDto group = db.users().insertGroup();
+    stream(usersInGroup).forEach(user -> db.users().insertMember(group, user));
+    OrganizationDto otherOrganization = db.organizations().insert();
+    GroupDto otherGroup = db.users().insertGroup(otherOrganization);
+    stream(usersInOtherGroup).forEach(user -> db.users().insertMember(otherGroup, user));
+    db.users().insertPermissionOnAnyone(otherOrganization, SYSTEM_ADMIN);
+
+    call_updateRootFlagFromPermissions(group, DATE_1);
+    stream(usersInGroup).forEach(user -> db.rootFlag().verify(user, false, DATE_1));
+    stream(usersInOtherGroup).forEach(db.rootFlag()::verifyUnchanged);
+    stream(usersNotInGroup).forEach(db.rootFlag()::verifyUnchanged);
+
+    call_updateRootFlagFromPermissions(otherGroup, DATE_2);
+    stream(usersInGroup).forEach(user -> db.rootFlag().verify(user, false, DATE_1));
+    stream(usersInOtherGroup).forEach(user -> db.rootFlag().verify(user, false, DATE_2));
+    stream(usersNotInGroup).forEach(db.rootFlag()::verifyUnchanged);
+  }
+
+  @Test
+  public void updateRootFlagOfUsersInGroupFromPermissions_set_root_flag_to_false_on_users_of_group_of_non_default_organization() {
+    UserDto[] nonAdminUsers = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()
+    };
+    UserDto[] adminPerUserPermissionUsers = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser() // incorrectly not root
+    };
+    UserDto[] adminPerGroupPermissionUsers = {
+      db.users().makeRoot(db.users().insertUser()),
+      db.users().insertUser()  // incorrectly not root
+    };
+    OrganizationDto otherOrganization = db.organizations().insert();
+    GroupDto nonAdminGroup = db.users().insertGroup(otherOrganization);
+    db.users().insertMembers(nonAdminGroup, nonAdminUsers);
+    db.users().insertMembers(nonAdminGroup, adminPerUserPermissionUsers);
+    stream(adminPerUserPermissionUsers).forEach(user -> db.users().insertPermissionOnUser(otherOrganization, user, SYSTEM_ADMIN));
+    GroupDto adminGroup = db.users().insertAdminGroup(otherOrganization);
+    db.users().insertMembers(adminGroup, adminPerGroupPermissionUsers);
+
+    call_updateRootFlagFromPermissions(nonAdminGroup, DATE_2);
+    stream(nonAdminUsers).forEach(user -> db.rootFlag().verify(user, false, DATE_2));
+    stream(adminPerUserPermissionUsers).forEach(user -> db.rootFlag().verify(user, false, DATE_2));
+    stream(adminPerGroupPermissionUsers).forEach(db.rootFlag()::verifyUnchanged);
+
+    call_updateRootFlagFromPermissions(adminGroup, DATE_1);
+    stream(nonAdminUsers).forEach(user -> db.rootFlag().verify(user, false, DATE_2));
+    stream(adminPerUserPermissionUsers).forEach(user -> db.rootFlag().verify(user, false, DATE_2));
+    stream(adminPerGroupPermissionUsers).forEach(user -> db.rootFlag().verify(user, false, DATE_1));
+  }
+
+  private void call_updateRootFlagFromPermissions(GroupDto groupDto, long now) {
+    when(system2.now()).thenReturn(now);
+    underTest.updateRootFlagOfUsersInGroupFromPermissions(db.getSession(), groupDto.getId(), db.getDefaultOrganization().getUuid());
+    db.commit();
+  }
 }
diff --git a/sonar-db/src/test/java/org/sonar/db/user/RootFlagAssertions.java b/sonar-db/src/test/java/org/sonar/db/user/RootFlagAssertions.java
new file mode 100644 (file)
index 0000000..344d5dc
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact 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.db.user;
+
+import org.sonar.db.DbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class RootFlagAssertions {
+  private final DbTester db;
+
+  public RootFlagAssertions(DbTester db) {
+    this.db = db;
+  }
+
+  public void verifyUnchanged(UserDto user) {
+    verify(user, user.isRoot(), user.getUpdatedAt());
+  }
+
+  public void verify(UserDto userDto, boolean root, long updatedAt) {
+    UserDto dto = db.getDbClient().userDao().selectByLogin(db.getSession(), userDto.getLogin());
+    assertThat(dto.isRoot())
+      .as("Root flag of user '%s' is same as when created", userDto.getLogin())
+      .isEqualTo(root);
+    assertThat(dto.getUpdatedAt())
+      .as("UpdatedAt of user '%s' has not changed since created")
+      .isEqualTo(updatedAt);
+  }
+
+  public void verify(UserDto userDto, boolean root) {
+    UserDto dto = db.getDbClient().userDao().selectByLogin(db.getSession(), userDto.getLogin());
+    assertThat(dto.isRoot())
+      .as("Root flag of user '%s' is '%s'", userDto.getLogin(), root)
+      .isEqualTo(root);
+    assertThat(dto.getUpdatedAt())
+      .as("UpdatedAt of user '%s' has changed since insertion", userDto.getLogin())
+      .isNotEqualTo(userDto);
+  }
+}
index afa5f4e90ba740aca019f73c9d33086819a39999..35eca3f53c1dbe5b31afd9ea79e52cdb6075c948 100644 (file)
@@ -39,6 +39,7 @@ import org.sonar.db.issue.IssueFilterDto;
 import org.sonar.db.issue.IssueFilterFavouriteDto;
 import org.sonar.db.measure.MeasureFilterDto;
 import org.sonar.db.measure.MeasureFilterFavouriteDto;
+import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.permission.UserPermissionDto;
 import org.sonar.db.property.PropertyDto;
 import org.sonar.db.property.PropertyQuery;
@@ -49,13 +50,16 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
 import static org.sonar.db.user.GroupMembershipQuery.IN;
 import static org.sonar.db.user.GroupMembershipQuery.builder;
 import static org.sonar.db.user.GroupTesting.newGroupDto;
 import static org.sonar.db.user.UserTesting.newUserDto;
 
 public class UserDaoTest {
-  private static final long NOW = 1500000000000L;
+  private static final long NOW = 1_500_000_000_000L;
+  private static final long DATE_1 = 1_222_001L;
+  private static final long DATE_2 = 4_333_555L;
 
   private System2 system2 = mock(System2.class);
 
@@ -659,6 +663,116 @@ public class UserDaoTest {
     assertThat(underTest.selectByLogin(session, inactiveRootUser.getLogin()).isRoot()).isTrue();
   }
 
+  @Test
+  public void updateRootFlagFromPermissions_sets_root_flag_to_false_if_user_has_no_permission_at_all() {
+    UserDto rootUser = db.users().makeRoot(db.users().insertUser());
+    UserDto notRootUser = db.users().insertUser();
+
+    call_updateRootFlagFromPermissions(rootUser, DATE_1);
+    db.rootFlag().verify(rootUser, false, DATE_1);
+    db.rootFlag().verify(notRootUser, false, notRootUser.getUpdatedAt());
+
+    call_updateRootFlagFromPermissions(notRootUser, DATE_2);
+    db.rootFlag().verify(rootUser, false, DATE_1);
+    db.rootFlag().verify(notRootUser, false, DATE_2);
+  }
+
+  @Test
+  public void updateRootFlagFromPermissions_sets_root_flag_to_true_if_user_has_admin_user_permission_on_default_organization() {
+    UserDto rootUser = db.users().insertRootByUserPermission();
+    UserDto incorrectlyNotRootUser = db.users().insertUser();
+    db.users().insertPermissionOnUser(db.getDefaultOrganization(), incorrectlyNotRootUser, SYSTEM_ADMIN);
+
+    call_updateRootFlagFromPermissions(rootUser, DATE_1);
+    db.rootFlag().verify(rootUser, true, DATE_1);
+    db.rootFlag().verify(incorrectlyNotRootUser, false, incorrectlyNotRootUser.getUpdatedAt());
+
+    call_updateRootFlagFromPermissions(incorrectlyNotRootUser, DATE_2);
+    db.rootFlag().verify(rootUser, true, DATE_1);
+    db.rootFlag().verify(incorrectlyNotRootUser, true, DATE_2);
+  }
+
+  @Test
+  public void updateRootFlagFromPermissions_ignores_permissions_on_anyone_on_default_organization() {
+    UserDto rootUser = db.users().makeRoot(db.users().insertUser());
+    UserDto incorrectlyNotRootUser = db.users().insertUser();
+    db.users().insertPermissionOnAnyone(db.getDefaultOrganization(), SYSTEM_ADMIN);
+
+    call_updateRootFlagFromPermissions(rootUser, DATE_1);
+    db.rootFlag().verify(rootUser, false, DATE_1);
+    db.rootFlag().verify(incorrectlyNotRootUser, false, incorrectlyNotRootUser.getUpdatedAt());
+
+    call_updateRootFlagFromPermissions(incorrectlyNotRootUser, DATE_2);
+    db.rootFlag().verify(rootUser, false, DATE_1);
+    db.rootFlag().verify(incorrectlyNotRootUser, false, DATE_2);
+  }
+
+  @Test
+  public void updateRootFlagFromPermissions_ignores_permissions_on_anyone_on_other_organization() {
+    UserDto falselyRootUser = db.users().makeRoot(db.users().insertUser());
+    UserDto notRootUser = db.users().insertUser();
+    OrganizationDto otherOrganization = db.organizations().insert();
+    db.users().insertPermissionOnAnyone(otherOrganization, SYSTEM_ADMIN);
+
+    call_updateRootFlagFromPermissions(falselyRootUser, DATE_2);
+    db.rootFlag().verify(falselyRootUser, false, DATE_2);
+    db.rootFlag().verify(notRootUser, false, notRootUser.getUpdatedAt());
+
+    call_updateRootFlagFromPermissions(notRootUser, DATE_1);
+    db.rootFlag().verify(falselyRootUser, false, DATE_2);
+    db.rootFlag().verify(notRootUser, false, DATE_1);
+  }
+
+  @Test
+  public void updateRootFlagFromPermissions_sets_root_flag_to_false_if_user_has_admin_user_permission_on_other_organization() {
+    UserDto falselyRootUser = db.users().makeRoot(db.users().insertUser());
+    UserDto notRootUser = db.users().insertUser();
+    OrganizationDto otherOrganization = db.organizations().insert();
+    db.users().insertPermissionOnUser(otherOrganization, falselyRootUser, SYSTEM_ADMIN);
+    db.users().insertPermissionOnUser(otherOrganization, notRootUser, SYSTEM_ADMIN);
+
+    call_updateRootFlagFromPermissions(falselyRootUser, DATE_1);
+    db.rootFlag().verify(falselyRootUser, false, DATE_1);
+    db.rootFlag().verify(notRootUser, false, notRootUser.getUpdatedAt());
+
+    call_updateRootFlagFromPermissions(notRootUser, DATE_2);
+    db.rootFlag().verify(falselyRootUser, false, DATE_1);
+    db.rootFlag().verify(notRootUser, false, DATE_2);
+  }
+
+  @Test
+  public void updateRootFlagFromPermissions_sets_root_flag_to_true_if_user_has_admin_group_permission_on_default_organization() {
+    UserDto rootUser = db.users().makeRoot(db.users().insertUser());
+    UserDto incorrectlyNotRootUser = db.users().insertUser();
+    GroupDto groupDto = db.users().insertAdminGroup(db.getDefaultOrganization());
+    db.users().insertMembers(groupDto, rootUser, incorrectlyNotRootUser);
+
+    call_updateRootFlagFromPermissions(rootUser, DATE_1);
+    db.rootFlag().verify(rootUser, true, DATE_1);
+    db.rootFlag().verify(incorrectlyNotRootUser, false, incorrectlyNotRootUser.getUpdatedAt());
+
+    call_updateRootFlagFromPermissions(incorrectlyNotRootUser, DATE_2);
+    db.rootFlag().verify(rootUser, true, DATE_1);
+    db.rootFlag().verify(incorrectlyNotRootUser, true, DATE_2);
+  }
+
+  @Test
+  public void updateRootFlagFromPermissions_sets_root_flag_to_false_if_user_has_admin_group_permission_on_other_organization() {
+    UserDto falselyRootUser = db.users().makeRoot(db.users().insertUser());
+    UserDto notRootUser = db.users().insertUser();
+    GroupDto otherOrganizationGroupDto = db.users().insertGroup(db.organizations().insert());
+    db.users().insertPermissionOnGroup(otherOrganizationGroupDto, SYSTEM_ADMIN);
+    db.users().insertMembers(otherOrganizationGroupDto, falselyRootUser, notRootUser);
+
+    call_updateRootFlagFromPermissions(falselyRootUser, DATE_2);
+    db.rootFlag().verify(falselyRootUser, false, DATE_2);
+    db.rootFlag().verify(notRootUser, false, notRootUser.getUpdatedAt());
+
+    call_updateRootFlagFromPermissions(notRootUser, DATE_1);
+    db.rootFlag().verify(falselyRootUser, false, DATE_2);
+    db.rootFlag().verify(notRootUser, false, DATE_1);
+  }
+
   private void commit(Runnable runnable) {
     runnable.run();
     session.commit();
@@ -737,4 +851,11 @@ public class UserDaoTest {
     dbClient.userGroupDao().insert(session, dto);
     return dto;
   }
+
+  private void call_updateRootFlagFromPermissions(UserDto userDto, long now) {
+    when(system2.now()).thenReturn(now);
+    underTest.updateRootFlagFromPermissions(db.getSession(), userDto.getId(), db.getDefaultOrganization().getUuid());
+    db.commit();
+  }
+
 }
index 444beb61ee86b4b9b2ef0acac5580f17130153e1..31ef077d935516b034cf67a866257d2bfd8dcc5e 100644 (file)
  */
 package org.sonar.db.user;
 
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
+import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
@@ -31,6 +33,9 @@ import org.sonar.db.organization.OrganizationDto;
 import org.sonar.db.permission.GroupPermissionDto;
 import org.sonar.db.permission.UserPermissionDto;
 
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
 import static org.sonar.db.user.GroupTesting.newGroupDto;
 import static org.sonar.db.user.UserTesting.newUserDto;
 
@@ -60,6 +65,76 @@ public class UserDbTester {
     return updatedUser;
   }
 
+  public UserDto makeRoot(UserDto userDto) {
+    dbClient.userDao().setRoot(db.getSession(), userDto.getLogin(), true);
+    db.commit();
+    return dbClient.userDao().selectByLogin(db.getSession(), userDto.getLogin());
+  }
+
+  public UserDto makeNotRoot(UserDto userDto) {
+    dbClient.userDao().setRoot(db.getSession(), userDto.getLogin(), false);
+    db.commit();
+    return dbClient.userDao().selectByLogin(db.getSession(), userDto.getLogin());
+  }
+
+  public UserDto insertRootByUserPermission(String login) {
+    return insertRootByUserPermissionImpl(requireNonNull(login));
+  }
+
+  public UserDto insertRootByUserPermission() {
+    return insertRootByUserPermissionImpl(null);
+  }
+
+  private UserDto insertRootByUserPermissionImpl(@Nullable String login) {
+    UserDto rootByUserPermissionUser = makeRoot(login == null ? insertUser() : insertUser(login));
+    insertPermissionOnUser(db.getDefaultOrganization(), rootByUserPermissionUser, SYSTEM_ADMIN);
+    return rootByUserPermissionUser;
+  }
+
+  public UserDto insertRootByGroupPermission(String login) {
+    return insertRootByGroupPermissionImpl(requireNonNull(login), null);
+  }
+
+  /**
+   * @see #insertAdminGroup()
+   */
+  public UserDto insertRootByGroupPermission(String login, GroupDto adminGroupDto) {
+    return insertRootByGroupPermissionImpl(requireNonNull(login), adminGroupDto);
+  }
+
+
+  /**
+   * @see #insertAdminGroup()
+   */
+  public UserDto insertRootByGroupPermission(GroupDto adminGroupDto) {
+    return insertRootByGroupPermissionImpl(null, adminGroupDto);
+  }
+
+  public UserDto insertRootByGroupPermission() {
+    return insertRootByGroupPermissionImpl(null, null);
+  }
+
+  public UserDto insertRootByGroupPermissionImpl(@Nullable String login, @Nullable GroupDto groupDto) {
+    UserDto rootByGroupPermissionUser = db.users().makeRoot(login == null ? insertUser() : insertUser(login));
+    GroupDto adminGroup = createOrCheckAdminGroup(groupDto);
+    insertMember(adminGroup, rootByGroupPermissionUser);
+    return rootByGroupPermissionUser;
+  }
+
+  private GroupDto createOrCheckAdminGroup(@Nullable GroupDto groupDto) {
+    if (groupDto == null) {
+      GroupDto adminGroup = insertGroup(db.getDefaultOrganization());
+      insertPermissionOnGroup(adminGroup, SYSTEM_ADMIN);
+      return adminGroup;
+    }
+    checkArgument(
+      groupDto.getOrganizationUuid().equals(db.getDefaultOrganization().getUuid()),
+      "Group '%s' must belong to the default organization", groupDto.getName());
+    List<String> groupPermissions = db.getDbClient().groupPermissionDao().selectGlobalPermissionsOfGroup(db.getSession(), groupDto.getOrganizationUuid(), groupDto.getId());
+    checkArgument(groupPermissions.contains(SYSTEM_ADMIN), "Group '%s' must have permission '%s'", groupDto.getId(), SYSTEM_ADMIN);
+    return groupDto;
+  }
+
   public Optional<UserDto> selectUserByLogin(String login) {
     return Optional.ofNullable(dbClient.userDao().selectByLogin(db.getSession(), login));
   }
@@ -79,6 +154,32 @@ public class UserDbTester {
     return insertGroup(group);
   }
 
+  /**
+   * Creates a group in the default organization with {@link GlobalPermissions#SYSTEM_ADMIN} permission.
+   */
+  public GroupDto insertAdminGroup() {
+    GroupDto groupDto = insertGroup();
+    insertPermissionOnGroup(groupDto, SYSTEM_ADMIN);
+    return groupDto;
+  }
+
+  /**
+   * Creates a group in the specified organization with {@link GlobalPermissions#SYSTEM_ADMIN} permission.
+   */
+  public GroupDto insertAdminGroup(OrganizationDto organizationDto) {
+    GroupDto groupDto = insertGroup(organizationDto);
+    insertPermissionOnGroup(groupDto, SYSTEM_ADMIN);
+    return groupDto;
+  }
+
+  /**
+   * Create group in specified organization
+   */
+  public GroupDto insertGroup(OrganizationDto organizationDto) {
+    GroupDto group = newGroupDto().setOrganizationUuid(organizationDto.getUuid());
+    return insertGroup(group);
+  }
+
   public GroupDto insertGroup(GroupDto dto) {
     db.getDbClient().groupDao().insert(db.getSession(), dto);
     db.commit();
@@ -107,6 +208,14 @@ public class UserDbTester {
     return dto;
   }
 
+  public void insertMembers(GroupDto group, UserDto... users) {
+    Arrays.stream(users).forEach(user -> {
+      UserGroupDto dto = new UserGroupDto().setGroupId(group.getId()).setUserId(user.getId());
+      db.getDbClient().userGroupDao().insert(db.getSession(), dto);
+    });
+    db.commit();
+  }
+
   public List<Long> selectGroupIdsOfUser(UserDto user) {
     return db.getDbClient().groupMembershipDao().selectGroupIdsByUserId(db.getSession(), user.getId());
   }
@@ -140,6 +249,11 @@ public class UserDbTester {
     return dto;
   }
 
+  public void deletePermissionFromGroup(GroupDto group, String permission) {
+    db.getDbClient().groupPermissionDao().delete(db.getSession(), permission, group.getOrganizationUuid(), group.getId(), null);
+    db.commit();
+  }
+
   public GroupPermissionDto insertProjectPermissionOnAnyone(String permission, ComponentDto project) {
     return insertProjectPermissionOnAnyone(db.getDefaultOrganization(), permission, project);
   }