diff options
-rw-r--r-- | it/it-plugins/billing-plugin/src/main/java/BillingValidations.java | 9 | ||||
-rw-r--r-- | it/it-tests/src/test/java/it/organization/BillingTest.java | 41 | ||||
-rw-r--r-- | server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidations.java | 5 | ||||
-rw-r--r-- | server/sonar-server/src/main/java/org/sonar/server/organization/BillingValidationsProxyImpl.java | 5 | ||||
-rw-r--r-- | server/sonar-server/src/main/java/org/sonar/server/ui/ws/OrganizationAction.java | 23 | ||||
-rw-r--r-- | server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyImplTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyTest.java) | 31 | ||||
-rw-r--r-- | server/sonar-server/src/test/java/org/sonar/server/ui/ws/OrganizationActionTest.java | 34 |
7 files changed, 135 insertions, 13 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 b21d9c64837..344f24925fd 100644 --- a/it/it-plugins/billing-plugin/src/main/java/BillingValidations.java +++ b/it/it-plugins/billing-plugin/src/main/java/BillingValidations.java @@ -27,6 +27,7 @@ import static java.lang.String.format; public class BillingValidations implements BillingValidationsExtension { private static final String PREVENT_PROJECT_ANALYSIS_SETTING = "sonar.billing.preventProjectAnalysis"; + private static final String PREVENT_UPDATING_PROJECTS_VISIBILITY_TO_PRIVATE_SETTING = "sonar.billing.preventUpdatingProjectsVisibilityToPrivate"; private final Settings settings; @@ -41,4 +42,12 @@ public class BillingValidations implements BillingValidationsExtension { throw new BillingValidationsException(format("Organization %s cannot perform analysis", organization.getKey())); } } + + @Override + public boolean canUpdateProjectVisibilityToPrivate(Organization organization) { + if (!settings.hasKey(PREVENT_UPDATING_PROJECTS_VISIBILITY_TO_PRIVATE_SETTING)) { + return true; + } + return !settings.getBoolean(PREVENT_UPDATING_PROJECTS_VISIBILITY_TO_PRIVATE_SETTING); + } } 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 10a623dbe61..11a94a9f7ff 100644 --- a/it/it-tests/src/test/java/it/organization/BillingTest.java +++ b/it/it-tests/src/test/java/it/organization/BillingTest.java @@ -24,12 +24,15 @@ import com.sonar.orchestrator.build.BuildResult; import com.sonar.orchestrator.build.SonarScanner; import it.Category6Suite; import org.junit.AfterClass; +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.client.GetRequest; import org.sonarqube.ws.client.WsClient; +import org.sonarqube.ws.client.WsResponse; import org.sonarqube.ws.client.organization.CreateWsRequest; import util.ItUtils; import util.user.UserRule; @@ -41,11 +44,15 @@ import static org.sonarqube.ws.WsCe.TaskResponse; import static util.ItUtils.newAdminWsClient; import static util.ItUtils.newOrganizationKey; import static util.ItUtils.newProjectKey; +import static util.ItUtils.newUserWsClient; import static util.ItUtils.projectDir; import static util.ItUtils.resetSettings; import static util.ItUtils.setServerProperty; public class BillingTest { + + private static final String USER_LOGIN = "USER_LOGIN"; + @ClassRule public static Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; @@ -58,15 +65,21 @@ public class BillingTest { private static WsClient adminClient; @BeforeClass - public static void setUp() throws Exception { + public static void prepare() throws Exception { adminClient = newAdminWsClient(orchestrator); enableOrganizationsSupport(); resetSettings(orchestrator, "sonar.billing.preventProjectAnalysis"); } + @Before + public void setUp() throws Exception { + userRule.deactivateUsers(USER_LOGIN); + } + @AfterClass public static void tearDown() throws Exception { resetSettings(orchestrator, "sonar.billing.preventProjectAnalysis"); + userRule.deactivateUsers(USER_LOGIN); } @Test @@ -92,6 +105,22 @@ public class BillingTest { assertThat(taskResponse.getTask().getErrorMessage()).contains(format("Organization %s cannot perform analysis", organizationKey)); } + @Test + public void api_navigation_organization_return_canUpdateProjectsVisibilityToPrivate() { + String organizationKey = createOrganization(); + userRule.createUser(USER_LOGIN, USER_LOGIN); + adminClient.organizations().addMember(organizationKey, USER_LOGIN); + + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "false"); + assertWsResponseAsAdmin(new GetRequest("api/navigation/organization").setParam("organization", organizationKey), "\"canUpdateProjectsVisibilityToPrivate\":true"); + + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "true"); + assertWsResponseAsAdmin(new GetRequest("api/navigation/organization").setParam("organization", organizationKey), "\"canUpdateProjectsVisibilityToPrivate\":false"); + + setServerProperty(orchestrator, "sonar.billing.preventUpdatingProjectsVisibilityToPrivate", "true"); + assertWsResponseAsUser(new GetRequest("api/navigation/organization").setParam("organization", organizationKey), "\"canUpdateProjectsVisibilityToPrivate\":false"); + } + private static String createOrganization() { String key = newOrganizationKey(); adminClient.organizations().create(new CreateWsRequest.Builder().setKey(key).setName(key).build()).getOrganization(); @@ -106,4 +135,14 @@ public class BillingTest { "sonar.password", "admin")); return ItUtils.extractCeTaskId(buildResult); } + + 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){ + 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 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<Page> personalOrgForBilling = page -> organization.getUserId() == null || !page.getKey().startsWith("billing/"); List<Page> 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<Page> 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/BillingValidationsProxyTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyImplTest.java index 649a35cc743..31874248397 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/BillingValidationsProxyImplTest.java @@ -22,12 +22,14 @@ 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 BillingValidationsProxyTest { +public class BillingValidationsProxyImplTest { private static String ORGANIZATION_KEY = "ORGANIZATION_KEY"; private static String ORGANIZATION_UUID = "ORGANIZATION_UUID"; @@ -39,8 +41,8 @@ public class BillingValidationsProxyTest { @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); @@ -49,11 +51,34 @@ public class BillingValidationsProxyTest { @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/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 + "," + + " }" + + "}"); + } } |