From 1eed5d9bfbf46bc9ffc0b7d7673a3beaf2b9c9a6 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Wed, 26 Apr 2017 17:39:39 +0200 Subject: SONAR-9121 Add a flag in api/navigation/organization to know if projects can become private --- .../server/organization/BillingValidations.java | 5 ++ .../organization/BillingValidationsProxyImpl.java | 5 ++ .../org/sonar/server/ui/ws/OrganizationAction.java | 23 ++++-- .../BillingValidationsProxyImplTest.java | 84 ++++++++++++++++++++++ .../organization/BillingValidationsProxyTest.java | 59 --------------- .../sonar/server/ui/ws/OrganizationActionTest.java | 34 ++++++++- 6 files changed, 142 insertions(+), 68 deletions(-) create mode 100644 server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyImplTest.java delete mode 100644 server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyTest.java (limited to 'server/sonar-server') 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 1c524acce1a..14103f7db19 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 @@ -34,6 +34,11 @@ public interface BillingValidations { */ void checkOnProjectAnalysis(Organization organization); + /** + * @return true if the organization can use private projects + */ + boolean canUpdateProjectVisibilityToPrivate(Organization organization); + class Organization { private final String key; private final String uuid; 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 c091e346b9e..c9512f73226 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 @@ -40,4 +40,9 @@ public class BillingValidationsProxyImpl implements BillingValidationsProxy { } billingValidationsExtension.checkOnProjectAnalysis(organization); } + + @Override + public boolean canUpdateProjectVisibilityToPrivate(Organization organization) { + return billingValidationsExtension == null || billingValidationsExtension.canUpdateProjectVisibilityToPrivate(organization); + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/ws/OrganizationAction.java b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/OrganizationAction.java index f52bdf87b56..915c69e93ed 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ui/ws/OrganizationAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/OrganizationAction.java @@ -31,13 +31,16 @@ import org.sonar.api.web.page.Page; 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.organization.DefaultOrganizationProvider; import org.sonar.server.project.Visibility; import org.sonar.server.ui.PageRepository; import org.sonar.server.user.UserSession; import static org.sonar.core.util.stream.MoreCollectors.toList; +import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; +import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS; import static org.sonar.server.ws.KeyExamples.KEY_ORG_EXAMPLE_001; import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; @@ -50,12 +53,15 @@ public class OrganizationAction implements NavigationWsAction { private final DefaultOrganizationProvider defaultOrganizationProvider; private final UserSession userSession; private final PageRepository pageRepository; + private final BillingValidationsProxy billingValidations; - public OrganizationAction(DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider, UserSession userSession, PageRepository pageRepository) { + public OrganizationAction(DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider, UserSession userSession, PageRepository pageRepository, + BillingValidationsProxy billingValidations) { this.dbClient = dbClient; this.defaultOrganizationProvider = defaultOrganizationProvider; this.userSession = userSession; this.pageRepository = pageRepository; + this.billingValidations = billingValidations; } @Override @@ -94,16 +100,19 @@ public class OrganizationAction implements NavigationWsAction { private void writeOrganization(JsonWriter json, OrganizationDto organization, boolean newProjectPrivate) { json.name("organization") .beginObject() - .prop("canAdmin", userSession.hasPermission(OrganizationPermission.ADMINISTER, organization)) - .prop("canProvisionProjects", userSession.hasPermission(OrganizationPermission.PROVISION_PROJECTS, organization)) - .prop("canDelete", organization.isGuarded() ? userSession.isSystemAdministrator() : userSession.hasPermission(OrganizationPermission.ADMINISTER, organization)) + .prop("canAdmin", userSession.hasPermission(ADMINISTER, organization)) + .prop("canProvisionProjects", userSession.hasPermission(PROVISION_PROJECTS, organization)) + .prop("canDelete", organization.isGuarded() ? userSession.isSystemAdministrator() : userSession.hasPermission(ADMINISTER, organization)) .prop("isDefault", organization.getKey().equals(defaultOrganizationProvider.get().getKey())) - .prop("projectVisibility", Visibility.getLabel(newProjectPrivate)); + .prop("projectVisibility", Visibility.getLabel(newProjectPrivate)) + .prop("canUpdateProjectsVisibilityToPrivate", + userSession.hasPermission(ADMINISTER, organization) && + billingValidations.canUpdateProjectVisibilityToPrivate(new BillingValidations.Organization(organization.getKey(), organization.getUuid()))); Predicate personalOrgForBilling = page -> organization.getUserId() == null || !page.getKey().startsWith("billing/"); List pages = pageRepository.getOrganizationPages(false).stream().filter(personalOrgForBilling).collect(toList()); json.name("pages"); writePages(json, pages); - if (userSession.hasPermission(OrganizationPermission.ADMINISTER, organization)) { + if (userSession.hasPermission(ADMINISTER, organization)) { List adminPages = pageRepository.getOrganizationPages(true).stream().filter(personalOrgForBilling).collect(toList()); json.name("adminPages"); writePages(json, adminPages); 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 new file mode 100644 index 00000000000..31874248397 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyImplTest.java @@ -0,0 +1,84 @@ +/* + * 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; + +import org.junit.Test; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static org.sonar.server.organization.BillingValidations.Organization; + +public class BillingValidationsProxyImplTest { + + private static String ORGANIZATION_KEY = "ORGANIZATION_KEY"; + private static String ORGANIZATION_UUID = "ORGANIZATION_UUID"; + + private BillingValidationsExtension billingValidationsExtension = mock(BillingValidationsExtension.class); + + private BillingValidationsProxyImpl underTest; + + @Test + public void checkOnProjectAnalysis_calls_extension_when_available() { + underTest = new BillingValidationsProxyImpl(billingValidationsExtension); + Organization organization = new Organization(ORGANIZATION_KEY, ORGANIZATION_UUID); + + underTest.checkOnProjectAnalysis(organization); + + verify(billingValidationsExtension).checkOnProjectAnalysis(organization); + } + + @Test + public void checkOnProjectAnalysis_does_nothing_when_no_extension_available() { + underTest = new BillingValidationsProxyImpl(); + Organization organization = new Organization(ORGANIZATION_KEY, ORGANIZATION_UUID); + + underTest.checkOnProjectAnalysis(organization); + + verifyZeroInteractions(billingValidationsExtension); + } + + @Test + public void canUpdateProjectsVisibilityToPrivate_calls_extension_when_available() { + underTest = new BillingValidationsProxyImpl(billingValidationsExtension); + Organization organization = new Organization(ORGANIZATION_KEY, ORGANIZATION_UUID); + when(billingValidationsExtension.canUpdateProjectVisibilityToPrivate(organization)).thenReturn(false); + + boolean result = underTest.canUpdateProjectVisibilityToPrivate(organization); + + assertThat(result).isFalse(); + verify(billingValidationsExtension).canUpdateProjectVisibilityToPrivate(organization); + } + + @Test + public void canUpdateProjectsVisibilityToPrivate_return_true_when_no_extension() { + underTest = new BillingValidationsProxyImpl(); + Organization organization = new Organization(ORGANIZATION_KEY, ORGANIZATION_UUID); + + boolean result = underTest.canUpdateProjectVisibilityToPrivate(organization); + + assertThat(result).isTrue(); + verifyZeroInteractions(billingValidationsExtension); + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyTest.java deleted file mode 100644 index 649a35cc743..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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; - -import org.junit.Test; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.sonar.server.organization.BillingValidations.Organization; - -public class BillingValidationsProxyTest { - - private static String ORGANIZATION_KEY = "ORGANIZATION_KEY"; - private static String ORGANIZATION_UUID = "ORGANIZATION_UUID"; - - private BillingValidationsExtension billingValidationsExtension = mock(BillingValidationsExtension.class); - - private BillingValidationsProxyImpl underTest; - - @Test - public void checkOnProjectAnalysis_calls_extension_when_available() { - underTest = new BillingValidationsProxyImpl(billingValidationsExtension); - - Organization organization = new Organization(ORGANIZATION_KEY, ORGANIZATION_UUID); - underTest.checkOnProjectAnalysis(organization); - - verify(billingValidationsExtension).checkOnProjectAnalysis(organization); - } - - @Test - public void checkOnProjectAnalysis_does_nothing_when_no_extension_available() { - underTest = new BillingValidationsProxyImpl(); - - Organization organization = new Organization(ORGANIZATION_KEY, ORGANIZATION_UUID); - underTest.checkOnProjectAnalysis(organization); - - verifyZeroInteractions(billingValidationsExtension); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java index 3feab3cae3d..a23a57c9eb4 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java @@ -32,6 +32,8 @@ import org.sonar.core.platform.PluginRepository; import org.sonar.db.DbClient; import org.sonar.db.DbTester; import org.sonar.db.organization.OrganizationDto; +import org.sonar.server.organization.BillingValidations; +import org.sonar.server.organization.BillingValidationsProxy; import org.sonar.server.organization.DefaultOrganizationProvider; import org.sonar.server.organization.TestDefaultOrganizationProvider; import org.sonar.server.tester.UserSessionRule; @@ -42,6 +44,7 @@ import org.sonar.server.ws.WsActionTester; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -61,8 +64,9 @@ public class OrganizationActionTest { private DbClient dbClient = dbTester.getDbClient(); private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(dbTester); private PageRepository pageRepository = mock(PageRepository.class); + private BillingValidationsProxy billingValidations = mock(BillingValidationsProxy.class); - private WsActionTester ws = new WsActionTester(new OrganizationAction(dbClient, defaultOrganizationProvider, userSession, pageRepository)); + private WsActionTester ws = new WsActionTester(new OrganizationAction(dbClient, defaultOrganizationProvider, userSession, pageRepository, billingValidations)); @Test public void verify_definition() { @@ -254,6 +258,23 @@ public class OrganizationActionTest { assertJson(executeRequest(organization).getInput()).isSimilarTo("{\"organization\": {\"projectVisibility\": \"public\"}}"); } + @Test + public void returns_non_admin_and_canUpdateProjectsVisibilityToPrivate_false_when_user_logged_in_but_not_admin_and_extension_returns_true() { + OrganizationDto defaultOrganization = dbTester.getDefaultOrganization(); + + userSession.logIn(); + when(billingValidations.canUpdateProjectVisibilityToPrivate(any(BillingValidations.Organization.class))).thenReturn(true); + verifyCanUpdateProjectsVisibilityToPrivateResponse(executeRequest(dbTester.getDefaultOrganization()), false); + + userSession.logIn().addPermission(ADMINISTER, defaultOrganization); + when(billingValidations.canUpdateProjectVisibilityToPrivate(any(BillingValidations.Organization.class))).thenReturn(false); + verifyCanUpdateProjectsVisibilityToPrivateResponse(executeRequest(dbTester.getDefaultOrganization()), false); + + userSession.logIn().addPermission(ADMINISTER, defaultOrganization); + when(billingValidations.canUpdateProjectVisibilityToPrivate(any(BillingValidations.Organization.class))).thenReturn(true); + verifyCanUpdateProjectsVisibilityToPrivateResponse(executeRequest(dbTester.getDefaultOrganization()), true); + } + private void initWithPages(Page... pages) { PluginRepository pluginRepository = mock(PluginRepository.class); when(pluginRepository.hasPlugin(anyString())).thenReturn(true); @@ -263,7 +284,7 @@ public class OrganizationActionTest { } }}); pageRepository.start(); - ws = new WsActionTester(new OrganizationAction(dbClient, defaultOrganizationProvider, userSession, pageRepository)); + ws = new WsActionTester(new OrganizationAction(dbClient, defaultOrganizationProvider, userSession, pageRepository, billingValidations)); } private TestResponse executeRequest(@Nullable OrganizationDto organization) { @@ -285,4 +306,13 @@ public class OrganizationActionTest { " }" + "}"); } + + private static void verifyCanUpdateProjectsVisibilityToPrivateResponse(TestResponse response, boolean canUpdateProjectsVisibilityToPrivate) { + assertJson(response.getInput()) + .isSimilarTo("{" + + " \"organization\": {" + + " \"canUpdateProjectsVisibilityToPrivate\": " + canUpdateProjectsVisibilityToPrivate + "," + + " }" + + "}"); + } } -- cgit v1.2.3