diff options
10 files changed, 228 insertions, 12 deletions
diff --git a/it/it-plugins/billing-plugin/src/main/java/BillingValidations.java b/it/it-plugins/billing-plugin/src/main/java/BillingValidations.java index 344f24925fd..0e57d8d305d 100644 --- a/it/it-plugins/billing-plugin/src/main/java/BillingValidations.java +++ b/it/it-plugins/billing-plugin/src/main/java/BillingValidations.java @@ -44,6 +44,14 @@ public class BillingValidations implements BillingValidationsExtension { } @Override + public void checkCanUpdateProjectVisibility(Organization organization, boolean updateToPrivate) { + boolean preventUpdatingProjectsToPrivate = settings.getBoolean(PREVENT_UPDATING_PROJECTS_VISIBILITY_TO_PRIVATE_SETTING); + if (preventUpdatingProjectsToPrivate) { + throw new BillingValidationsException(format("Organization %s cannot use private project", organization.getKey())); + } + } + + @Override public boolean canUpdateProjectVisibilityToPrivate(Organization organization) { if (!settings.hasKey(PREVENT_UPDATING_PROJECTS_VISIBILITY_TO_PRIVATE_SETTING)) { return true; diff --git a/it/it-tests/src/test/java/it/organization/BillingTest.java b/it/it-tests/src/test/java/it/organization/BillingTest.java index a78fdfc3a66..869f7601943 100644 --- a/it/it-tests/src/test/java/it/organization/BillingTest.java +++ b/it/it-tests/src/test/java/it/organization/BillingTest.java @@ -31,15 +31,19 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonarqube.ws.client.GetRequest; +import org.sonarqube.ws.client.HttpException; import org.sonarqube.ws.client.WsClient; import org.sonarqube.ws.client.WsResponse; import org.sonarqube.ws.client.organization.CreateWsRequest; +import org.sonarqube.ws.client.organization.UpdateProjectVisibilityWsRequest; +import org.sonarqube.ws.client.project.CreateRequest; import util.ItUtils; import util.user.UserRule; import static it.Category6Suite.enableOrganizationsSupport; import static java.lang.String.format; import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.junit.Assert.fail; import static org.sonarqube.ws.WsCe.TaskResponse; import static util.ItUtils.newAdminWsClient; import static util.ItUtils.newOrganizationKey; @@ -73,12 +77,12 @@ public class BillingTest { @Before public void setUp() throws Exception { userRule.deactivateUsers(USER_LOGIN); - resetSettings(orchestrator, "sonar.billing.preventProjectAnalysis"); + resetSettings(orchestrator, null, "sonar.billing.preventProjectAnalysis", "sonar.billing.preventUpdatingProjectsVisibilityToPrivate"); } @AfterClass public static void tearDown() throws Exception { - resetSettings(orchestrator, "sonar.billing.preventProjectAnalysis"); + resetSettings(orchestrator, null, "sonar.billing.preventProjectAnalysis", "sonar.billing.preventUpdatingProjectsVisibilityToPrivate"); userRule.deactivateUsers(USER_LOGIN); } @@ -124,8 +128,7 @@ public class BillingTest { @Test public void api_navigation_component_returns_canUpdateProjectVisibilityToPrivate() { String organizationKey = createOrganization(); - String projectKey = newProjectKey(); - executeAnalysis(projectKey, organizationKey); + String projectKey = createPublicProject(organizationKey); setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "false"); assertWsResponseAsAdmin(new GetRequest("api/navigation/component").setParam("componentKey", projectKey), "\"canUpdateProjectVisibilityToPrivate\":true"); @@ -134,12 +137,42 @@ public class BillingTest { assertWsResponseAsAdmin(new GetRequest("api/navigation/component").setParam("componentKey", projectKey), "\"canUpdateProjectVisibilityToPrivate\":false"); } + @Test + public void does_not_fail_to_update_default_projects_visibility_to_private() { + String organizationKey = createOrganization(); + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "false"); + + adminClient.organizations().updateProjectVisibility(UpdateProjectVisibilityWsRequest.builder().setOrganization(organizationKey).setProjectVisibility("private").build()); + + assertWsResponseAsAdmin(new GetRequest("api/navigation/organization").setParam("organization", organizationKey), "\"projectVisibility\":\"private\""); + } + + @Test + public void fail_to_update_default_projects_visibility_to_private() { + String organizationKey = createOrganization(); + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "true"); + + try { + adminClient.organizations().updateProjectVisibility(UpdateProjectVisibilityWsRequest.builder().setOrganization(organizationKey).setProjectVisibility("private").build()); + fail(); + } catch (HttpException ex) { + assertThat(ex.code()).isEqualTo(400); + assertThat(ex.content()).contains(format("Organization %s cannot use private project", organizationKey)); + } + } + private static String createOrganization() { String key = newOrganizationKey(); adminClient.organizations().create(new CreateWsRequest.Builder().setKey(key).setName(key).build()).getOrganization(); return key; } + private static String createPublicProject(String organizationKey) { + String projectKey = newProjectKey(); + adminClient.projects().create(CreateRequest.builder().setKey(projectKey).setName(projectKey).setOrganization(organizationKey).setVisibility("public").build()); + return projectKey; + } + private static String executeAnalysis(String organizationKey) { return executeAnalysis(newProjectKey(), organizationKey); } @@ -153,12 +186,12 @@ public class BillingTest { return ItUtils.extractCeTaskId(buildResult); } - private void assertWsResponseAsAdmin(GetRequest request, String expectedContent){ + private void assertWsResponseAsAdmin(GetRequest request, String expectedContent) { WsResponse response = adminClient.wsConnector().call(request).failIfNotSuccessful(); assertThat(response.content()).contains(expectedContent); } - private void assertWsResponseAsUser(GetRequest request, String expectedContent){ + private void assertWsResponseAsUser(GetRequest request, String expectedContent) { WsResponse response = newUserWsClient(orchestrator, USER_LOGIN, USER_LOGIN).wsConnector().call(request).failIfNotSuccessful(); assertThat(response.content()).contains(expectedContent); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidations.java b/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidations.java index 14103f7db19..55faca1638a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidations.java +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidations.java @@ -35,6 +35,11 @@ public interface BillingValidations { void checkOnProjectAnalysis(Organization organization); /** + * @throws BillingValidationsException when the organization cannot use private projects + */ + void checkCanUpdateProjectVisibility(Organization organization, boolean updateToPrivate); + + /** * @return true if the organization can use private projects */ boolean canUpdateProjectVisibilityToPrivate(Organization organization); diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsProxyImpl.java b/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsProxyImpl.java index c9512f73226..64c2277ead6 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsProxyImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsProxyImpl.java @@ -42,6 +42,14 @@ public class BillingValidationsProxyImpl implements BillingValidationsProxy { } @Override + public void checkCanUpdateProjectVisibility(Organization organization, boolean updateToPrivate) { + if (billingValidationsExtension == null) { + return; + } + billingValidationsExtension.checkCanUpdateProjectVisibility(organization, updateToPrivate); + } + + @Override public boolean canUpdateProjectVisibilityToPrivate(Organization organization) { return billingValidationsExtension == null || billingValidationsExtension.canUpdateProjectVisibilityToPrivate(organization); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/UpdateProjectVisibilityAction.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/UpdateProjectVisibilityAction.java index 6beec1aa8ef..183fa7f02d8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/UpdateProjectVisibilityAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/UpdateProjectVisibilityAction.java @@ -28,6 +28,8 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.permission.OrganizationPermission; +import org.sonar.server.organization.BillingValidations; +import org.sonar.server.organization.BillingValidationsProxy; import org.sonar.server.project.Visibility; import org.sonar.server.user.UserSession; @@ -40,10 +42,12 @@ public class UpdateProjectVisibilityAction implements OrganizationsWsAction { private final UserSession userSession; private final DbClient dbClient; + private final BillingValidationsProxy billingValidations; - public UpdateProjectVisibilityAction(UserSession userSession, DbClient dbClient) { + public UpdateProjectVisibilityAction(UserSession userSession, DbClient dbClient, BillingValidationsProxy billingValidations) { this.userSession = userSession; this.dbClient = dbClient; + this.billingValidations = billingValidations; } @Override @@ -74,9 +78,18 @@ public class UpdateProjectVisibilityAction implements OrganizationsWsAction { Optional<OrganizationDto> optionalOrganization = dbClient.organizationDao().selectByKey(dbSession, organizationKey); OrganizationDto organization = checkFoundWithOptional(optionalOrganization, "No organization with key '" + organizationKey + "' can be found."); userSession.checkPermission(OrganizationPermission.ADMINISTER, organization.getUuid()); + checkCanUpdateProjectsVisibility(organization, newProjectsPrivate); dbClient.organizationDao().setNewProjectPrivate(dbSession, organization, newProjectsPrivate); dbSession.commit(); } response.noContent(); } + + private void checkCanUpdateProjectsVisibility(OrganizationDto organization, boolean newProjectsPrivate) { + try { + billingValidations.checkCanUpdateProjectVisibility(new BillingValidations.Organization(organization.getKey(), organization.getUuid()), newProjectsPrivate); + } catch (BillingValidations.BillingValidationsException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyImplTest.java index 31874248397..616c5973a42 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyImplTest.java @@ -59,6 +59,26 @@ public class BillingValidationsProxyImplTest { } @Test + public void checkCanUpdateProjectsVisibility_calls_extension_when_available() { + underTest = new BillingValidationsProxyImpl(billingValidationsExtension); + Organization organization = new Organization(ORGANIZATION_KEY, ORGANIZATION_UUID); + + underTest.checkCanUpdateProjectVisibility(organization, true); + + verify(billingValidationsExtension).checkCanUpdateProjectVisibility(organization, true); + } + + @Test + public void checkCanUpdateProjectsVisibility_does_nothing_when_no_extension_available() { + underTest = new BillingValidationsProxyImpl(); + Organization organization = new Organization(ORGANIZATION_KEY, ORGANIZATION_UUID); + + underTest.checkCanUpdateProjectVisibility(organization, true); + + verifyZeroInteractions(billingValidationsExtension); + } + + @Test public void canUpdateProjectsVisibilityToPrivate_calls_extension_when_available() { underTest = new BillingValidationsProxyImpl(billingValidationsExtension); Organization organization = new Organization(ORGANIZATION_KEY, ORGANIZATION_UUID); diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/UpdateProjectVisibilityActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/UpdateProjectVisibilityActionTest.java index 7e03a0ec794..9532015c426 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/UpdateProjectVisibilityActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/UpdateProjectVisibilityActionTest.java @@ -27,11 +27,17 @@ import org.sonar.db.DbTester; import org.sonar.db.organization.OrganizationDto; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.organization.BillingValidations; +import org.sonar.server.organization.BillingValidationsProxy; import org.sonar.server.tester.UserSessionRule; import org.sonar.server.ws.TestRequest; import org.sonar.server.ws.WsActionTester; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; import static org.sonar.server.organization.ws.OrganizationsWsSupport.PARAM_ORGANIZATION; import static org.sonar.server.organization.ws.UpdateProjectVisibilityAction.ACTION; @@ -45,7 +51,9 @@ public class UpdateProjectVisibilityActionTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - private UpdateProjectVisibilityAction underTest = new UpdateProjectVisibilityAction(userSession, dbTester.getDbClient()); + private BillingValidationsProxy billingValidations = mock(BillingValidationsProxy.class); + + private UpdateProjectVisibilityAction underTest = new UpdateProjectVisibilityAction(userSession, dbTester.getDbClient(), billingValidations); private WsActionTester wsTester = new WsActionTester(underTest); @Test @@ -71,7 +79,7 @@ public class UpdateProjectVisibilityActionTest { } @Test - public void should_change_project_visibility_to_private() { + public void change_project_visibility_to_private() { OrganizationDto organization = dbTester.organizations().insert(); dbTester.organizations().setNewProjectPrivate(organization, false); userSession.logIn().addPermission(ADMINISTER, organization); @@ -85,7 +93,7 @@ public class UpdateProjectVisibilityActionTest { } @Test - public void should_change_project_visibility_to_public() { + public void change_project_visibility_to_public() { OrganizationDto organization = dbTester.organizations().insert(); dbTester.organizations().setNewProjectPrivate(organization, true); userSession.logIn().addPermission(ADMINISTER, organization); @@ -100,7 +108,40 @@ public class UpdateProjectVisibilityActionTest { } @Test - public void should_fail_if_organization_does_not_exist() { + public void fail_to_update_visibility_to_private_when_organization_is_not_allowed_to_use_private_projects() { + OrganizationDto organization = dbTester.organizations().insert(); + dbTester.organizations().setNewProjectPrivate(organization, true); + userSession.logIn().addPermission(ADMINISTER, organization); + doThrow(new BillingValidations.BillingValidationsException("This organization cannot use project private")).when(billingValidations) + .checkCanUpdateProjectVisibility(any(BillingValidations.Organization.class), eq(true)); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("This organization cannot use project private"); + + wsTester.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_PROJECT_VISIBILITY, "private") + .execute(); + } + + @Test + public void does_not_fail_to_update_visibility_to_public_when_organization_is_not_allowed_to_use_private_projects() { + OrganizationDto organization = dbTester.organizations().insert(); + dbTester.organizations().setNewProjectPrivate(organization, true); + userSession.logIn().addPermission(ADMINISTER, organization); + doThrow(new BillingValidations.BillingValidationsException("This organization cannot use project private")).when(billingValidations) + .checkCanUpdateProjectVisibility(any(BillingValidations.Organization.class), eq(true)); + + wsTester.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_PROJECT_VISIBILITY, "public") + .execute(); + + assertThat(dbTester.organizations().getNewProjectPrivate(organization)).isFalse(); + } + + @Test + public void fail_if_organization_does_not_exist() { TestRequest request = wsTester.newRequest() .setParam(PARAM_ORGANIZATION, "does not exist") .setParam(PARAM_PROJECT_VISIBILITY, "private"); @@ -119,5 +160,4 @@ public class UpdateProjectVisibilityActionTest { expectedException.expect(ForbiddenException.class); request.execute(); } - } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/organization/OrganizationService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/organization/OrganizationService.java index 035411fe48e..edede842c06 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/organization/OrganizationService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/organization/OrganizationService.java @@ -105,4 +105,11 @@ public class OrganizationService extends BaseService { call(post); } + + public void updateProjectVisibility(UpdateProjectVisibilityWsRequest request) { + PostRequest post = new PostRequest(path("update_project_visibility")) + .setParam("organization", request.getOrganization()) + .setParam("projectVisibility", request.getProjectVisibility()); + call(post, UpdateWsResponse.parser()); + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/organization/UpdateProjectVisibilityWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/organization/UpdateProjectVisibilityWsRequest.java new file mode 100644 index 00000000000..e7af9537548 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/organization/UpdateProjectVisibilityWsRequest.java @@ -0,0 +1,68 @@ +/* + * 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.sonarqube.ws.client.organization; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +@Immutable +public class UpdateProjectVisibilityWsRequest { + private final String organization; + private final String projectVisibility; + + private UpdateProjectVisibilityWsRequest(Builder builder) { + this.organization = builder.organization; + this.projectVisibility = builder.projectVisibility; + } + + @CheckForNull + public String getOrganization() { + return organization; + } + + @CheckForNull + public String getProjectVisibility() { + return projectVisibility; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String organization; + private String projectVisibility; + + public Builder setOrganization(@Nullable String organization) { + this.organization = organization; + return this; + } + + public Builder setProjectVisibility(@Nullable String projectVisibility) { + this.projectVisibility = projectVisibility; + return this; + } + + public UpdateProjectVisibilityWsRequest build() { + return new UpdateProjectVisibilityWsRequest(this); + } + } +} diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/organization/OrganizationServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/organization/OrganizationServiceTest.java index b87a1ecc73d..aefc8a6411a 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/organization/OrganizationServiceTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/organization/OrganizationServiceTest.java @@ -96,4 +96,18 @@ public class OrganizationServiceTest { .hasParam("login", "login-1") .andNoOtherParam(); } + + @Test + public void update_project_visibility() { + underTest.updateProjectVisibility(UpdateProjectVisibilityWsRequest.builder() + .setOrganization("O1") + .setProjectVisibility("private") + .build()); + + serviceTester.assertThat(serviceTester.getPostRequest()) + .hasPath("update_project_visibility") + .hasParam("organization", "O1") + .hasParam("projectVisibility", "private") + .andNoOtherParam(); + } } |