/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.organization.ws;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.config.MapSettings;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
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.permission.OrganizationPermission;
import org.sonar.db.permission.template.PermissionTemplateDto;
import org.sonar.db.permission.template.PermissionTemplateUserDto;
import org.sonar.db.property.PropertyDto;
import org.sonar.db.property.PropertyQuery;
import org.sonar.db.user.GroupDto;
import org.sonar.db.user.UserDto;
import org.sonar.server.es.EsTester;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.index.UserIndex;
import org.sonar.server.user.index.UserIndexDefinition;
import org.sonar.server.user.index.UserIndexer;
import org.sonar.server.user.index.UserQuery;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;
import static org.sonar.api.CoreProperties.DEFAULT_ISSUE_ASSIGNEE;
import static org.sonar.api.web.UserRole.CODEVIEWER;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
import static org.sonar.db.permission.OrganizationPermission.ADMINISTER_QUALITY_GATES;
import static org.sonar.db.permission.OrganizationPermission.SCAN;
import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_ORGANIZATION;
public class RemoveMemberActionTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSession = UserSessionRule.standalone().logIn().setRoot();
@Rule
public EsTester es = new EsTester(new UserIndexDefinition(new MapSettings()));
@Rule
public DbTester db = DbTester.create();
private DbClient dbClient = db.getDbClient();
private DbSession dbSession = db.getSession();
private UserIndex userIndex = new UserIndex(es.client());
private UserIndexer userIndexer = new UserIndexer(dbClient, es.client());
private WsActionTester ws = new WsActionTester(new RemoveMemberAction(dbClient, userSession, userIndexer));
private OrganizationDto organization;
private ComponentDto project;
private UserDto user;
@Before
public void setUp() {
organization = db.organizations().insert();
project = db.components().insertPrivateProject(organization);
user = db.users().insertUser();
db.organizations().addMember(organization, user);
userIndexer.index(user.getLogin());
UserDto adminUser = db.users().insertAdminByUserPermission(organization);
db.organizations().addMember(organization, adminUser);
userIndexer.index(adminUser.getLogin());
}
@Test
public void definition() {
WebService.Action definition = ws.getDef();
assertThat(definition.key()).isEqualTo("remove_member");
assertThat(definition.since()).isEqualTo("6.4");
assertThat(definition.isPost()).isTrue();
assertThat(definition.isInternal()).isTrue();
assertThat(definition.params()).extracting(WebService.Param::key).containsOnly("organization", "login");
WebService.Param organization = definition.param("organization");
assertThat(organization.isRequired()).isTrue();
WebService.Param login = definition.param("login");
assertThat(login.isRequired()).isTrue();
}
@Test
public void no_content_http_204_returned() {
TestResponse result = call(organization.getKey(), user.getLogin());
assertThat(result.getStatus()).isEqualTo(HTTP_NO_CONTENT);
assertThat(result.getInput()).isEmpty();
}
@Test
public void remove_member_from_db_and_user_index() {
assertMember(organization.getUuid(), user);
call(organization.getKey(), user.getLogin());
assertNotAMember(organization.getUuid(), user);
}
@Test
public void remove_organization_permissions() {
UserDto anotherUser = db.users().insertUser();
OrganizationDto anotherOrganization = db.organizations().insert();
ComponentDto anotherProject = db.components().insertPrivateProject(anotherOrganization);
assertMember(organization.getUuid(), user);
db.users().insertPermissionOnUser(organization, user, ADMINISTER);
db.users().insertPermissionOnUser(organization, user, SCAN);
db.users().insertPermissionOnUser(anotherOrganization, user, ADMINISTER);
db.users().insertPermissionOnUser(anotherOrganization, user, SCAN);
db.users().insertPermissionOnUser(organization, anotherUser, ADMINISTER);
db.users().insertPermissionOnUser(organization, anotherUser, SCAN);
db.users().insertProjectPermissionOnUser(user, CODEVIEWER, project);
db.users().insertProjectPermissionOnUser(user, USER, project);
db.users().insertProjectPermissionOnUser(user, CODEVIEWER, anotherProject);
db.users().insertProjectPermissionOnUser(user, USER, anotherProject);
db.users().insertProjectPermissionOnUser(anotherUser, CODEVIEWER, project);
db.users().insertProjectPermissionOnUser(anotherUser, USER, project);
call(organization.getKey(), user.getLogin());
assertNotAMember(organization.getUuid(), user);
assertOrgPermissionsOfUser(user, organization);
assertOrgPermissionsOfUser(user, anotherOrganization, ADMINISTER, SCAN);
assertOrgPermissionsOfUser(anotherUser, organization, ADMINISTER, SCAN);
assertProjectPermissionsOfUser(user, project);
assertProjectPermissionsOfUser(user, anotherProject, CODEVIEWER, USER);
assertProjectPermissionsOfUser(anotherUser, project, CODEVIEWER, USER);
}
@Test
public void remove_template_permissions() {
OrganizationDto anotherOrganization = db.organizations().insert();
UserDto anotherUser = db.users().insertUser();
PermissionTemplateDto template = db.permissionTemplates().insertTemplate(organization);
PermissionTemplateDto anotherTemplate = db.permissionTemplates().insertTemplate(anotherOrganization);
String permission = "PERMISSION";
db.permissionTemplates().addUserToTemplate(template.getId(), user.getId(), permission);
db.permissionTemplates().addUserToTemplate(template.getId(), anotherUser.getId(), permission);
db.permissionTemplates().addUserToTemplate(anotherTemplate.getId(), user.getId(), permission);
call(organization.getKey(), user.getLogin());
assertThat(dbClient.permissionTemplateDao().selectUserPermissionsByTemplateId(dbSession, template.getId())).extracting(PermissionTemplateUserDto::getUserId)
.containsOnly(anotherUser.getId());
assertThat(dbClient.permissionTemplateDao().selectUserPermissionsByTemplateId(dbSession, anotherTemplate.getId())).extracting(PermissionTemplateUserDto::getUserId)
.containsOnly(user.getId());
}
@Test
public void remove_from_organization_groups() {
OrganizationDto anotherOrganization = db.organizations().insert();
UserDto anotherUser = db.users().insertUser();
GroupDto group = db.users().insertGroup(organization);
GroupDto anotherGroup = db.users().insertGroup(anotherOrganization);
db.users().insertMembers(group, user, anotherUser);
db.users().insertMembers(anotherGroup, user, anotherUser);
call(organization.getKey(), user.getLogin());
assertThat(dbClient.groupMembershipDao().selectGroupIdsByUserId(dbSession, user.getId()))
.containsOnly(anotherGroup.getId());
assertThat(dbClient.groupMembershipDao().selectGroupIdsByUserId(dbSession, anotherUser.getId()))
.containsOnly(group.getId(), anotherGroup.getId());
}
@Test
public void remove_from_default_organization_group() {
GroupDto defaultGroup = db.users().insertDefaultGroup(organization, "default");
db.users().insertMember(defaultGroup, user);
call(organization.getKey(), user.getLogin());
assertThat(dbClient.groupMembershipDao().selectGroupIdsByUserId(dbSession, user.getId())).isEmpty();
}
@Test
public void remove_from_org_properties() {
OrganizationDto anotherOrganization = db.organizations().insert();
ComponentDto anotherProject = db.components().insertPrivateProject(anotherOrganization);
UserDto anotherUser = db.users().insertUser();
insertProperty("KEY_11", "VALUE", project.getId(), user.getId());
insertProperty("KEY_12", "VALUE", project.getId(), user.getId());
insertProperty("KEY_11", "VALUE", project.getId(), anotherUser.getId());
insertProperty("KEY_11", "VALUE", anotherProject.getId(), user.getId());
call(organization.getKey(), user.getLogin());
assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setComponentId(project.getId()).build(), dbSession))
.hasSize(1).extracting(PropertyDto::getUserId).containsOnly(anotherUser.getId());
assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setComponentId(anotherProject.getId()).build(), dbSession)).extracting(PropertyDto::getUserId)
.hasSize(1).containsOnly(user.getId());
}
@Test
public void remove_from_default_assignee_properties() {
OrganizationDto anotherOrganization = db.organizations().insert();
ComponentDto anotherProject = db.components().insertPrivateProject(anotherOrganization);
UserDto anotherUser = db.users().insertUser();
insertProperty(DEFAULT_ISSUE_ASSIGNEE, user.getLogin(), project.getId(), null);
insertProperty("ANOTHER_KEY", user.getLogin(), project.getId(), null);
insertProperty(DEFAULT_ISSUE_ASSIGNEE, anotherUser.getLogin(), project.getId(), null);
insertProperty(DEFAULT_ISSUE_ASSIGNEE, user.getLogin(), anotherProject.getId(), null);
call(organization.getKey(), user.getLogin());
assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setComponentId(project.getId()).build(), dbSession))
.hasSize(2).extracting(PropertyDto::getKey, PropertyDto::getValue)
.containsOnly(tuple("ANOTHER_KEY", user.getLogin()), tuple(DEFAULT_ISSUE_ASSIGNEE, anotherUser.getLogin()));
assertThat(dbClient.propertiesDao().selectByQuery(PropertyQuery.builder().setComponentId(anotherProject.getId()).build(), dbSession)).extracting(PropertyDto::getValue)
.hasSize(1).containsOnly(user.getLogin());
}
@Test
public void user_is_removed_only_from_designated_organization() {
OrganizationDto anotherOrg = db.organizations().insert();
db.organizations().addMember(anotherOrg, user);
call(organization.getKey(), user.getLogin());
assertMember(anotherOrg.getUuid(), user);
}
@Test
public void remove_member_as_organization_admin() {
userSession.logIn().addPermission(ADMINISTER, organization);
call(organization.getKey(), user.getLogin());
assertNotAMember(organization.getUuid(), user);
}
@Test
public void do_not_fail_if_user_already_removed_from_organization() {
call(organization.getKey(), user.getLogin());
call(organization.getKey(), user.getLogin());
}
@Test
public void fail_if_login_does_not_exist() {
expectedException.expect(NotFoundException.class);
expectedException.expectMessage("User 'login-42' is not found");
call(organization.getKey(), "login-42");
}
@Test
public void fail_if_organization_does_not_exist() {
expectedException.expect(NotFoundException.class);
expectedException.expectMessage("Organization 'org-42' is not found");
call("org-42", user.getLogin());
}
@Test
public void fail_if_no_login_provided() {
expectedException.expect(IllegalArgumentException.class);
call(organization.getKey(), null);
}
@Test
public void fail_if_no_organization_provided() {
expectedException.expect(IllegalArgumentException.class);
call(null, user.getLogin());
}
@Test
public void fail_if_insufficient_permissions() {
userSession.logIn().addPermission(ADMINISTER_QUALITY_GATES, organization);
expectedException.expect(ForbiddenException.class);
call(organization.getKey(), user.getLogin());
}
@Test
public void remove_org_admin_is_allowed_when_another_org_admin_exists() throws Exception {
OrganizationDto anotherOrganization = db.organizations().insert();
UserDto admin1 = db.users().insertAdminByUserPermission(anotherOrganization);
db.organizations().addMember(anotherOrganization, admin1);
userIndexer.index(admin1.getLogin());
UserDto admin2 = db.users().insertAdminByUserPermission(anotherOrganization);
db.organizations().addMember(anotherOrganization, admin2);
userIndexer.index(admin2.getLogin());
call(anotherOrganization.getKey(), admin1.getLogin());
assertNotAMember(anotherOrganization.getUuid(), admin1);
assertMember(anotherOrganization.getUuid(), admin2);
}
@Test
public void fail_to_remove_last_organization_admin() {
OrganizationDto anotherOrganization = db.organizations().insert();
UserDto admin = db.users().insertAdminByUserPermission(anotherOrganization);
db.organizations().addMember(anotherOrganization, admin);
UserDto user = db.users().insertUser();
db.organizations().addMember(anotherOrganization, user);
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("The last administrator member cannot be removed");
call(anotherOrganization.getKey(), admin.getLogin());
}
private TestResponse call(@Nullable String organizationKey, @Nullable String login) {
TestRequest request = ws.newRequest();
setNullable(organizationKey, o -> request.setParam(PARAM_ORGANIZATION, o));
setNullable(login, l -> request.setParam("login", l));
return request.execute();
}
private void assertNotAMember(String organizationUuid, UserDto user) {
assertThat(dbClient.organizationMemberDao().select(dbSession, organizationUuid, user.getId())).isNotPresent();
assertThat(userIndex.search(UserQuery.builder().setOrganizationUuid(organizationUuid).setTextQuery(user.getLogin()).build(), new SearchOptions()).getDocs()).isEmpty();
}
private void assertMember(String organizationUuid, UserDto user) {
assertThat(dbClient.organizationMemberDao().select(dbSession, organizationUuid, user.getId())).isPresent();
assertThat(userIndex.getNullableByLogin(user.getLogin()).organizationUuids()).contains(organizationUuid);
}
private void assertOrgPermissionsOfUser(UserDto user, OrganizationDto organization, OrganizationPermission... permissions) {
assertThat(dbClient.userPermissionDao().selectGlobalPermissionsOfUser(dbSession, user.getId(), organization.getUuid()).stream()
.map(OrganizationPermission::fromKey))
.containsOnly(permissions);
}
private void assertProjectPermissionsOfUser(UserDto user, ComponentDto project, String... permissions) {
assertThat(dbClient.userPermissionDao().selectProjectPermissionsOfUser(dbSession, user.getId(), project.getId())).containsOnly(permissions);
}
private void insertProperty(String key, @Nullable String value, @Nullable Long resourceId, @Nullable Integer userId) {
PropertyDto dto = new PropertyDto().setKey(key)
.setResourceId(resourceId == null ? null : resourceId)
.setUserId(userId == null ? null : userId)
.setValue(value);
db.properties().insertProperty(dto);
}
}