]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4397 Add new permissions "scan" and "dryrun"
authorJulien HENRY <julien.henry@sonarsource.com>
Thu, 20 Jun 2013 09:23:55 +0000 (11:23 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Tue, 25 Jun 2013 14:08:53 +0000 (16:08 +0200)
14 files changed:
plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchSettings.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/ServerClient.java
sonar-batch/src/main/java/org/sonar/batch/scan/ModuleSettings.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchSettingsTest.java
sonar-batch/src/test/java/org/sonar/batch/scan/ModuleSettingsTest.java
sonar-core/src/main/java/org/sonar/core/persistence/DatabaseVersion.java
sonar-core/src/main/resources/org/sonar/core/persistence/rows-h2.sql
sonar-plugin-api/src/main/java/org/sonar/api/config/Settings.java
sonar-plugin-api/src/main/java/org/sonar/api/utils/HttpDownloader.java
sonar-server/src/main/webapp/WEB-INF/app/controllers/batch_bootstrap_controller.rb
sonar-server/src/main/webapp/WEB-INF/app/views/roles/global.html.erb
sonar-server/src/main/webapp/WEB-INF/db/migrate/414_add_scan_and_dryrun_permissions.rb [new file with mode: 0644]
sonar-server/src/main/webapp/WEB-INF/lib/need_authorization.rb

index b796753716506fc8a48a6d3ffb3b51d3c68acbef..81bce121649589a23240886cc590833494685fe1 100644 (file)
@@ -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.
 
 #------------------------------------------------------------------------------
 #
index eed98ee577c7c76e855d11c1e03510a969336703..17a95e7cfc3e5f9b3ad6b456df96c512155e3d03 100644 (file)
@@ -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 -> <key,val>
@@ -45,7 +48,7 @@ public class BatchSettings extends Settings {
   private Map<String, String> 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<Map<String, String>> json = (List<Map<String, String>>) 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.");
+    }
+  }
 }
index 133fb03ef7c684eeaf255a6f7d1ac906b2da7a85..16028be1299bf679484c6158e2ae496991798198 100644 (file)
@@ -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);
   }
 
index 9dc33277fbfd902e697c85e7cd93f20ea0872336..91eb139310090c685795f954b0b4cea5ca700127 100644 (file)
@@ -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.");
+    }
+  }
 }
index bfcf3d12c6a3d6a58f3290896be14ededb0d2acd..64716030ee0818493bf6b8f4167af16d3c161de1 100644 (file)
@@ -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.<String, String> emptyMap()));
+  BootstrapSettings bootstrapSettings;
+
+  @Before
+  public void prepare() {
+    bootstrapSettings = new BootstrapSettings(new BootstrapProperties(Collections.<String, String> 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.<String, String> 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();
   }
index e3487f6dd79b03a70b535ed2e4332f6504310ac4..d8ae94c8fa2a266a26b6112b45ee518b40aa22c9 100644 (file)
@@ -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");
+  }
 }
index edcbbc9969c514eb1a54f675b8fa2185d92265a4..f85f02253b9d7a48782443015bcf704f4860d405 100644 (file)
@@ -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
index 4e43875c862ed7cd45557fda100d37a8812f7cee..4d643553f1e66642621ca80ce47af6ce16c1aeb0 100644 (file)
@@ -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;
index 8623e9c458a71cbe52a38482952ed72b53240536..d23873e8bfccf269cddaaec60f8278896d4712fc 100644 (file)
@@ -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) {
+  }
 }
index f6d9bdbd2bd9d495cc4f47950a9ac90cceb6fdfc..cb64b82be9582fdcf3a2f8ea0009dfde2e98bbf2 100644 (file)
@@ -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<String> 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<String, String> 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<String, String> 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;
+    }
   }
 }
index b6ed3b66655dbcfb6c80428f2e34e4df33f67ace..6c2f9c93084ba24727bc4f5dd91a229ab754ea6b 100644 (file)
@@ -26,19 +26,31 @@ class BatchBootstrapController < Api::ApiController
 
   # GET /batch_bootstrap/db?project=<key or id>
   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=<key or id>]
+  
+  # GET /batch_bootstrap/properties?[project=<key or id>][&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
index 912ea799922bbe9a056f17dc66944554ff3c1de2..405fe0d9934e53c3024ba1d491ae453c28800442 100644 (file)
@@ -9,7 +9,7 @@
   </tr>
   </thead>
   <tbody>
-    <% ['admin', 'profileadmin', 'sharedashboard'].each do |globalRole| %>
+    <% ['admin', 'profileadmin', 'sharedashboard', 'scan', 'dryrun'].each do |globalRole| %>
     <tr class="<%= cycle('even', 'odd', :name => 'globalRole') -%>" >
       <td valign="top">
         <b><%= message('global_role.' + globalRole) -%></b><br/>
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 (file)
index 0000000..978b8db
--- /dev/null
@@ -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
index 1deab73eb622b735c54c011ac6f7de571a44684e..b6f4e02163e786d17a98859aa98f02caac04b70d 100644 (file)
@@ -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.