/* * 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 it.organization; import com.sonar.orchestrator.Orchestrator; import com.sonar.orchestrator.build.BuildFailureException; import it.Category6Suite; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonarqube.ws.Organizations; import org.sonarqube.ws.WsComponents; import org.sonarqube.ws.WsUsers; import org.sonarqube.ws.client.HttpException; import org.sonarqube.ws.client.PostRequest; import org.sonarqube.ws.client.WsClient; import org.sonarqube.ws.client.component.ComponentsService; import org.sonarqube.ws.client.organization.CreateWsRequest; import org.sonarqube.ws.client.organization.OrganizationService; import org.sonarqube.ws.client.organization.SearchWsRequest; import org.sonarqube.ws.client.organization.UpdateWsRequest; import org.sonarqube.ws.client.permission.AddUserWsRequest; import org.sonarqube.ws.client.permission.PermissionsService; import org.sonarqube.ws.client.user.GroupsRequest; import util.user.GroupManagement; import util.user.Groups; import util.user.UserRule; import static it.Category6Suite.enableOrganizationsSupport; import static java.util.Collections.singletonList; import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static util.ItUtils.deleteOrganizationsIfExists; import static util.ItUtils.newAdminWsClient; import static util.ItUtils.newUserWsClient; import static util.ItUtils.newWsClient; import static util.ItUtils.resetSettings; import static util.ItUtils.runProjectAnalysis; import static util.ItUtils.setServerProperty; public class OrganizationTest { private static final String DEFAULT_ORGANIZATION_KEY = "default-organization"; private static final String NAME = "Foo Company"; private static final String KEY = "foo-company"; private static final String DESCRIPTION = "the description of Foo company"; private static final String URL = "https://www.foo.fr"; private static final String AVATAR_URL = "https://www.foo.fr/corporate_logo.png"; private static final String SETTING_ANYONE_CAN_CREATE_ORGANIZATIONS = "sonar.organizations.anyoneCanCreate"; private static final String USER_LOGIN = "foo"; @ClassRule public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; @ClassRule public static UserRule userRule = UserRule.from(orchestrator); @Rule public ExpectedException expectedException = ExpectedException.none(); private WsClient adminClient = newAdminWsClient(orchestrator); private OrganizationService anonymousOrganizationService = newWsClient(orchestrator).organizations(); private OrganizationService adminOrganizationService = adminClient.organizations(); @BeforeClass public static void enableOrganizations() throws Exception { enableOrganizationsSupport(); } @Before public void setUp() throws Exception { resetSettings(orchestrator, null, SETTING_ANYONE_CAN_CREATE_ORGANIZATIONS); deleteOrganizationsIfExists(orchestrator, KEY, "an-org"); userRule.deactivateUsers(USER_LOGIN); } @After public void tearDown() throws Exception { deleteOrganizationsIfExists(orchestrator, KEY, "an-org"); } @Test public void create_update_delete_organizations_and_check_security() { verifyOrganizationDoesNotExit(KEY); Organizations.Organization createdOrganization = adminOrganizationService.create(new CreateWsRequest.Builder() .setName(NAME) .setKey(KEY) .setDescription(DESCRIPTION) .setUrl(URL) .setAvatar(AVATAR_URL) .build()) .getOrganization(); assertThat(createdOrganization.getName()).isEqualTo(NAME); assertThat(createdOrganization.getKey()).isEqualTo(KEY); assertThat(createdOrganization.getDescription()).isEqualTo(DESCRIPTION); assertThat(createdOrganization.getUrl()).isEqualTo(URL); assertThat(createdOrganization.getAvatar()).isEqualTo(AVATAR_URL); verifySingleSearchResult(createdOrganization, NAME, DESCRIPTION, URL, AVATAR_URL); // update by id adminOrganizationService.update(new UpdateWsRequest.Builder() .setKey(createdOrganization.getKey()) .setName("new name") .setDescription("new description") .setUrl("new url") .setAvatar("new avatar url") .build()); verifySingleSearchResult(createdOrganization, "new name", "new description", "new url", "new avatar url"); // update by key adminOrganizationService.update(new UpdateWsRequest.Builder() .setKey(createdOrganization.getKey()) .setName("new name 2") .setDescription("new description 2") .setUrl("new url 2") .setAvatar("new avatar url 2") .build()); verifySingleSearchResult(createdOrganization, "new name 2", "new description 2", "new url 2", "new avatar url 2"); // remove optional fields adminOrganizationService.update(new UpdateWsRequest.Builder() .setKey(createdOrganization.getKey()) .setName("new name 3") .setDescription("") .setUrl("") .setAvatar("") .build()); verifySingleSearchResult(createdOrganization, "new name 3", null, null, null); // delete organization adminOrganizationService.delete(createdOrganization.getKey()); verifyOrganizationDoesNotExit(KEY); adminOrganizationService.create(new CreateWsRequest.Builder() .setName(NAME) .setKey(KEY) .build()) .getOrganization(); verifySingleSearchResult(createdOrganization, NAME, null, null, null); // verify anonymous can't create update nor delete an organization by default verifyAnonymousNotAuthorized(service -> service.create(new CreateWsRequest.Builder().setName("An org").build())); verifyUserNotAuthenticated(service -> service.update(new UpdateWsRequest.Builder().setKey(KEY).setName("new name").build())); verifyUserNotAuthenticated(service -> service.delete(KEY)); // verify logged in user without any permission can't create update nor delete an organization by default userRule.createUser(USER_LOGIN, USER_LOGIN); verifyUserNotAuthorized(USER_LOGIN, USER_LOGIN, service -> service.create(new CreateWsRequest.Builder().setName("An org").build())); verifyUserNotAuthorized(USER_LOGIN, USER_LOGIN, service -> service.update(new UpdateWsRequest.Builder().setKey(KEY).setName("new name").build())); verifyUserNotAuthorized(USER_LOGIN, USER_LOGIN, service -> service.delete(KEY)); setServerProperty(orchestrator, SETTING_ANYONE_CAN_CREATE_ORGANIZATIONS, "true"); // verify anonymous still can't create update nor delete an organization if property is true verifyUserNotAuthenticated(service -> service.create(new CreateWsRequest.Builder().setName("An org").build())); verifyUserNotAuthenticated(service -> service.update(new UpdateWsRequest.Builder().setKey(KEY).setName("new name").build())); verifyUserNotAuthenticated(service -> service.delete(KEY)); // verify logged in user without any permission can't create nor update nor delete an organization if property is true verifyUserNotAuthorized(USER_LOGIN, USER_LOGIN, service -> service.update(new UpdateWsRequest.Builder().setKey(KEY).setName("new name").build())); verifyUserNotAuthorized(USER_LOGIN, USER_LOGIN, service -> service.delete(KEY)); // clean-up adminOrganizationService.delete(KEY); verifySingleSearchResult( verifyUserAuthorized(USER_LOGIN, USER_LOGIN, service -> service.create(new CreateWsRequest.Builder().setName("An org").build())).getOrganization(), "An org", null, null, null); } private void verifyAnonymousNotAuthorized(Consumer<OrganizationService> consumer) { try { consumer.accept(anonymousOrganizationService); fail("An HttpException should have been raised"); } catch (HttpException e) { assertThat(e.code()).isEqualTo(403); } } private void verifyUserNotAuthenticated(Consumer<OrganizationService> consumer) { try { consumer.accept(anonymousOrganizationService); fail("An HttpException should have been raised"); } catch (HttpException e) { assertThat(e.code()).isEqualTo(401); } } private void verifyUserNotAuthorized(String login, String password, Consumer<OrganizationService> consumer) { try { OrganizationService organizationService = newUserWsClient(orchestrator, login, password).organizations(); consumer.accept(organizationService); fail("An HttpException should have been raised"); } catch (HttpException e) { assertThat(e.code()).isEqualTo(403); } } private <T> T verifyUserAuthorized(String login, String password, Function<OrganizationService, T> consumer) { OrganizationService organizationService = newUserWsClient(orchestrator, login, password).organizations(); return consumer.apply(organizationService); } @Test public void create_generates_key_from_name() { // create organization without key String name = "Foo Company to keyize"; String expectedKey = "foo-company-to-keyize"; Organizations.Organization createdOrganization = adminOrganizationService.create(new CreateWsRequest.Builder() .setName(name) .build()) .getOrganization(); assertThat(createdOrganization.getKey()).isEqualTo(expectedKey); verifySingleSearchResult(createdOrganization, name, null, null, null); // clean-up adminOrganizationService.delete(expectedKey); } @Test public void default_organization_can_not_be_deleted() { try { adminOrganizationService.delete(DEFAULT_ORGANIZATION_KEY); fail("a HttpException should have been raised"); } catch (HttpException e) { assertThat(e.code()).isEqualTo(400); } } @Test public void create_fails_if_user_is_not_root() { userRule.createUser(USER_LOGIN, USER_LOGIN); CreateWsRequest createWsRequest = new CreateWsRequest.Builder() .setName("bla bla") .build(); OrganizationService fooUserOrganizationService = newUserWsClient(orchestrator, USER_LOGIN, USER_LOGIN).organizations(); expect403HttpError(() -> fooUserOrganizationService.create(createWsRequest)); userRule.setRoot(USER_LOGIN); assertThat(fooUserOrganizationService.create(createWsRequest).getOrganization().getKey()).isEqualTo("bla-bla"); // delete org, attempt recreate when no root anymore and ensure it can't anymore fooUserOrganizationService.delete("bla-bla"); userRule.unsetRoot(USER_LOGIN); expect403HttpError(() -> fooUserOrganizationService.create(createWsRequest)); } @Test public void an_organization_member_can_analyze_project() { verifyOrganizationDoesNotExit(KEY); Organizations.Organization createdOrganization = adminOrganizationService.create(new CreateWsRequest.Builder() .setName(KEY) .setKey(KEY) .build()) .getOrganization(); verifySingleSearchResult(createdOrganization, KEY, null, null, null); userRule.createUser(USER_LOGIN, USER_LOGIN); userRule.removeGroups("sonar-users"); adminOrganizationService.addMember(KEY, USER_LOGIN); addPermissionsToUser(KEY, USER_LOGIN, "provisioning", "scan"); runProjectAnalysis(orchestrator, "shared/xoo-sample", "sonar.organization", KEY, "sonar.login", USER_LOGIN, "sonar.password", USER_LOGIN); ComponentsService componentsService = newUserWsClient(orchestrator, USER_LOGIN, USER_LOGIN).components(); assertThat(searchSampleProject(KEY, componentsService).getComponentsList()).hasSize(1); } @Test public void by_default_anonymous_cannot_analyse_project_on_organization() { verifyOrganizationDoesNotExit(KEY); Organizations.Organization createdOrganization = adminOrganizationService.create(new CreateWsRequest.Builder() .setName(KEY) .setKey(KEY) .build()) .getOrganization(); verifySingleSearchResult(createdOrganization, KEY, null, null, null); try { runProjectAnalysis(orchestrator, "shared/xoo-sample", "sonar.organization", KEY); fail(); } catch (BuildFailureException e) { assertThat(e.getResult().getLogs()).contains("Insufficient privileges"); } ComponentsService componentsService = newAdminWsClient(orchestrator).components(); assertThat(searchSampleProject(KEY, componentsService).getComponentsCount()).isEqualTo(0); } @Test public void by_default_anonymous_can_browse_project_on_organization() { adminOrganizationService.create(new CreateWsRequest.Builder() .setName(KEY) .setKey(KEY) .build()) .getOrganization(); runProjectAnalysis(orchestrator, "shared/xoo-sample", "sonar.organization", KEY, "sonar.login", "admin", "sonar.password", "admin"); ComponentsService componentsService = newWsClient(orchestrator).components(); assertThat(searchSampleProject(KEY, componentsService).getComponentsList()).hasSize(1); } private void addPermissionsToUser(String orgKeyAndName, String login, String permission, String... otherPermissions) { PermissionsService permissionsService = newAdminWsClient(orchestrator).permissions(); permissionsService.addUser(new AddUserWsRequest().setLogin(login).setOrganization(orgKeyAndName).setPermission(permission)); for (String otherPermission : otherPermissions) { permissionsService.addUser(new AddUserWsRequest().setLogin(login).setOrganization(orgKeyAndName).setPermission(otherPermission)); } } @Test public void deleting_an_organization_also_deletes_projects_and_check_security() { verifyOrganizationDoesNotExit(KEY); Organizations.Organization createdOrganization = adminOrganizationService.create(new CreateWsRequest.Builder() .setName(KEY) .setKey(KEY) .build()) .getOrganization(); verifySingleSearchResult(createdOrganization, KEY, null, null, null); GroupManagement groupManagement = userRule.forOrganization(KEY); userRule.createUser(USER_LOGIN, USER_LOGIN); adminOrganizationService.addMember(KEY, USER_LOGIN); groupManagement.createGroup("grp1"); groupManagement.createGroup("grp2"); groupManagement.associateGroupsToUser(USER_LOGIN, "grp1", "grp2"); assertThat(groupManagement.getUserGroups(USER_LOGIN).getGroups()) .extracting(Groups.Group::getName) .contains("grp1", "grp2"); addPermissionsToUser(KEY, USER_LOGIN, "provisioning", "scan"); runProjectAnalysis(orchestrator, "shared/xoo-sample", "sonar.organization", KEY, "sonar.login", USER_LOGIN, "sonar.password", USER_LOGIN); ComponentsService componentsService = newAdminWsClient(orchestrator).components(); assertThat(searchSampleProject(KEY, componentsService).getComponentsList()).hasSize(1); adminOrganizationService.delete(KEY); expect404HttpError(() -> searchSampleProject(KEY, componentsService)); verifyOrganizationDoesNotExit(KEY); } @Test public void return_groups_belonging_to_a_user_on_an_organization() throws Exception { String userLogin = randomAlphabetic(10); String groupName = randomAlphabetic(10); adminClient.organizations().create(new CreateWsRequest.Builder().setKey(KEY).setName(KEY).build()).getOrganization(); userRule.createUser(userLogin, userLogin); adminOrganizationService.addMember(KEY, userLogin); adminClient.wsConnector().call(new PostRequest("api/user_groups/create") .setParam("name", groupName) .setParam("description", groupName) .setParam("organization", KEY)).failIfNotSuccessful(); adminClient.wsConnector().call(new PostRequest("api/user_groups/add_user") .setParam("login", userLogin) .setParam("name", groupName) .setParam("organization", KEY)).failIfNotSuccessful(); List<WsUsers.GroupsWsResponse.Group> result = adminClient.users().groups( GroupsRequest.builder().setLogin(userLogin).setOrganization(KEY).build()).getGroupsList(); assertThat(result).extracting(WsUsers.GroupsWsResponse.Group::getName).containsOnly(groupName, "Members"); } private WsComponents.SearchWsResponse searchSampleProject(String organizationKey, ComponentsService componentsService) { return componentsService .search(new org.sonarqube.ws.client.component.SearchWsRequest() .setOrganization(organizationKey) .setQualifiers(singletonList("TRK")) .setQuery("sample")); } private void expect403HttpError(Runnable runnable) { try { runnable.run(); fail("Ws call should have failed"); } catch (HttpException e) { assertThat(e.code()).isEqualTo(403); } } private void expect404HttpError(Runnable runnable) { try { runnable.run(); fail("Ws call should have failed"); } catch (HttpException e) { assertThat(e.code()).isEqualTo(404); } } private void verifyOrganizationDoesNotExit(String organizationKey) { Organizations.SearchWsResponse searchWsResponse = anonymousOrganizationService.search(new SearchWsRequest.Builder().setOrganizations(organizationKey).build()); assertThat(searchWsResponse.getOrganizationsList()).isEmpty(); } private void verifySingleSearchResult(Organizations.Organization createdOrganization, String name, String description, String url, String avatarUrl) { List<Organizations.Organization> organizations = anonymousOrganizationService.search(new SearchWsRequest.Builder().setOrganizations(createdOrganization.getKey()) .build()).getOrganizationsList(); assertThat(organizations).hasSize(1); Organizations.Organization searchedOrganization = organizations.get(0); assertThat(searchedOrganization.getKey()).isEqualTo(createdOrganization.getKey()); assertThat(searchedOrganization.getName()).isEqualTo(name); if (description == null) { assertThat(searchedOrganization.hasDescription()).isFalse(); } else { assertThat(searchedOrganization.getDescription()).isEqualTo(description); } if (url == null) { assertThat(searchedOrganization.hasUrl()).isFalse(); } else { assertThat(searchedOrganization.getUrl()).isEqualTo(url); } if (avatarUrl == null) { assertThat(searchedOrganization.hasAvatar()).isFalse(); } else { assertThat(searchedOrganization.getAvatar()).isEqualTo(avatarUrl); } } }