From 03162017aec7a55e1601d1badda198ab7cbf7158 Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Thu, 20 Jun 2013 11:23:55 +0200 Subject: [PATCH] SONAR-4397 Add new permissions "scan" and "dryrun" --- .../resources/org/sonar/l10n/core.properties | 5 +- .../sonar/batch/bootstrap/BatchSettings.java | 19 +++- .../sonar/batch/bootstrap/ServerClient.java | 4 + .../org/sonar/batch/scan/ModuleSettings.java | 11 +++ .../batch/bootstrap/BatchSettingsTest.java | 86 ++++++++++++++++--- .../sonar/batch/scan/ModuleSettingsTest.java | 64 ++++++++++++-- .../core/persistence/DatabaseVersion.java | 2 +- .../org/sonar/core/persistence/rows-h2.sql | 8 +- .../java/org/sonar/api/config/Settings.java | 4 + .../org/sonar/api/utils/HttpDownloader.java | 37 ++++++-- .../controllers/batch_bootstrap_controller.rb | 34 ++++++-- .../WEB-INF/app/views/roles/global.html.erb | 2 +- .../414_add_scan_and_dryrun_permissions.rb | 73 ++++++++++++++++ .../webapp/WEB-INF/lib/need_authorization.rb | 2 +- 14 files changed, 309 insertions(+), 42 deletions(-) create mode 100644 sonar-server/src/main/webapp/WEB-INF/db/migrate/414_add_scan_and_dryrun_permissions.rb diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties index b7967537165..81bce121649 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties @@ -2206,7 +2206,10 @@ global_role.profileadmin=Quality Profile Administration global_role.profileadmin.desc=Ability to perform any action on the quality profiles. global_role.sharedashboard=Dashboard Sharing global_role.sharedashboard.desc=Ability to share dashboards that any user will be able to follow. - +global_role.scan=Ability to execute some Sonar analysis +global_role.scan.desc=Permission required to execute a Sonar analysis. This permission allows to get all settings (even the secured ones like scm account password, jira account password, ...) required to execute all Sonar plugins. +global_role.dryrun=Ability to execute some local (dry run) Sonar analysis +global_role.dryrun.desc=Permission required to execute a local (dry run) Sonar analysis without pushing the results to the Sonar server. This permission allows to get all settings required to execute all Sonar plugins except the secured one like scm account password, jira account password... This permission is required for instance to execute a local Sonar analysis in Sonar Eclipse. #------------------------------------------------------------------------------ # diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSettings.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSettings.java index eed98ee577c..17a95e7cfc3 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSettings.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSettings.java @@ -28,13 +28,16 @@ import org.sonar.api.CoreProperties; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; +import org.sonar.api.utils.SonarException; import javax.annotation.Nullable; + import java.util.List; import java.util.Map; public class BatchSettings extends Settings { private Configuration deprecatedConfiguration; + private boolean dryRun; // Keep module settings for initialization of ProjectSettings // module key -> @@ -45,7 +48,7 @@ public class BatchSettings extends Settings { private Map savedProperties; public BatchSettings(BootstrapSettings bootstrapSettings, PropertyDefinitions propertyDefinitions, - ServerClient client, Configuration deprecatedConfiguration) { + ServerClient client, Configuration deprecatedConfiguration) { super(propertyDefinitions); this.bootstrapSettings = bootstrapSettings; this.client = client; @@ -56,6 +59,8 @@ public class BatchSettings extends Settings { public void init(@Nullable ProjectReactor reactor) { savedProperties = this.getProperties(); + // Do not use getBoolean to avoid recursion + this.dryRun = "true".equals(bootstrapSettings.property(CoreProperties.DRY_RUN)); if (reactor != null) { LoggerFactory.getLogger(BatchSettings.class).info("Load project settings"); String branch = bootstrapSettings.property(CoreProperties.PROJECT_BRANCH_PROPERTY); @@ -87,9 +92,9 @@ public class BatchSettings extends Settings { private void downloadSettings(ServerClient client, @Nullable String projectKey) { String url; if (StringUtils.isNotBlank(projectKey)) { - url = "/batch_bootstrap/properties?project=" + projectKey; + url = "/batch_bootstrap/properties?project=" + projectKey + "&dryRun=" + dryRun; } else { - url = "/batch_bootstrap/properties"; + url = "/batch_bootstrap/properties?dryRun=" + dryRun; } String jsonText = client.request(url); List> json = (List>) JSONValue.parse(jsonText); @@ -129,4 +134,12 @@ public class BatchSettings extends Settings { protected void doOnClearProperties() { deprecatedConfiguration.clear(); } + + @Override + protected void doOnGetProperties(String key) { + if (dryRun && key.endsWith(".secured") && !key.contains(".license")) { + throw new SonarException("Access to the secured property '" + key + + "' is not possible in local (dry run) SonarQube analysis. The SonarQube plugin accessing to this property must be deactivated in dry run mode."); + } + } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java index 133fb03ef7c..16028be1299 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java @@ -105,6 +105,10 @@ public class ServerClient implements BatchComponent { if (he.getResponseCode() == 401) { return new SonarException(String.format(getMessageWhenNotAuthorized(), CoreProperties.LOGIN, CoreProperties.PASSWORD)); } + if (he.getResponseCode() == 403) { + // SONAR-4397 Details are in response content + return new SonarException(he.getResponseContent()); + } return new SonarException(String.format("Fail to execute request [code=%s, url=%s]", he.getResponseCode(), he.getUri()), he); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleSettings.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleSettings.java index 9dc33277fbf..91eb1393100 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleSettings.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleSettings.java @@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.config.Settings; +import org.sonar.api.utils.SonarException; import org.sonar.batch.bootstrap.BatchSettings; import javax.annotation.Nullable; @@ -39,9 +40,11 @@ import java.util.Map; public class ModuleSettings extends Settings { private final Configuration deprecatedCommonsConf; + private boolean dryRun; public ModuleSettings(BatchSettings batchSettings, ProjectDefinition project, Configuration deprecatedCommonsConf) { super(batchSettings.getDefinitions()); + this.dryRun = "true".equals(batchSettings.getString(CoreProperties.DRY_RUN)); LoggerFactory.getLogger(ModuleSettings.class).info("Load module settings"); this.deprecatedCommonsConf = deprecatedCommonsConf; @@ -105,4 +108,12 @@ public class ModuleSettings extends Settings { protected void doOnClearProperties() { deprecatedCommonsConf.clear(); } + + @Override + protected void doOnGetProperties(String key) { + if (this.dryRun && key.endsWith(".secured") && !key.contains(".license")) { + throw new SonarException("Access to the secured property '" + key + + "' is not possible in local (dry run) SonarQube analysis. The SonarQube plugin accessing to this property must be deactivated in dry run mode."); + } + } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchSettingsTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchSettingsTest.java index bfcf3d12c6a..64716030ee0 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchSettingsTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchSettingsTest.java @@ -21,10 +21,15 @@ package org.sonar.batch.bootstrap; import org.apache.commons.configuration.BaseConfiguration; import org.apache.commons.configuration.Configuration; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.CoreProperties; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.utils.SonarException; import java.util.Collections; import java.util.Map; @@ -35,20 +40,33 @@ import static org.mockito.Mockito.when; public class BatchSettingsTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + private static final String JSON_RESPONSE = "[{\"k\":\"sonar.cpd.cross\",\"v\":\"true\"}]"; + private static final String JSON_RESPONSE_WITH_SECURED = "[{\"k\":\"sonar.foo.secured\",\"v\":\"bar\"},{\"k\":\"sonar.foo.license.secured\",\"v\":\"bar2\"}]"; private static final String REACTOR_JSON_RESPONSE = "[{\"k\":\"sonar.cpd.cross\",\"v\":\"true\"}," + "{\"k\":\"sonar.java.coveragePlugin\",\"v\":\"jacoco\",\"p\":\"struts\"}," + "{\"k\":\"sonar.java.coveragePlugin\",\"v\":\"cobertura\",\"p\":\"struts-core\"}]"; + private static final String BRANCH_REACTOR_JSON_RESPONSE = "[{\"k\":\"sonar.cpd.cross\",\"v\":\"true\"}," + + "{\"k\":\"sonar.java.coveragePlugin\",\"v\":\"jacoco\",\"p\":\"struts:mybranch\"}," + + "{\"k\":\"sonar.java.coveragePlugin\",\"v\":\"cobertura\",\"p\":\"struts-core:mybranch\"}]"; + ServerClient client = mock(ServerClient.class); ProjectDefinition project = ProjectDefinition.create().setKey("struts"); Configuration deprecatedConf = new BaseConfiguration(); - BootstrapSettings bootstrapSettings = new BootstrapSettings(new BootstrapProperties(Collections. emptyMap())); + BootstrapSettings bootstrapSettings; + + @Before + public void prepare() { + bootstrapSettings = new BootstrapSettings(new BootstrapProperties(Collections. emptyMap())); + } @Test public void should_load_system_props() { - when(client.request("/batch_bootstrap/properties")).thenReturn(JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?dryRun=false")).thenReturn(JSON_RESPONSE); System.setProperty("BatchSettingsTest.testSystemProp", "system"); // Reconstruct bootstrap settings to get system property bootstrapSettings = new BootstrapSettings(new BootstrapProperties(Collections. emptyMap())); @@ -60,8 +78,8 @@ public class BatchSettingsTest { @Test public void should_load_project_props() { - when(client.request("/batch_bootstrap/properties")).thenReturn(JSON_RESPONSE); - when(client.request("/batch_bootstrap/properties?project=struts")).thenReturn(REACTOR_JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?dryRun=false")).thenReturn(JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?project=struts&dryRun=false")).thenReturn(REACTOR_JSON_RESPONSE); project.setProperty("project.prop", "project"); BatchSettings batchSettings = new BatchSettings(bootstrapSettings, new PropertyDefinitions(), client, deprecatedConf); @@ -72,7 +90,7 @@ public class BatchSettingsTest { @Test public void should_load_global_settings() { - when(client.request("/batch_bootstrap/properties")).thenReturn(JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?dryRun=false")).thenReturn(JSON_RESPONSE); BatchSettings batchSettings = new BatchSettings(bootstrapSettings, new PropertyDefinitions(), client, deprecatedConf); @@ -81,8 +99,8 @@ public class BatchSettingsTest { @Test public void should_load_project_root_settings() { - when(client.request("/batch_bootstrap/properties")).thenReturn(JSON_RESPONSE); - when(client.request("/batch_bootstrap/properties?project=struts")).thenReturn(REACTOR_JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?dryRun=false")).thenReturn(JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?project=struts&dryRun=false")).thenReturn(REACTOR_JSON_RESPONSE); BatchSettings batchSettings = new BatchSettings(bootstrapSettings, new PropertyDefinitions(), client, deprecatedConf); batchSettings.init(new ProjectReactor(project)); @@ -90,10 +108,52 @@ public class BatchSettingsTest { assertThat(batchSettings.getString("sonar.java.coveragePlugin")).isEqualTo("jacoco"); } + @Test + public void should_load_project_root_settings_on_branch() { + when(client.request("/batch_bootstrap/properties?dryRun=false")).thenReturn(JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?project=struts:mybranch&dryRun=false")).thenReturn(BRANCH_REACTOR_JSON_RESPONSE); + + bootstrapSettings.properties().put(CoreProperties.PROJECT_BRANCH_PROPERTY, "mybranch"); + + BatchSettings batchSettings = new BatchSettings(bootstrapSettings, new PropertyDefinitions(), client, deprecatedConf); + batchSettings.init(new ProjectReactor(project)); + + assertThat(batchSettings.getString("sonar.java.coveragePlugin")).isEqualTo("jacoco"); + } + + @Test + public void should_not_fail_when_accessing_secured_properties() { + when(client.request("/batch_bootstrap/properties?dryRun=false")).thenReturn(JSON_RESPONSE_WITH_SECURED); + when(client.request("/batch_bootstrap/properties?project=struts&dryRun=false")).thenReturn(REACTOR_JSON_RESPONSE); + + BatchSettings batchSettings = new BatchSettings(bootstrapSettings, new PropertyDefinitions(), client, deprecatedConf); + batchSettings.init(new ProjectReactor(project)); + + assertThat(batchSettings.getString("sonar.foo.license.secured")).isEqualTo("bar2"); + assertThat(batchSettings.getString("sonar.foo.secured")).isEqualTo("bar"); + } + + @Test + public void should_fail_when_accessing_secured_properties_in_dryrun() { + when(client.request("/batch_bootstrap/properties?dryRun=true")).thenReturn(JSON_RESPONSE_WITH_SECURED); + when(client.request("/batch_bootstrap/properties?project=struts&dryRun=true")).thenReturn(REACTOR_JSON_RESPONSE); + + bootstrapSettings.properties().put(CoreProperties.DRY_RUN, "true"); + + BatchSettings batchSettings = new BatchSettings(bootstrapSettings, new PropertyDefinitions(), client, deprecatedConf); + batchSettings.init(new ProjectReactor(project)); + + assertThat(batchSettings.getString("sonar.foo.license.secured")).isEqualTo("bar2"); + thrown.expect(SonarException.class); + thrown + .expectMessage("Access to the secured property 'sonar.foo.secured' is not possible in local (dry run) SonarQube analysis. The SonarQube plugin accessing to this property must be deactivated in dry run mode."); + batchSettings.getString("sonar.foo.secured"); + } + @Test public void should_keep_module_settings_for_later() { - when(client.request("/batch_bootstrap/properties")).thenReturn(JSON_RESPONSE); - when(client.request("/batch_bootstrap/properties?project=struts")).thenReturn(REACTOR_JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?dryRun=false")).thenReturn(JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?project=struts&dryRun=false")).thenReturn(REACTOR_JSON_RESPONSE); BatchSettings batchSettings = new BatchSettings(bootstrapSettings, new PropertyDefinitions(), client, deprecatedConf); batchSettings.init(new ProjectReactor(project)); @@ -106,7 +166,7 @@ public class BatchSettingsTest { @Test public void system_props_should_override_build_props() { - when(client.request("/batch_bootstrap/properties")).thenReturn(JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?dryRun=false")).thenReturn(JSON_RESPONSE); System.setProperty("BatchSettingsTest.testSystemProp", "system"); project.setProperty("BatchSettingsTest.testSystemProp", "build"); @@ -117,8 +177,8 @@ public class BatchSettingsTest { @Test public void should_forward_to_deprecated_commons_configuration() { - when(client.request("/batch_bootstrap/properties")).thenReturn(JSON_RESPONSE); - when(client.request("/batch_bootstrap/properties?project=struts")).thenReturn(REACTOR_JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?dryRun=false")).thenReturn(JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?project=struts&dryRun=false")).thenReturn(REACTOR_JSON_RESPONSE); BatchSettings batchSettings = new BatchSettings(bootstrapSettings, new PropertyDefinitions(), client, deprecatedConf); batchSettings.init(new ProjectReactor(project)); @@ -137,7 +197,7 @@ public class BatchSettingsTest { @Test public void project_should_be_optional() { - when(client.request("/batch_bootstrap/properties")).thenReturn(JSON_RESPONSE); + when(client.request("/batch_bootstrap/properties?dryRun=false")).thenReturn(JSON_RESPONSE); BatchSettings batchSettings = new BatchSettings(bootstrapSettings, new PropertyDefinitions(), client, deprecatedConf); assertThat(batchSettings.getProperties()).isNotEmpty(); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java index e3487f6dd79..d8ae94c8fa2 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java @@ -22,9 +22,13 @@ package org.sonar.batch.scan; import com.google.common.collect.ImmutableMap; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.PropertiesConfiguration; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.CoreProperties; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.config.PropertyDefinitions; +import org.sonar.api.utils.SonarException; import org.sonar.batch.bootstrap.BatchSettings; import java.util.List; @@ -35,6 +39,9 @@ import static org.mockito.Mockito.when; public class ModuleSettingsTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Test public void testOrderedProjects() { ProjectDefinition grandParent = ProjectDefinition.create(); @@ -54,13 +61,13 @@ public class ModuleSettingsTest { BatchSettings batchSettings = mock(BatchSettings.class); when(batchSettings.getDefinitions()).thenReturn(new PropertyDefinitions()); when(batchSettings.getProperties()).thenReturn(ImmutableMap.of( - "overridding", "batch", - "on-batch", "true" - )); + "overridding", "batch", + "on-batch", "true" + )); when(batchSettings.getModuleProperties("struts-core")).thenReturn(ImmutableMap.of( - "on-module", "true", - "overridding", "module" - )); + "on-module", "true", + "overridding", "module" + )); ProjectDefinition module = ProjectDefinition.create().setKey("struts-core"); Configuration deprecatedConf = new PropertiesConfiguration(); @@ -75,4 +82,49 @@ public class ModuleSettingsTest { assertThat(deprecatedConf.getString("on-batch")).isEqualTo("true"); assertThat(deprecatedConf.getString("on-module")).isEqualTo("true"); } + + @Test + public void should_not_fail_when_accessing_secured_properties() { + BatchSettings batchSettings = mock(BatchSettings.class); + when(batchSettings.getDefinitions()).thenReturn(new PropertyDefinitions()); + when(batchSettings.getProperties()).thenReturn(ImmutableMap.of( + "sonar.foo.secured", "bar" + )); + when(batchSettings.getModuleProperties("struts-core")).thenReturn(ImmutableMap.of( + "sonar.foo.license.secured", "bar2" + )); + + ProjectDefinition module = ProjectDefinition.create().setKey("struts-core"); + Configuration deprecatedConf = new PropertiesConfiguration(); + + ModuleSettings moduleSettings = new ModuleSettings(batchSettings, module, deprecatedConf); + + assertThat(moduleSettings.getString("sonar.foo.license.secured")).isEqualTo("bar2"); + assertThat(moduleSettings.getString("sonar.foo.secured")).isEqualTo("bar"); + } + + @Test + public void should_fail_when_accessing_secured_properties_in_dryrun() { + BatchSettings batchSettings = mock(BatchSettings.class); + when(batchSettings.getDefinitions()).thenReturn(new PropertyDefinitions()); + when(batchSettings.getString(CoreProperties.DRY_RUN)).thenReturn("true"); + when(batchSettings.getProperties()).thenReturn(ImmutableMap.of( + "sonar.foo.secured", "bar" + )); + when(batchSettings.getModuleProperties("struts-core")).thenReturn(ImmutableMap.of( + "sonar.foo.license.secured", "bar2" + )); + + ProjectDefinition module = ProjectDefinition.create().setKey("struts-core"); + Configuration deprecatedConf = new PropertiesConfiguration(); + + ModuleSettings moduleSettings = new ModuleSettings(batchSettings, module, deprecatedConf); + + assertThat(moduleSettings.getString("sonar.foo.license.secured")).isEqualTo("bar2"); + + thrown.expect(SonarException.class); + thrown + .expectMessage("Access to the secured property 'sonar.foo.secured' is not possible in local (dry run) SonarQube analysis. The SonarQube plugin accessing to this property must be deactivated in dry run mode."); + moduleSettings.getString("sonar.foo.secured"); + } } diff --git a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java index edcbbc9969c..f85f02253b9 100644 --- a/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java +++ b/sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java @@ -32,7 +32,7 @@ import java.util.List; */ public class DatabaseVersion implements BatchComponent, ServerComponent { - public static final int LAST_VERSION = 413; + public static final int LAST_VERSION = 414; public static enum Status { UP_TO_DATE, REQUIRES_UPGRADE, REQUIRES_DOWNGRADE, FRESH_INSTALL diff --git a/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql b/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql index 4e43875c862..4d643553f1e 100644 --- a/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql +++ b/sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql @@ -7,7 +7,12 @@ ALTER TABLE GROUPS ALTER COLUMN ID RESTART WITH 3; INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (1, 1, null, 'admin'); INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (2, 1, null, 'profileadmin'); INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (3, 1, null, 'sharedashboard'); -ALTER TABLE GROUP_ROLES ALTER COLUMN ID RESTART WITH 4; +INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (4, 1, null, 'scan'); +INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (5, null, null, 'scan'); +INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (6, 1, null, 'dryrun'); +INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (7, 2, null, 'dryrun'); +INSERT INTO GROUP_ROLES(ID, GROUP_ID, RESOURCE_ID, ROLE) VALUES (8, null, null, 'dryrun'); +ALTER TABLE GROUP_ROLES ALTER COLUMN ID RESTART WITH 9; INSERT INTO GROUPS_USERS(USER_ID, GROUP_ID) VALUES (1, 1); INSERT INTO GROUPS_USERS(USER_ID, GROUP_ID) VALUES (1, 2); @@ -174,6 +179,7 @@ INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('410'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('411'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('412'); INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('413'); +INSERT INTO SCHEMA_MIGRATIONS(VERSION) VALUES ('414'); INSERT INTO USERS(ID, LOGIN, NAME, EMAIL, CRYPTED_PASSWORD, SALT, CREATED_AT, UPDATED_AT, REMEMBER_TOKEN, REMEMBER_TOKEN_EXPIRES_AT) VALUES (1, 'admin', 'Administrator', '', 'a373a0e667abb2604c1fd571eb4ad47fe8cc0878', '48bc4b0d93179b5103fd3885ea9119498e9d161b', '2011-09-26 22:27:48.0', '2011-09-26 22:27:48.0', null, null); ALTER TABLE USERS ALTER COLUMN ID RESTART WITH 2; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java index 8623e9c458a..d23873e8bfc 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java @@ -107,6 +107,7 @@ public class Settings implements BatchComponent, ServerComponent { * Does not decrypt value. */ protected String getClearString(String key) { + doOnGetProperties(key); String validKey = definitions.validKey(key); String value = properties.get(validKey); if (value == null) { @@ -384,4 +385,7 @@ public class Settings implements BatchComponent, ServerComponent { protected void doOnClearProperties() { } + + protected void doOnGetProperties(String key) { + } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java index f6d9bdbd2bd..cb64b82be95 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java @@ -30,6 +30,7 @@ import com.google.common.io.Files; import com.google.common.io.InputSupplier; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.slf4j.LoggerFactory; import org.sonar.api.BatchComponent; import org.sonar.api.ServerComponent; @@ -74,7 +75,7 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo @Override String[] getSupportedSchemes() { - return new String[]{"http", "https"}; + return new String[] {"http", "https"}; } @Override @@ -130,8 +131,8 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo public static class BaseHttpDownloader { private static final List PROXY_SETTINGS = ImmutableList.of( - "http.proxyHost", "http.proxyPort", "http.nonProxyHosts", - "http.auth.ntlm.domain", "socksProxyHost", "socksProxyPort"); + "http.proxyHost", "http.proxyPort", "http.nonProxyHosts", + "http.auth.ntlm.domain", "socksProxyHost", "socksProxyPort"); private String userAgent; @@ -175,8 +176,8 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo private void registerProxyCredentials(Map settings) { Authenticator.setDefault(new ProxyAuthenticator( - settings.get("http.proxyUser"), - settings.get("http.proxyPassword"))); + settings.get("http.proxyUser"), + settings.get("http.proxyPassword"))); } private boolean requiresProxyAuthentication(Map settings) { @@ -228,7 +229,19 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo int responseCode = connection.getResponseCode(); if (responseCode >= 400) { - throw new HttpException(uri, responseCode); + InputStream errorResponse = null; + try { + errorResponse = connection.getErrorStream(); + if (errorResponse != null) { + String errorResponseContent = IOUtils.toString(errorResponse); + throw new HttpException(uri, responseCode, errorResponseContent); + } + else { + throw new HttpException(uri, responseCode); + } + } finally { + IOUtils.closeQuietly(errorResponse); + } } return connection.getInputStream(); @@ -252,11 +265,17 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo public static class HttpException extends RuntimeException { private final URI uri; private final int responseCode; + private final String responseContent; - public HttpException(URI uri, int responseCode) { + public HttpException(URI uri, int responseContent) { + this(uri, responseContent, null); + } + + public HttpException(URI uri, int responseCode, String responseContent) { super("Fail to download [" + uri + "]. Response code: " + responseCode); this.uri = uri; this.responseCode = responseCode; + this.responseContent = responseContent; } public int getResponseCode() { @@ -266,5 +285,9 @@ public class HttpDownloader extends UriReader.SchemeProcessor implements BatchCo public URI getUri() { return uri; } + + public String getResponseContent() { + return responseContent; + } } } diff --git a/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb b/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb index b6ed3b66655..6c2f9c93084 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb @@ -26,19 +26,31 @@ class BatchBootstrapController < Api::ApiController # GET /batch_bootstrap/db?project= def db + has_dryrun_role = has_role?(:dryrun) + return render_unauthorized("You're not authorized to execute a dry run analysis. Please contact your Sonar administrator.") if !has_dryrun_role project = load_project() + return render_unauthorized("You're not authorized to access to project '" + project.name + "', please contact your Sonar administrator") if project && !has_role?(:user, project) db_content = java_facade.createDatabaseForDryRun(project ? project.id : nil) send_data String.from_java_bytes(db_content) end - - # GET /batch_bootstrap/properties?[project=] + + # GET /batch_bootstrap/properties?[project=][&dryRun=true|false] def properties + dryRun = params[:dryRun].present? && params[:dryRun] == "true" + has_dryrun_role = has_role?(:dryrun) + has_scan_role = has_role?(:scan) + + return render_unauthorized("You're not authorized to execute any SonarQube analysis. Please contact your SonarQube administrator.") if (!has_dryrun_role && !has_scan_role) + return render_unauthorized("You're only authorized to execute a local (dry run) SonarQube analysis without pushing the results to the SonarQube server. Please contact your SonarQube administrator.") if (!dryRun && !has_scan_role) + keys=Set.new properties=[] # project properties root_project = load_project() + return render_unauthorized("You're not authorized to access to project '" + root_project.name + "', please contact your Sonar administrator") if root_project && !has_role?(:scan) && !has_role?(:user, root_project) + if root_project # bottom-up projects projects=[root_project].concat(root_project.ancestor_projects) @@ -57,7 +69,7 @@ class BatchBootstrapController < Api::ApiController # apply security has_user_role=has_role?(:user, root_project) has_admin_role=has_role?(:admin, root_project) - properties = properties.select{|prop| allowed?(prop.key, has_user_role, has_admin_role)} + properties = properties.select{|prop| allowed?(prop.key, dryRun, has_scan_role)} json_properties=properties.map { |property| to_json_property(property) } @@ -70,12 +82,18 @@ class BatchBootstrapController < Api::ApiController end private + + def render_unauthorized(message, status=403) + respond_to do |format| + format.json { render :text => message, :status => status } + format.xml { render :text => message, :status => status } + format.text { render :text => message, :status => status } + end + end def load_project if params[:project].present? - project = Project.by_key(params[:project]) - return access_denied if project && !has_role?(:user, project) - project + Project.by_key(params[:project]) else nil end @@ -87,9 +105,9 @@ class BatchBootstrapController < Api::ApiController hash end - def allowed?(property_key, has_user_role, has_admin_role) + def allowed?(property_key, dryRun, has_scan_role) if property_key.end_with?('.secured') - property_key.include?('.license') ? has_user_role : has_admin_role + property_key.include?('.license') ? true : (!dryRun && has_scan_role) else true end diff --git a/sonar-server/src/main/webapp/WEB-INF/app/views/roles/global.html.erb b/sonar-server/src/main/webapp/WEB-INF/app/views/roles/global.html.erb index 912ea799922..405fe0d9934 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/views/roles/global.html.erb +++ b/sonar-server/src/main/webapp/WEB-INF/app/views/roles/global.html.erb @@ -9,7 +9,7 @@ - <% ['admin', 'profileadmin', 'sharedashboard'].each do |globalRole| %> + <% ['admin', 'profileadmin', 'sharedashboard', 'scan', 'dryrun'].each do |globalRole| %> <%= message('global_role.' + globalRole) -%>
diff --git a/sonar-server/src/main/webapp/WEB-INF/db/migrate/414_add_scan_and_dryrun_permissions.rb b/sonar-server/src/main/webapp/WEB-INF/db/migrate/414_add_scan_and_dryrun_permissions.rb new file mode 100644 index 00000000000..978b8dbf1b5 --- /dev/null +++ b/sonar-server/src/main/webapp/WEB-INF/db/migrate/414_add_scan_and_dryrun_permissions.rb @@ -0,0 +1,73 @@ +# +# Sonar, entreprise quality control tool. +# Copyright (C) 2008-2013 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube 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. +# +# SonarQube 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. +# + +# +# Sonar 3.7 +# SONAR-4397 +# + +class AddScanAndDryrunPermissions < ActiveRecord::Migration + + def self.up + # -- Role scan -- + group_roles=GroupRole.find(:all, :conditions => {:role => 'admin', :resource_id => nil}) + groups = group_roles.map { |ur| ur.group_id } + # Anyone + unless groups.include?(nil) + groups << nil + end + groups.each do |group_id| + GroupRole.create(:group_id => group_id, :role => 'scan', :resource_id => nil) + end + + user_roles=UserRole.find(:all, :conditions => {:role => 'admin', :resource_id => nil}) + users = user_roles.map { |ur| ur.user_id } + users.each do |user_id| + UserRole.create(:user_id => user_id, :role=> 'scan', :resource_id => nil) + end + + # -- Role dryrun -- + group_roles=GroupRole.find(:all, :conditions => {:role => 'admin', :resource_id => nil}) + groups = group_roles.map { |ur| ur.group_id } + # Anyone + unless groups.include?(nil) + groups << nil + end + # sonar-users + userGroupName = Property.by_key('sonar.defaultGroup') + userGroupName = 'sonar-users' if userGroupName.nil? + userGroup = Group.find(:all, :conditions => {:name => userGroupName}).first + unless userGroup.nil? || groups.include?(userGroup.id) + groups << userGroup.id + end + + groups.each do |group_id| + GroupRole.create(:group_id => group_id, :role => 'dryrun', :resource_id => nil) + end + + user_roles=UserRole.find(:all, :conditions => {:role => 'admin', :resource_id => nil}) + users = user_roles.map { |ur| ur.user_id } + users.each do |user_id| + UserRole.create(:user_id => user_id, :role=> 'dryrun', :resource_id => nil) + end + + end + +end diff --git a/sonar-server/src/main/webapp/WEB-INF/lib/need_authorization.rb b/sonar-server/src/main/webapp/WEB-INF/lib/need_authorization.rb index 1deab73eb62..b6f4e02163e 100644 --- a/sonar-server/src/main/webapp/WEB-INF/lib/need_authorization.rb +++ b/sonar-server/src/main/webapp/WEB-INF/lib/need_authorization.rb @@ -59,7 +59,7 @@ module NeedAuthorization def has_role?(role, objects=nil) if objects.nil? role_symbol=role.to_sym - if role_symbol==:admin || role_symbol==:profileadmin || role_symbol==:sharedashboard + if role_symbol==:admin || role_symbol==:profileadmin || role_symbol==:sharedashboard || role_symbol==:scan || role_symbol==:dryrun AuthorizerFactory.authorizer.has_role?(self, role_symbol) else # There's no concept of global users or global codeviewers. -- 2.39.5