@@ -20,12 +20,14 @@ | |||
package org.sonar.server.permission; | |||
import java.util.List; | |||
import org.sonar.core.permission.GlobalPermissions; | |||
import java.util.Optional; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.permission.GroupPermissionDto; | |||
import org.sonar.server.exceptions.BadRequestException; | |||
import static java.lang.String.format; | |||
import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; | |||
import static org.sonar.server.permission.ws.PermissionRequestValidator.validateNotAnyoneAndAdminPermission; | |||
public class GroupPermissionChanger { | |||
@@ -37,57 +39,68 @@ public class GroupPermissionChanger { | |||
} | |||
public boolean apply(DbSession dbSession, GroupPermissionChange change) { | |||
if (shouldSkip(dbSession, change)) { | |||
return false; | |||
} | |||
switch (change.getOperation()) { | |||
case ADD: | |||
validateNotAnyoneAndAdminPermission(change.getPermission(), change.getGroupIdOrAnyone()); | |||
GroupPermissionDto addedDto = new GroupPermissionDto() | |||
.setRole(change.getPermission()) | |||
.setOrganizationUuid(change.getOrganizationUuid()) | |||
.setGroupId(change.getGroupIdOrAnyone().getId()) | |||
.setResourceId(change.getNullableProjectId()); | |||
dbClient.groupPermissionDao().insert(dbSession, addedDto); | |||
break; | |||
return addPermission(dbSession, change); | |||
case REMOVE: | |||
checkAdminUsersExistOutsideTheRemovedGroup(dbSession, change); | |||
dbClient.groupPermissionDao().delete(dbSession, | |||
change.getPermission(), | |||
change.getOrganizationUuid(), | |||
change.getGroupIdOrAnyone().getId(), | |||
change.getNullableProjectId()); | |||
break; | |||
return removePermission(dbSession, change); | |||
default: | |||
throw new UnsupportedOperationException("Unsupported permission change: " + change.getOperation()); | |||
} | |||
} | |||
private boolean addPermission(DbSession dbSession, GroupPermissionChange change) { | |||
if (loadExistingPermissions(dbSession, change).contains(change.getPermission())) { | |||
return false; | |||
} | |||
validateNotAnyoneAndAdminPermission(change.getPermission(), change.getGroupIdOrAnyone()); | |||
GroupPermissionDto addedDto = new GroupPermissionDto() | |||
.setRole(change.getPermission()) | |||
.setOrganizationUuid(change.getOrganizationUuid()) | |||
.setGroupId(change.getGroupIdOrAnyone().getId()) | |||
.setResourceId(change.getNullableProjectId()); | |||
dbClient.groupPermissionDao().insert(dbSession, addedDto); | |||
return true; | |||
} | |||
private boolean shouldSkip(DbSession dbSession, GroupPermissionChange change) { | |||
List<String> existingPermissions; | |||
if (change.getGroupIdOrAnyone().isAnyone()) { | |||
existingPermissions = dbClient.groupPermissionDao().selectAnyonePermissions(dbSession, change.getNullableProjectId()); | |||
} else { | |||
existingPermissions = dbClient.groupPermissionDao().selectGroupPermissions(dbSession, change.getGroupIdOrAnyone().getId(), change.getNullableProjectId()); | |||
private boolean removePermission(DbSession dbSession, GroupPermissionChange change) { | |||
if (!loadExistingPermissions(dbSession, change).contains(change.getPermission())) { | |||
return false; | |||
} | |||
switch (change.getOperation()) { | |||
case ADD: | |||
return existingPermissions.contains(change.getPermission()); | |||
case REMOVE: | |||
return !existingPermissions.contains(change.getPermission()); | |||
default: | |||
throw new UnsupportedOperationException("Unsupported operation: " + change.getOperation()); | |||
checkIfRemainingGlobalAdministrators(dbSession, change); | |||
dbClient.groupPermissionDao().delete(dbSession, | |||
change.getPermission(), | |||
change.getOrganizationUuid(), | |||
change.getGroupIdOrAnyone().getId(), | |||
change.getNullableProjectId()); | |||
return true; | |||
} | |||
private List<String> loadExistingPermissions(DbSession dbSession, GroupPermissionChange change) { | |||
Optional<ProjectId> projectId = change.getProjectId(); | |||
if (projectId.isPresent()) { | |||
return dbClient.groupPermissionDao().selectProjectPermissionsOfGroup(dbSession, | |||
change.getOrganizationUuid(), | |||
change.getGroupIdOrAnyone().getId(), | |||
projectId.get().getId()); | |||
} | |||
return dbClient.groupPermissionDao().selectGlobalPermissionsOfGroup(dbSession, | |||
change.getOrganizationUuid(), | |||
change.getGroupIdOrAnyone().getId()); | |||
} | |||
private void checkAdminUsersExistOutsideTheRemovedGroup(DbSession dbSession, GroupPermissionChange change) { | |||
if (GlobalPermissions.SYSTEM_ADMIN.equals(change.getPermission()) && | |||
!change.getProjectId().isPresent() && | |||
// TODO support organizations | |||
dbClient.roleDao().countUserPermissions(dbSession, change.getPermission(), change.getGroupIdOrAnyone().getId()) <= 0) { | |||
throw new BadRequestException(String.format("Last group with '%s' permission. Permission cannot be removed.", GlobalPermissions.SYSTEM_ADMIN)); | |||
private void checkIfRemainingGlobalAdministrators(DbSession dbSession, GroupPermissionChange change) { | |||
if (SYSTEM_ADMIN.equals(change.getPermission()) && | |||
!change.getGroupIdOrAnyone().isAnyone() && | |||
!change.getProjectId().isPresent()) { | |||
// removing global admin permission from group | |||
int remaining = dbClient.authorizationDao().countRemainingUserIdsWithGlobalPermissionIfExcludeGroup(dbSession, | |||
change.getOrganizationUuid(), SYSTEM_ADMIN, change.getGroupIdOrAnyone().getId()); | |||
if (remaining == 0) { | |||
throw new BadRequestException(format("Last group with permission '%s'. Permission cannot be removed.", SYSTEM_ADMIN)); | |||
} | |||
} | |||
} | |||
@@ -26,16 +26,15 @@ import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.api.server.ws.WebService.NewController; | |||
import org.sonar.core.permission.GlobalPermissions; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.permission.PermissionQuery; | |||
import org.sonar.db.user.GroupDto; | |||
import org.sonar.server.organization.DefaultOrganizationProvider; | |||
import org.sonar.server.user.UserSession; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static java.lang.String.format; | |||
import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; | |||
import static org.sonar.server.usergroups.ws.GroupWsSupport.PARAM_GROUP_ID; | |||
import static org.sonar.server.usergroups.ws.GroupWsSupport.PARAM_GROUP_NAME; | |||
import static org.sonar.server.usergroups.ws.GroupWsSupport.defineGroupWsParameters; | |||
@@ -73,13 +72,12 @@ public class DeleteAction implements UserGroupsWsAction { | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
userSession.checkLoggedIn().checkPermission(GlobalPermissions.SYSTEM_ADMIN); | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
GroupId groupId = support.findGroup(dbSession, request); | |||
userSession.checkOrganizationPermission(groupId.getOrganizationUuid(), SYSTEM_ADMIN); | |||
checkNotTryingToDeleteDefaultGroup(dbSession, groupId); | |||
checkNotTryingToDeleteLastSystemAdminGroup(dbSession, groupId); | |||
checkNotTryingToDeleteLastAdminGroup(dbSession, groupId); | |||
removeGroupMembers(dbSession, groupId); | |||
removeGroupPermissions(dbSession, groupId); | |||
removeFromPermissionTemplates(dbSession, groupId); | |||
@@ -105,17 +103,11 @@ public class DeleteAction implements UserGroupsWsAction { | |||
} | |||
} | |||
private void checkNotTryingToDeleteLastSystemAdminGroup(DbSession dbSession, GroupId group) { | |||
boolean hasAdminPermission = dbClient.groupPermissionDao() | |||
.selectGroupPermissions(dbSession, group.getId(), null) | |||
.contains(GlobalPermissions.SYSTEM_ADMIN); | |||
// TODO support organizations | |||
boolean isOneRemainingAdminGroup = dbClient.groupPermissionDao().countGroups(dbSession, GlobalPermissions.SYSTEM_ADMIN, null) == 1; | |||
boolean hasNoStandaloneAdminUser = dbClient.userPermissionDao().countUsers(dbSession, | |||
PermissionQuery.builder().setPermission(GlobalPermissions.SYSTEM_ADMIN).withAtLeastOnePermission().build()) == 0; | |||
boolean isLastAdminGroup = hasAdminPermission && isOneRemainingAdminGroup && hasNoStandaloneAdminUser; | |||
checkArgument(!isLastAdminGroup, "The last system admin group cannot be deleted"); | |||
private void checkNotTryingToDeleteLastAdminGroup(DbSession dbSession, GroupId group) { | |||
int remaining = dbClient.authorizationDao().countRemainingUserIdsWithGlobalPermissionIfExcludeGroup(dbSession, | |||
group.getOrganizationUuid(), SYSTEM_ADMIN, group.getId()); | |||
checkArgument(remaining > 0, "The last system admin group cannot be deleted"); | |||
} | |||
private void removeGroupMembers(DbSession dbSession, GroupId groupId) { |
@@ -33,6 +33,7 @@ import org.sonar.db.component.ComponentTesting; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.organization.OrganizationTesting; | |||
import org.sonar.db.user.GroupDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.exceptions.BadRequestException; | |||
import org.sonar.server.usergroups.ws.GroupIdOrAnyone; | |||
@@ -79,22 +80,24 @@ public class GroupPermissionChangerTest { | |||
@Test | |||
public void add_permission_to_anyone() { | |||
GroupIdOrAnyone groupId = new GroupIdOrAnyone(db.getDefaultOrganization().getUuid(), null); | |||
OrganizationDto defaultOrganization = db.getDefaultOrganization(); | |||
GroupIdOrAnyone groupId = new GroupIdOrAnyone(defaultOrganization.getUuid(), null); | |||
apply(new GroupPermissionChange(PermissionChange.Operation.ADD, GlobalPermissions.QUALITY_GATE_ADMIN, null, groupId)); | |||
assertThat(db.users().selectGroupPermissions(group, null)).isEmpty(); | |||
assertThat(db.users().selectAnyonePermissions(null)).containsOnly(GlobalPermissions.QUALITY_GATE_ADMIN); | |||
assertThat(db.users().selectAnyonePermissions(defaultOrganization, null)).containsOnly(GlobalPermissions.QUALITY_GATE_ADMIN); | |||
} | |||
@Test | |||
public void add_project_permission_to_anyone() { | |||
GroupIdOrAnyone groupId = new GroupIdOrAnyone(db.getDefaultOrganization().getUuid(), null); | |||
OrganizationDto defaultOrganization = db.getDefaultOrganization(); | |||
GroupIdOrAnyone groupId = new GroupIdOrAnyone(defaultOrganization.getUuid(), null); | |||
apply(new GroupPermissionChange(PermissionChange.Operation.ADD, UserRole.ISSUE_ADMIN, new ProjectId(project), groupId)); | |||
assertThat(db.users().selectAnyonePermissions(null)).isEmpty(); | |||
assertThat(db.users().selectAnyonePermissions(project)).containsOnly(UserRole.ISSUE_ADMIN); | |||
assertThat(db.users().selectAnyonePermissions(defaultOrganization, null)).isEmpty(); | |||
assertThat(db.users().selectAnyonePermissions(defaultOrganization, project)).containsOnly(UserRole.ISSUE_ADMIN); | |||
} | |||
@Test | |||
@@ -162,16 +165,28 @@ public class GroupPermissionChangerTest { | |||
} | |||
@Test | |||
public void fail_to_remove_sysadmin_permission_if_no_more_sysadmins() { | |||
public void fail_to_remove_admin_permission_if_no_more_admins() { | |||
GroupIdOrAnyone groupId = new GroupIdOrAnyone(group); | |||
db.users().insertPermissionOnGroup(group, GlobalPermissions.SYSTEM_ADMIN); | |||
expectedException.expect(BadRequestException.class); | |||
expectedException.expectMessage("Last group with 'admin' permission. Permission cannot be removed."); | |||
expectedException.expectMessage("Last group with permission 'admin'. Permission cannot be removed."); | |||
underTest.apply(db.getSession(), new GroupPermissionChange(PermissionChange.Operation.REMOVE, GlobalPermissions.SYSTEM_ADMIN, null, groupId)); | |||
} | |||
@Test | |||
public void remove_admin_group_if_still_other_admins() { | |||
GroupIdOrAnyone groupId = new GroupIdOrAnyone(group); | |||
db.users().insertPermissionOnGroup(group, GlobalPermissions.SYSTEM_ADMIN); | |||
UserDto admin = db.users().insertUser(); | |||
db.users().insertPermissionOnUser(admin, GlobalPermissions.SYSTEM_ADMIN); | |||
apply(new GroupPermissionChange(PermissionChange.Operation.REMOVE, GlobalPermissions.SYSTEM_ADMIN, null, groupId)); | |||
assertThat(db.users().selectGroupPermissions(group, null)).isEmpty(); | |||
} | |||
private void apply(GroupPermissionChange change) { | |||
underTest.apply(db.getSession(), change); | |||
db.commit(); |
@@ -145,13 +145,13 @@ public class RemoveGroupActionTest extends BasePermissionWsTest<RemoveGroupActio | |||
} | |||
@Test | |||
public void fail_to_remove_last_sysadmin_permission() throws Exception { | |||
public void fail_to_remove_last_admin_permission() throws Exception { | |||
db.users().insertPermissionOnGroup(aGroup, SYSTEM_ADMIN); | |||
db.users().insertPermissionOnGroup(aGroup, PROVISIONING); | |||
loginAsAdmin(); | |||
expectedException.expect(BadRequestException.class); | |||
expectedException.expectMessage("Last group with 'admin' permission. Permission cannot be removed."); | |||
expectedException.expectMessage("Last group with permission 'admin'. Permission cannot be removed."); | |||
newRequest() | |||
.setParam(PARAM_GROUP_NAME, aGroup.getName()) |
@@ -28,12 +28,15 @@ import org.sonar.api.config.MapSettings; | |||
import org.sonar.api.config.Settings; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.api.web.UserRole; | |||
import org.sonar.core.permission.GlobalPermissions; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.ComponentTesting; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.organization.OrganizationTesting; | |||
import org.sonar.db.permission.template.PermissionTemplateDto; | |||
import org.sonar.db.permission.template.PermissionTemplateTesting; | |||
import org.sonar.db.user.GroupDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
@@ -44,6 +47,7 @@ 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.db.organization.OrganizationTesting.newOrganizationDto; | |||
import static org.sonar.server.usergroups.ws.GroupWsSupport.PARAM_GROUP_ID; | |||
import static org.sonar.server.usergroups.ws.GroupWsSupport.PARAM_GROUP_NAME; | |||
import static org.sonar.server.usergroups.ws.GroupWsSupport.PARAM_ORGANIZATION_KEY; | |||
@@ -63,7 +67,7 @@ public class DeleteActionTest { | |||
@Before | |||
public void setUp() { | |||
defaultGroup = db.users().insertGroup(defaultOrganizationProvider.getDto(), CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE); | |||
defaultGroup = db.users().insertGroup(db.getDefaultOrganization(), CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE); | |||
Settings settings = new MapSettings().setProperty(CoreProperties.CORE_DEFAULT_GROUP, CoreProperties.CORE_DEFAULT_GROUP_DEFAULT_VALUE); | |||
ws = new WsTester(new UserGroupsWs( | |||
@@ -76,9 +80,10 @@ public class DeleteActionTest { | |||
@Test | |||
public void delete_by_id() throws Exception { | |||
GroupDto group = db.users().insertGroup(defaultOrganizationProvider.getDto(), "to-delete"); | |||
addAdmin(db.getDefaultOrganization()); | |||
GroupDto group = db.users().insertGroup(); | |||
loginAsAdminOnDefaultOrganization(); | |||
loginAsAdmin(); | |||
newRequest() | |||
.setParam("id", group.getId().toString()) | |||
.execute() | |||
@@ -89,9 +94,10 @@ public class DeleteActionTest { | |||
@Test | |||
public void delete_by_name_on_default_organization() throws Exception { | |||
GroupDto group = db.users().insertGroup(defaultOrganizationProvider.getDto(), "to-delete"); | |||
addAdminToDefaultOrganization(); | |||
GroupDto group = db.users().insertGroup(); | |||
loginAsAdminOnDefaultOrganization(); | |||
loginAsAdmin(); | |||
newRequest() | |||
.setParam(PARAM_GROUP_NAME, group.getName()) | |||
.execute() | |||
@@ -103,9 +109,10 @@ public class DeleteActionTest { | |||
@Test | |||
public void delete_by_name_and_organization() throws Exception { | |||
OrganizationDto org = OrganizationTesting.insert(db, newOrganizationDto()); | |||
addAdmin(org); | |||
GroupDto group = db.users().insertGroup(org, "to-delete"); | |||
loginAsAdmin(org); | |||
loginAsAdmin(); | |||
newRequest() | |||
.setParam(PARAM_ORGANIZATION_KEY, org.getKey()) | |||
.setParam(PARAM_GROUP_NAME, group.getName()) | |||
@@ -117,27 +124,28 @@ public class DeleteActionTest { | |||
@Test | |||
public void delete_by_name_fails_if_organization_is_not_correct() throws Exception { | |||
OrganizationDto org = newOrganizationDto().setUuid("org1"); | |||
OrganizationDto org = newOrganizationDto(); | |||
OrganizationTesting.insert(db, org); | |||
loginAsAdmin(); | |||
loginAsAdmin(org); | |||
expectedException.expect(NotFoundException.class); | |||
expectedException.expectMessage("No organization with key 'org1'"); | |||
expectedException.expectMessage("No organization with key 'missing'"); | |||
newRequest() | |||
.setParam(PARAM_ORGANIZATION_KEY, "org1") | |||
.setParam(PARAM_ORGANIZATION_KEY, "missing") | |||
.setParam(PARAM_GROUP_NAME, "a-group") | |||
.execute(); | |||
} | |||
@Test | |||
public void delete_members() throws Exception { | |||
GroupDto group = db.users().insertGroup(defaultOrganizationProvider.getDto(), "to-be-deleted"); | |||
UserDto user = db.users().insertUser("a-user"); | |||
addAdminToDefaultOrganization(); | |||
GroupDto group = db.users().insertGroup(); | |||
UserDto user = db.users().insertUser(); | |||
db.users().insertMember(group, user); | |||
loginAsAdminOnDefaultOrganization(); | |||
loginAsAdmin(); | |||
newRequest() | |||
.setParam("id", group.getId().toString()) | |||
.execute() | |||
@@ -148,11 +156,12 @@ public class DeleteActionTest { | |||
@Test | |||
public void delete_permissions() throws Exception { | |||
GroupDto group = db.users().insertGroup(defaultOrganizationProvider.getDto(), "to-be-deleted"); | |||
addAdminToDefaultOrganization(); | |||
GroupDto group = db.users().insertGroup(); | |||
ComponentDto project = componentTester.insertComponent(ComponentTesting.newProjectDto()); | |||
db.users().insertProjectPermissionOnGroup(group, UserRole.ADMIN, project); | |||
loginAsAdminOnDefaultOrganization(); | |||
loginAsAdmin(); | |||
newRequest() | |||
.setParam("id", group.getId().toString()) | |||
.execute() | |||
@@ -162,11 +171,15 @@ public class DeleteActionTest { | |||
} | |||
@Test | |||
public void delete_permission_templates() throws Exception { | |||
GroupDto group = db.users().insertGroup(defaultOrganizationProvider.getDto(), "to-be-deleted"); | |||
// TODO | |||
public void delete_group_from_permission_templates() throws Exception { | |||
addAdminToDefaultOrganization(); | |||
GroupDto group = db.users().insertGroup(); | |||
PermissionTemplateDto template = db.getDbClient().permissionTemplateDao().insert(db.getSession(), PermissionTemplateTesting.newPermissionTemplateDto()); | |||
db.getDbClient().permissionTemplateDao().insertGroupPermission(db.getSession(), template.getId(), group.getId(), "perm"); | |||
db.commit(); | |||
loginAsAdminOnDefaultOrganization(); | |||
assertThat(db.countRowsOfTable("perm_templates_groups")).isEqualTo(1); | |||
loginAsAdmin(); | |||
newRequest() | |||
.setParam("id", group.getId().toString()) | |||
.execute().assertNoContent(); | |||
@@ -174,17 +187,27 @@ public class DeleteActionTest { | |||
assertThat(db.countRowsOfTable("perm_templates_groups")).isEqualTo(0); | |||
} | |||
@Test(expected = NotFoundException.class) | |||
@Test | |||
public void fail_if_id_does_not_exist() throws Exception { | |||
loginAsAdmin(); | |||
addAdminToDefaultOrganization(); | |||
loginAsAdminOnDefaultOrganization(); | |||
long groupId = defaultGroup.getId() + 123; | |||
expectedException.expect(NotFoundException.class); | |||
expectedException.expectMessage("No group with id '" + groupId + "'"); | |||
newRequest() | |||
.setParam("id", String.valueOf(defaultGroup.getId() + 123)) | |||
.setParam("id", String.valueOf(groupId)) | |||
.execute(); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
@Test | |||
public void cannot_delete_default_group_of_default_organization() throws Exception { | |||
loginAsAdmin(); | |||
loginAsAdminOnDefaultOrganization(); | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Default group 'sonar-users' cannot be deleted"); | |||
newRequest() | |||
.setParam("id", defaultGroup.getId().toString()) | |||
.execute(); | |||
@@ -193,9 +216,10 @@ public class DeleteActionTest { | |||
@Test | |||
public void delete_group_of_an_organization_even_if_name_is_default_group_of_default_organization() throws Exception { | |||
OrganizationDto org = OrganizationTesting.insert(db, newOrganizationDto()); | |||
addAdmin(org); | |||
GroupDto group = db.users().insertGroup(org, defaultGroup.getName()); | |||
loginAsAdmin(org); | |||
loginAsAdmin(); | |||
newRequest() | |||
.setParam("id", group.getId().toString()) | |||
.execute(); | |||
@@ -206,10 +230,9 @@ public class DeleteActionTest { | |||
@Test | |||
public void cannot_delete_last_system_admin_group() throws Exception { | |||
GroupDto group = db.users().insertGroup(defaultOrganizationProvider.getDto(), "system-admins"); | |||
GroupDto group = db.users().insertGroup(); | |||
db.users().insertPermissionOnGroup(group, SYSTEM_ADMIN); | |||
loginAsAdmin(); | |||
loginAsAdminOnDefaultOrganization(); | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("The last system admin group cannot be deleted"); | |||
@@ -220,37 +243,52 @@ public class DeleteActionTest { | |||
} | |||
@Test | |||
public void delete_system_admin_group_if_not_last() throws Exception { | |||
OrganizationDto defaultOrg = defaultOrganizationProvider.getDto(); | |||
GroupDto funkyAdmins = db.users().insertGroup(defaultOrg, "funky-admins"); | |||
db.users().insertPermissionOnGroup(funkyAdmins, SYSTEM_ADMIN); | |||
GroupDto boringAdmins = db.users().insertGroup(defaultOrg, "boring-admins"); | |||
db.users().insertPermissionOnGroup(boringAdmins, SYSTEM_ADMIN); | |||
loginAsAdmin(); | |||
public void delete_admin_group_fails_if_no_admin_users_left() throws Exception { | |||
// admin users are part of the group to be deleted | |||
OrganizationDto org = OrganizationTesting.insert(db, newOrganizationDto()); | |||
GroupDto adminGroup = db.users().insertGroup(org, "admins"); | |||
db.users().insertPermissionOnGroup(adminGroup, SYSTEM_ADMIN); | |||
UserDto bigBoss = db.users().insertUser(); | |||
db.users().insertMember(adminGroup, bigBoss); | |||
loginAsAdmin(org); | |||
newRequest() | |||
.setParam(PARAM_GROUP_NAME, boringAdmins.getName()) | |||
.execute(); | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("The last system admin group cannot be deleted"); | |||
assertThat(db.getDbClient().groupPermissionDao().countGroups(db.getSession(), SYSTEM_ADMIN, null)).isEqualTo(1); | |||
newRequest().setParam(PARAM_GROUP_ID, adminGroup.getId().toString()).execute(); | |||
} | |||
@Test | |||
public void delete_last_system_admin_group_if_admin_user_left() throws Exception { | |||
GroupDto lastGroup = db.users().insertGroup(defaultOrganizationProvider.getDto(), "last-group"); | |||
db.users().insertPermissionOnGroup(lastGroup, SYSTEM_ADMIN); | |||
UserDto bigBoss = db.users().insertUser("big.boss"); | |||
db.users().insertPermissionOnUser(bigBoss, SYSTEM_ADMIN); | |||
public void delete_admin_group_succeeds_if_other_groups_have_administrators() throws Exception { | |||
OrganizationDto org = OrganizationTesting.insert(db, newOrganizationDto()); | |||
GroupDto adminGroup1 = db.users().insertGroup(org, "admins"); | |||
db.users().insertPermissionOnGroup(adminGroup1, SYSTEM_ADMIN); | |||
GroupDto adminGroup2 = db.users().insertGroup(org, "admins"); | |||
db.users().insertPermissionOnGroup(adminGroup2, SYSTEM_ADMIN); | |||
UserDto bigBoss = db.users().insertUser(); | |||
db.users().insertMember(adminGroup2, bigBoss); | |||
loginAsAdmin(org); | |||
newRequest().setParam(PARAM_GROUP_ID, adminGroup1.getId().toString()).execute(); | |||
loginAsAdmin(); | |||
newRequest().setParam(PARAM_GROUP_NAME, lastGroup.getName()).execute(); | |||
assertThat(db.users().selectGroupPermissions(adminGroup2, null)).hasSize(1); | |||
} | |||
private void addAdminToDefaultOrganization() { | |||
addAdmin(db.getDefaultOrganization()); | |||
} | |||
private void addAdmin(OrganizationDto org) { | |||
UserDto admin = db.users().insertUser(); | |||
db.users().insertPermissionOnUser(org, admin, SYSTEM_ADMIN); | |||
} | |||
assertThat(db.users().selectGroupById(lastGroup.getId())).isNull(); | |||
private void loginAsAdminOnDefaultOrganization() { | |||
loginAsAdmin(db.getDefaultOrganization()); | |||
} | |||
private void loginAsAdmin() { | |||
userSession.login("admin").setGlobalPermissions(SYSTEM_ADMIN); | |||
private void loginAsAdmin(OrganizationDto org) { | |||
userSession.login().addOrganizationPermission(org.getUuid(), GlobalPermissions.SYSTEM_ADMIN); | |||
} | |||
private WsTester.TestRequest newRequest() { |
@@ -77,6 +77,15 @@ public class AuthorizationDao implements Dao { | |||
return mapper(dbSession).selectRootComponentPermissionsOfAnonymous(rootComponentId); | |||
} | |||
/** | |||
* The number of users who will still have the permission when the group {@code excludedGroupId} | |||
* is deleted. | |||
*/ | |||
public int countRemainingUserIdsWithGlobalPermissionIfExcludeGroup(DbSession dbSession, String organizationUuid, | |||
String permission, long excludedGroupId) { | |||
return mapper(dbSession).countRemainingUserIdsWithGlobalPermissionIfExcludeGroup(organizationUuid, permission, excludedGroupId); | |||
} | |||
public Collection<Long> keepAuthorizedProjectIds(DbSession dbSession, Collection<Long> componentIds, @Nullable Integer userId, String role) { | |||
return executeLargeInputs( | |||
componentIds, |
@@ -37,6 +37,8 @@ public interface AuthorizationMapper { | |||
Set<String> selectRootComponentPermissionsOfAnonymous(@Param("rootComponentId") long rootComponentId); | |||
int countRemainingUserIdsWithGlobalPermissionIfExcludeGroup(@Param("organizationUuid") String organizationUuid, @Param("permission") String permission, @Param("excludedGroupId") long excludedGroupId); | |||
List<Long> keepAuthorizedProjectIdsForAnonymous(@Param("role") String role, @Param("componentIds") Collection<Long> componentIds); | |||
List<Long> keepAuthorizedProjectIdsForUser(@Param("userId") long userId, @Param("role") String role, @Param("componentIds") Collection<Long> componentIds); |
@@ -92,21 +92,20 @@ public class GroupPermissionDao implements Dao { | |||
} | |||
/** | |||
* @return the permissions granted to the requested group, optionally on the requested project. An | |||
* empty list is returned if the group or project do not exist. | |||
* Selects the global permissions granted to group. An empty list is returned if the | |||
* group does not exist. | |||
*/ | |||
public List<String> selectGroupPermissions(DbSession session, long groupId, @Nullable Long projectId) { | |||
return session.getMapper(GroupPermissionMapper.class).selectGroupPermissions(groupId, projectId); | |||
public List<String> selectGlobalPermissionsOfGroup(DbSession session, String organizationUuid, @Nullable Long groupId) { | |||
return mapper(session).selectGlobalPermissionsOfGroup(organizationUuid, groupId); | |||
} | |||
/** | |||
* @return the permissions granted to Anyone virtual group, optionally on the requested project. An | |||
* empty list is returned if the project does not exist. | |||
* @deprecated not compatible with organizations if {@code projectId} is null. Should have an organization parameter. | |||
* Selects the permissions granted to group and project. An empty list is returned if the | |||
* group or project do not exist. | |||
*/ | |||
@Deprecated | |||
public List<String> selectAnyonePermissions(DbSession session, @Nullable Long projectId) { | |||
return session.getMapper(GroupPermissionMapper.class).selectAnyonePermissions(projectId); | |||
public List<String> selectProjectPermissionsOfGroup(DbSession session, String organizationUuid, @Nullable Long groupId, long projectId) { | |||
return mapper(session).selectProjectPermissionsOfGroup(organizationUuid, groupId, projectId); | |||
} | |||
/** |
@@ -54,10 +54,6 @@ public interface GroupPermissionMapper { | |||
void groupsCountByProjectIdAndPermission(Map<String, Object> parameters, ResultHandler resultHandler); | |||
List<String> selectGroupPermissions(@Param("groupId") long groupId, @Nullable @Param("projectId") Long projectId); | |||
List<String> selectAnyonePermissions(@Nullable @Param("projectId") Long projectId); | |||
void insert(GroupPermissionDto dto); | |||
void deleteByRootComponentId(@Param("rootComponentId") long componentId); | |||
@@ -66,4 +62,8 @@ public interface GroupPermissionMapper { | |||
@Nullable @Param("groupId") Long groupId, @Nullable @Param("rootComponentId") Long rootComponentId); | |||
int countRowsByRootComponentId(@Param("rootComponentId") long rootComponentId); | |||
List<String> selectGlobalPermissionsOfGroup(@Param("organizationUuid") String organizationUuid, @Nullable @Param("groupId") Long groupId); | |||
List<String> selectProjectPermissionsOfGroup(@Param("organizationUuid") String organizationUuid, @Nullable @Param("groupId") Long groupId, @Param("projectId") long projectId); | |||
} |
@@ -49,6 +49,10 @@ public class RoleDao implements Dao { | |||
mapper(session).deleteGroupRolesByGroupId(groupId); | |||
} | |||
/** | |||
* @deprecated does not support organizations | |||
*/ | |||
@Deprecated | |||
public int countUserPermissions(DbSession session, String permission, @Nullable Long allGroupsExceptThisGroupId) { | |||
return mapper(session).countUsersWithPermission(permission, allGroupsExceptThisGroupId); | |||
} |
@@ -71,6 +71,29 @@ | |||
gr.group_id is null | |||
</select> | |||
<select id="countRemainingUserIdsWithGlobalPermissionIfExcludeGroup" parameterType="map" resultType="int"> | |||
select count(1) from | |||
( | |||
select gu.user_id | |||
from groups_users gu | |||
inner join group_roles gr on gr.group_id = gu.group_id | |||
where | |||
gr.organization_uuid = #{organizationUuid,jdbcType=VARCHAR} and | |||
gr.role = #{permission,jdbcType=VARCHAR} and | |||
gr.resource_id is null and | |||
gr.group_id is not null and | |||
gr.group_id != #{excludedGroupId,jdbcType=BIGINT} | |||
union | |||
select ur.user_id | |||
from user_roles ur | |||
where | |||
ur.resource_id is null and | |||
ur.role = #{permission,jdbcType=VARCHAR} | |||
) remaining | |||
</select> | |||
<select id="keepAuthorizedProjectIdsForUser" parameterType="map" resultType="long"> | |||
SELECT gr.resource_id | |||
FROM group_roles gr |
@@ -124,30 +124,36 @@ | |||
</if> | |||
</select> | |||
<select id="selectGroupPermissions" parameterType="map" resultType="String"> | |||
<select id="selectGlobalPermissionsOfGroup" parameterType="map" resultType="String"> | |||
select gr.role | |||
from group_roles gr | |||
where gr.group_id = #{groupId} | |||
and | |||
<if test="projectId == null"> | |||
gr.resource_id is null | |||
</if> | |||
<if test="projectId != null"> | |||
gr.resource_id = #{projectId} | |||
</if> | |||
where | |||
gr.organization_uuid = #{organizationUuid,jdbcType=VARCHAR} and | |||
gr.resource_id is null and | |||
<choose> | |||
<when test="groupId != null"> | |||
gr.group_id = #{groupId,jdbcType=BIGINT} | |||
</when> | |||
<otherwise> | |||
gr.group_id is null | |||
</otherwise> | |||
</choose> | |||
</select> | |||
<select id="selectAnyonePermissions" parameterType="map" resultType="String"> | |||
<select id="selectProjectPermissionsOfGroup" parameterType="map" resultType="String"> | |||
select gr.role | |||
from group_roles gr | |||
where gr.group_id is null | |||
and | |||
<if test="projectId == null"> | |||
gr.resource_id is null | |||
</if> | |||
<if test="projectId != null"> | |||
gr.resource_id = #{projectId} | |||
</if> | |||
where | |||
gr.organization_uuid = #{organizationUuid,jdbcType=VARCHAR} and | |||
gr.resource_id = #{projectId,jdbcType=BIGINT} and | |||
<choose> | |||
<when test="groupId != null"> | |||
gr.group_id = #{groupId,jdbcType=BIGINT} | |||
</when> | |||
<otherwise> | |||
gr.group_id is null | |||
</otherwise> | |||
</choose> | |||
</select> | |||
<select id="countRowsByRootComponentId" parameterType="long" resultType="int"> |
@@ -174,6 +174,59 @@ public class AuthorizationDaoTest { | |||
USER, "admin")).isEmpty(); | |||
} | |||
@Test | |||
public void countRemainingUserIdsWithGlobalPermissionIfExcludeGroup() { | |||
// users with global permission "perm1" : | |||
// - "u1" and "u2" through group "g1" | |||
// - "u1" and "u3" through group "g2" | |||
// - "u4" | |||
UserDto user1 = db.users().insertUser(); | |||
UserDto user2 = db.users().insertUser(); | |||
UserDto user3 = db.users().insertUser(); | |||
UserDto user4 = db.users().insertUser(); | |||
UserDto user5 = db.users().insertUser(); | |||
OrganizationDto org = OrganizationTesting.insert(db, newOrganizationDto()); | |||
GroupDto group1 = db.users().insertGroup(org, "g1"); | |||
db.users().insertPermissionOnGroup(group1, "perm1"); | |||
db.users().insertPermissionOnGroup(group1, "perm2"); | |||
db.users().insertMember(group1, user1); | |||
db.users().insertMember(group1, user2); | |||
GroupDto group2 = db.users().insertGroup(org, "g2"); | |||
db.users().insertPermissionOnGroup(group2, "perm1"); | |||
db.users().insertPermissionOnGroup(group2, "perm2"); | |||
db.users().insertMember(group2, user1); | |||
db.users().insertMember(group2, user3); | |||
// group3 has the permission "perm1" but has no users | |||
GroupDto group3 = db.users().insertGroup(org, "g2"); | |||
db.users().insertPermissionOnGroup(group3, "perm1"); | |||
db.users().insertPermissionOnUser(user4, "perm1"); | |||
db.users().insertPermissionOnUser(user4, "perm2"); | |||
db.users().insertPermissionOnAnyone(org, "perm1"); | |||
// excluding group "g1" -> remain u1, u3 and u4 | |||
assertThat(underTest.countRemainingUserIdsWithGlobalPermissionIfExcludeGroup(db.getSession(), | |||
org.getUuid(), "perm1", group1.getId())).isEqualTo(3); | |||
// excluding group "g2" -> remain u1, u2 and u4 | |||
assertThat(underTest.countRemainingUserIdsWithGlobalPermissionIfExcludeGroup(db.getSession(), | |||
org.getUuid(), "perm1", group2.getId())).isEqualTo(3); | |||
// excluding group "g3" -> remain u1, u2, u3 and u4 | |||
assertThat(underTest.countRemainingUserIdsWithGlobalPermissionIfExcludeGroup(db.getSession(), | |||
org.getUuid(), "perm1", group3.getId())).isEqualTo(4); | |||
// nobody has the permission | |||
assertThat(underTest.countRemainingUserIdsWithGlobalPermissionIfExcludeGroup(db.getSession(), | |||
org.getUuid(), "missingPermission", group1.getId())).isEqualTo(0); | |||
} | |||
@Test | |||
public void keep_authorized_project_ids_for_user() { | |||
db.prepareDbUnit(getClass(), "keep_authorized_project_ids_for_user.xml"); |
@@ -30,6 +30,8 @@ import org.sonar.api.web.UserRole; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.organization.OrganizationDto; | |||
import org.sonar.db.organization.OrganizationTesting; | |||
import org.sonar.db.user.GroupDto; | |||
import static java.util.Arrays.asList; | |||
@@ -43,6 +45,7 @@ import static org.sonar.core.permission.GlobalPermissions.PROVISIONING; | |||
import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION; | |||
import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; | |||
import static org.sonar.db.component.ComponentTesting.newProjectDto; | |||
import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto; | |||
import static org.sonar.db.user.GroupTesting.newGroupDto; | |||
public class GroupPermissionDaoTest { | |||
@@ -277,43 +280,51 @@ public class GroupPermissionDaoTest { | |||
} | |||
@Test | |||
public void selectGroupPermissions() { | |||
GroupDto group1 = db.users().insertGroup(newGroupDto()); | |||
GroupDto group2 = db.users().insertGroup(newGroupDto()); | |||
public void selectGlobalPermissionsOfGroup() { | |||
OrganizationDto org1 = OrganizationTesting.insert(db, newOrganizationDto()); | |||
OrganizationDto org2 = OrganizationTesting.insert(db, newOrganizationDto()); | |||
GroupDto group1 = db.users().insertGroup(org1, "group1"); | |||
GroupDto group2 = db.users().insertGroup(org2, "group2"); | |||
ComponentDto project = db.components().insertProject(); | |||
db.users().insertPermissionOnAnyone("perm1"); | |||
db.users().insertPermissionOnAnyone(org1, "perm1"); | |||
db.users().insertPermissionOnGroup(group1, "perm2"); | |||
db.users().insertPermissionOnGroup(group1, "perm3"); | |||
db.users().insertPermissionOnGroup(group2, "perm4"); | |||
db.users().insertProjectPermissionOnGroup(group1, "perm5", project); | |||
db.users().insertProjectPermissionOnAnyone("perm6", project); | |||
db.users().insertProjectPermissionOnAnyone(org1, "perm6", project); | |||
// select global permissions on group | |||
assertThat(underTest.selectGroupPermissions(dbSession, group1.getId(), null)).containsOnly("perm2", "perm3"); | |||
assertThat(underTest.selectGroupPermissions(dbSession, UNKNOWN_GROUP_ID, null)).isEmpty(); | |||
assertThat(underTest.selectGlobalPermissionsOfGroup(dbSession, org1.getUuid(), group1.getId())).containsOnly("perm2", "perm3"); | |||
assertThat(underTest.selectGlobalPermissionsOfGroup(dbSession, org2.getUuid(), group2.getId())).containsOnly("perm4"); | |||
assertThat(underTest.selectGlobalPermissionsOfGroup(dbSession, org1.getUuid(), null)).containsOnly("perm1"); | |||
// select project permissions on group | |||
assertThat(underTest.selectGroupPermissions(dbSession, group1.getId(), project.getId())).containsOnly("perm5"); | |||
assertThat(underTest.selectGroupPermissions(dbSession, group1.getId(), UNKNOWN_PROJECT_ID)).isEmpty(); | |||
// group1 is not in org2 | |||
assertThat(underTest.selectGlobalPermissionsOfGroup(dbSession, org2.getUuid(), group1.getId())).isEmpty(); | |||
assertThat(underTest.selectGlobalPermissionsOfGroup(dbSession, org2.getUuid(), null)).isEmpty(); | |||
} | |||
@Test | |||
public void selectAnyonePermissions() { | |||
GroupDto group1 = db.users().insertGroup(newGroupDto()); | |||
ComponentDto project = db.components().insertProject(); | |||
public void selectProjectPermissionsOfGroup() { | |||
OrganizationDto org1 = OrganizationTesting.insert(db, newOrganizationDto()); | |||
GroupDto group1 = db.users().insertGroup(org1, "group1"); | |||
ComponentDto project1 = db.components().insertProject(); | |||
ComponentDto project2 = db.components().insertProject(); | |||
db.users().insertPermissionOnAnyone("perm1"); | |||
db.users().insertPermissionOnAnyone(org1, "perm1"); | |||
db.users().insertPermissionOnGroup(group1, "perm2"); | |||
db.users().insertProjectPermissionOnGroup(group1, "perm3", project); | |||
db.users().insertProjectPermissionOnAnyone("perm4", project); | |||
// select global permissions on group | |||
assertThat(underTest.selectAnyonePermissions(dbSession, null)).containsOnly("perm1"); | |||
// select project permissions on group | |||
assertThat(underTest.selectAnyonePermissions(dbSession, project.getId())).containsOnly("perm4"); | |||
assertThat(underTest.selectAnyonePermissions(dbSession, UNKNOWN_PROJECT_ID)).isEmpty(); | |||
db.users().insertProjectPermissionOnGroup(group1, "perm3", project1); | |||
db.users().insertProjectPermissionOnGroup(group1, "perm4", project1); | |||
db.users().insertProjectPermissionOnGroup(group1, "perm5", project2); | |||
db.users().insertProjectPermissionOnAnyone(org1, "perm6", project1); | |||
assertThat(underTest.selectProjectPermissionsOfGroup(dbSession, org1.getUuid(), group1.getId(), project1.getId())) | |||
.containsOnly("perm3", "perm4"); | |||
assertThat(underTest.selectProjectPermissionsOfGroup(dbSession, org1.getUuid(), group1.getId(), project2.getId())) | |||
.containsOnly("perm5"); | |||
assertThat(underTest.selectProjectPermissionsOfGroup(dbSession, org1.getUuid(), null, project1.getId())) | |||
.containsOnly("perm6"); | |||
assertThat(underTest.selectProjectPermissionsOfGroup(dbSession, org1.getUuid(), null, project2.getId())) | |||
.isEmpty(); | |||
} | |||
@Test |
@@ -167,15 +167,21 @@ public class UserDbTester { | |||
} | |||
public List<String> selectGroupPermissions(GroupDto group, @Nullable ComponentDto project) { | |||
return db.getDbClient().groupPermissionDao().selectGroupPermissions(db.getSession(), group.getId(), project == null ? null : project.getId()); | |||
} | |||
/** | |||
* @deprecated does not support organizations | |||
*/ | |||
@Deprecated | |||
public List<String> selectAnyonePermissions(@Nullable ComponentDto project) { | |||
return db.getDbClient().groupPermissionDao().selectAnyonePermissions(db.getSession(), project == null ? null : project.getId()); | |||
if (project == null) { | |||
return db.getDbClient().groupPermissionDao().selectGlobalPermissionsOfGroup(db.getSession(), | |||
group.getOrganizationUuid(), group.getId()); | |||
} | |||
return db.getDbClient().groupPermissionDao().selectProjectPermissionsOfGroup(db.getSession(), | |||
group.getOrganizationUuid(), group.getId(), project.getId()); | |||
} | |||
public List<String> selectAnyonePermissions(OrganizationDto org, @Nullable ComponentDto project) { | |||
if (project == null) { | |||
return db.getDbClient().groupPermissionDao().selectGlobalPermissionsOfGroup(db.getSession(), | |||
org.getUuid(), null); | |||
} | |||
return db.getDbClient().groupPermissionDao().selectProjectPermissionsOfGroup(db.getSession(), | |||
org.getUuid(), null, project.getId()); | |||
} | |||
// USER PERMISSIONS |