aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscode.java43
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscodeImpl.java81
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/user/SystemPasscodeImplTest.java123
-rw-r--r--sonar-application/src/main/assembly/conf/sonar.properties7
-rw-r--r--tests/plugins/fake-governance-plugin/src/main/java/FakeGovernancePlugin.java3
-rw-r--r--tests/plugins/fake-governance-plugin/src/main/java/systemPasscode/SystemPasscodeWebService.java49
-rw-r--r--tests/src/test/java/org/sonarqube/tests/Category5Suite.java5
-rw-r--r--tests/src/test/java/org/sonarqube/tests/authorisation/SystemPasscodeTest.java94
10 files changed, 406 insertions, 3 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
index 6de6ea8288a..4f67d5f0bb3 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
@@ -53,6 +53,7 @@ import org.sonar.server.platform.db.EmbeddedDatabaseFactory;
import org.sonar.server.rule.index.RuleIndex;
import org.sonar.server.search.EsSearchModule;
import org.sonar.server.setting.ThreadLocalSettings;
+import org.sonar.server.user.SystemPasscodeImpl;
import org.sonar.server.user.ThreadLocalUserSession;
import org.sonar.server.util.OkHttpClientProvider;
@@ -101,6 +102,7 @@ public class PlatformLevel1 extends PlatformLevel {
// user session
ThreadLocalUserSession.class,
+ SystemPasscodeImpl.class,
// DB
DBSessionsImpl.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscode.java b/server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscode.java
new file mode 100644
index 00000000000..4174446eb69
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscode.java
@@ -0,0 +1,43 @@
+/*
+ * 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.user;
+
+import org.sonar.api.server.ws.Request;
+
+/**
+ * Passcode for accessing some web services, usually for connecting
+ * monitoring tools without using the credentials
+ * of a system administrator.
+ */
+public interface SystemPasscode {
+
+ /**
+ * Whether the system passcode is configured in sonar.properties or not.
+ * By default passcode is not defined and {@code false} is returned.
+ */
+ boolean isConfigured();
+
+ /**
+ * Whether the configured system passcode is provided by the HTTP request or not.
+ * Returns {@code false} if {@link #isConfigured()} is {@code false}.
+ */
+ boolean isValid(Request request);
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscodeImpl.java b/server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscodeImpl.java
new file mode 100644
index 00000000000..5a04178479b
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/user/SystemPasscodeImpl.java
@@ -0,0 +1,81 @@
+/*
+ * 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.user;
+
+import java.util.Optional;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.Startable;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.utils.log.Loggers;
+
+@ServerSide
+public class SystemPasscodeImpl implements SystemPasscode, Startable {
+
+ public static final String PASSCODE_HTTP_HEADER = "X-Sonar-Passcode";
+ public static final String PASSCODE_CONF_PROPERTY = "sonar.web.systemPasscode";
+
+ private final Configuration configuration;
+ private String configuredPasscode;
+
+ public SystemPasscodeImpl(Configuration configuration) {
+ this.configuration = configuration;
+ }
+
+ @Override
+ public boolean isConfigured() {
+ return configuredPasscode != null;
+ }
+
+ @Override
+ public boolean isValid(Request request) {
+ if (configuredPasscode == null) {
+ return false;
+ }
+ return request.header(PASSCODE_HTTP_HEADER)
+ .map(s -> configuredPasscode.equals(s))
+ .orElse(false);
+ }
+
+ @Override
+ public void start() {
+ Optional<String> passcodeOpt = configuration.get(PASSCODE_CONF_PROPERTY)
+ // if present, result is never empty string
+ .map(StringUtils::trimToNull);
+
+ if (passcodeOpt.isPresent()) {
+ logState("enabled");
+ configuredPasscode = passcodeOpt.get();
+ } else {
+ logState("disabled");
+ configuredPasscode = null;
+ }
+ }
+
+ private void logState(String state) {
+ Loggers.get(getClass()).info("System authentication by passcode is {}", state);
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java
index 5685f9fd064..aff151adb48 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java
@@ -62,7 +62,7 @@ public class WebhookCallerImplTest {
server.enqueue(new MockResponse().setBody("pong").setResponseCode(201));
WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
- assertThat(delivery.getHttpStatus().get()).isEqualTo(201);
+ assertThat(delivery.getHttpStatus()).hasValue(201);
assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0);
assertThat(delivery.getError()).isEmpty();
assertThat(delivery.getAt()).isEqualTo(NOW);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/user/SystemPasscodeImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/user/SystemPasscodeImplTest.java
new file mode 100644
index 00000000000..9c37c5467cc
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/user/SystemPasscodeImplTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.user;
+
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.server.ws.TestRequest;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SystemPasscodeImplTest {
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ private MapSettings settings = new MapSettings();
+ private SystemPasscodeImpl underTest = new SystemPasscodeImpl(settings.asConfig());
+
+ @After
+ public void tearDown() {
+ underTest.stop();
+ }
+
+ @Test
+ public void isConfigured_is_true_if_property_is_not_blank() {
+ verifyIsConfigured("foo", true);
+ }
+
+ @Test
+ public void isConfigured_is_false_if_property_value_is_blank() {
+ verifyIsConfigured(" ", false);
+ }
+
+ @Test
+ public void isConfigured_is_false_if_property_value_is_empty() {
+ verifyIsConfigured("", false);
+ }
+
+ @Test
+ public void isConfigured_is_false_if_property_is_not_defined() {
+ assertThat(underTest.isConfigured()).isFalse();
+ }
+
+ @Test
+ public void startup_logs_show_that_feature_is_enabled() {
+ configurePasscode("foo");
+ underTest.start();
+
+ assertThat(logTester.logs(LoggerLevel.INFO)).contains("System authentication by passcode is enabled");
+ }
+
+ @Test
+ public void startup_logs_show_that_feature_is_disabled() {
+ underTest.start();
+
+ assertThat(logTester.logs(LoggerLevel.INFO)).contains("System authentication by passcode is disabled");
+ }
+
+ @Test
+ public void isValid_is_true_if_request_header_matches_configured_passcode() {
+ verifyIsValid(true, "foo", "foo");
+ }
+
+ @Test
+ public void isValid_is_false_if_request_header_matches_configured_passcode_with_different_case() {
+ verifyIsValid(false, "foo", "FOO");
+ }
+
+ @Test
+ public void isValid_is_false_if_request_header_does_not_match_configured_passcode() {
+ verifyIsValid(false, "foo", "bar");
+ }
+
+ @Test
+ public void isValid_is_false_if_request_header_is_defined_but_passcode_is_not_configured() {
+ verifyIsValid(false, null, "foo");
+ }
+
+ @Test
+ public void isValid_is_false_if_request_header_is_empty() {
+ verifyIsValid(false, "foo", "");
+ }
+
+ private void verifyIsValid(boolean expectedResult, String configuredPasscode, String header) {
+ configurePasscode(configuredPasscode);
+
+ TestRequest request = new TestRequest();
+ request.setHeader("X-Sonar-Passcode", header);
+
+ assertThat(underTest.isValid(request)).isEqualTo(expectedResult);
+ }
+
+ private void verifyIsConfigured(String propertyValue, boolean expectedResult) {
+ configurePasscode(propertyValue);
+ assertThat(underTest.isConfigured()).isEqualTo(expectedResult);
+ }
+
+ private void configurePasscode(String propertyValue) {
+ settings.setProperty("sonar.web.systemPasscode", propertyValue);
+ underTest.start();
+ }
+}
diff --git a/sonar-application/src/main/assembly/conf/sonar.properties b/sonar-application/src/main/assembly/conf/sonar.properties
index fdc340fbb6b..a970e7b8010 100644
--- a/sonar-application/src/main/assembly/conf/sonar.properties
+++ b/sonar-application/src/main/assembly/conf/sonar.properties
@@ -137,6 +137,13 @@
# and cannot be greater than 3 months. Value must be strictly positive.
#sonar.web.sessionTimeoutInMinutes=4320
+# A passcode can be defined to access some web services from monitoring
+# tools without having to use the credentials of a system administrator.
+# Check the Web API documentation to know which web services are supporting this authentication mode.
+# The passcode should be provided in HTTP requests with the header "X-Sonar-Passcode".
+# By default feature is disabled.
+#sonar.web.systemPasscode=
+
#--------------------------------------------------------------------------------------------------
# SSO AUTHENTICATION
diff --git a/tests/plugins/fake-governance-plugin/src/main/java/FakeGovernancePlugin.java b/tests/plugins/fake-governance-plugin/src/main/java/FakeGovernancePlugin.java
index 9460af58547..19f6fdf68c3 100644
--- a/tests/plugins/fake-governance-plugin/src/main/java/FakeGovernancePlugin.java
+++ b/tests/plugins/fake-governance-plugin/src/main/java/FakeGovernancePlugin.java
@@ -1,4 +1,3 @@
-
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
@@ -20,6 +19,7 @@
*/
import org.sonar.api.Plugin;
+import systemPasscode.SystemPasscodeWebService;
import workerCount.FakeWorkerCountProviderImpl;
import workerCount.RefreshWorkerCountAction;
import workerlatch.LatchControllerWorkerMeasureComputer;
@@ -35,6 +35,7 @@ public class FakeGovernancePlugin implements Plugin {
context.addExtension(WorkerLatchMetrics.class);
context.addExtension(LatchControllerWorkerMeasureComputer.class);
context.addExtension(RefreshWorkerCountAction.class);
+ context.addExtension(SystemPasscodeWebService.class);
}
}
diff --git a/tests/plugins/fake-governance-plugin/src/main/java/systemPasscode/SystemPasscodeWebService.java b/tests/plugins/fake-governance-plugin/src/main/java/systemPasscode/SystemPasscodeWebService.java
new file mode 100644
index 00000000000..cdeadf75de0
--- /dev/null
+++ b/tests/plugins/fake-governance-plugin/src/main/java/systemPasscode/SystemPasscodeWebService.java
@@ -0,0 +1,49 @@
+/*
+ * 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 systemPasscode;
+
+import java.net.HttpURLConnection;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.user.SystemPasscode;
+
+public class SystemPasscodeWebService implements WebService, RequestHandler {
+ private final SystemPasscode passcode;
+
+ public SystemPasscodeWebService(SystemPasscode passcode) {
+ this.passcode = passcode;
+ }
+
+ @Override
+ public void define(Context context) {
+ NewController controller = context.createController("api/system_passcode");
+ controller.createAction("check").setHandler(this);
+ controller.done();
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ if (!passcode.isValid(request)) {
+ response.stream().setStatus(HttpURLConnection.HTTP_UNAUTHORIZED);
+ }
+ }
+}
diff --git a/tests/src/test/java/org/sonarqube/tests/Category5Suite.java b/tests/src/test/java/org/sonarqube/tests/Category5Suite.java
index 298d51dcabe..951185485a8 100644
--- a/tests/src/test/java/org/sonarqube/tests/Category5Suite.java
+++ b/tests/src/test/java/org/sonarqube/tests/Category5Suite.java
@@ -21,6 +21,7 @@ package org.sonarqube.tests;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
+import org.sonarqube.tests.authorisation.SystemPasscodeTest;
import org.sonarqube.tests.ce.CeShutdownTest;
import org.sonarqube.tests.ce.CeWorkersTest;
import org.sonarqube.tests.cluster.ClusterTest;
@@ -73,7 +74,9 @@ import org.sonarqube.tests.user.UserEsResilienceTest;
IssueCreationDatePluginChangedTest.class,
// elasticsearch
- ElasticsearchSettingsTest.class
+ ElasticsearchSettingsTest.class,
+
+ SystemPasscodeTest.class
})
public class Category5Suite {
diff --git a/tests/src/test/java/org/sonarqube/tests/authorisation/SystemPasscodeTest.java b/tests/src/test/java/org/sonarqube/tests/authorisation/SystemPasscodeTest.java
new file mode 100644
index 00000000000..32fa0cba328
--- /dev/null
+++ b/tests/src/test/java/org/sonarqube/tests/authorisation/SystemPasscodeTest.java
@@ -0,0 +1,94 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonarqube.tests.authorisation;
+
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.OrchestratorBuilder;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonarqube.tests.Tester;
+import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.WsRequest;
+import org.sonarqube.ws.client.WsResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static util.ItUtils.pluginArtifact;
+
+public class SystemPasscodeTest {
+
+ private static final String VALID_PASSCODE = "123456";
+ private static final String INVALID_PASSCODE = "not" + VALID_PASSCODE;
+ private static final String PASSCODE_HEADER = "X-Sonar-Passcode";
+
+ private static Orchestrator orchestrator;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ OrchestratorBuilder builder = Orchestrator.builderEnv()
+ // this privileged plugin provides the WS api/system_passcode/check
+ // that is used by the tests
+ .addPlugin(pluginArtifact("fake-governance-plugin"))
+ .setServerProperty("sonar.web.systemPasscode", VALID_PASSCODE);
+ orchestrator = builder.build();
+ orchestrator.start();
+ }
+
+ @AfterClass
+ public static void stop() {
+ if (orchestrator != null) {
+ orchestrator.stop();
+ }
+ }
+
+ @Rule
+ public Tester tester = new Tester(orchestrator);
+
+ @Test
+ public void system_access_is_granted_if_valid_passcode_is_sent_through_http_header() {
+ WsRequest request = newRequest()
+ .setHeader(PASSCODE_HEADER, VALID_PASSCODE);
+
+ WsResponse response = tester.asAnonymous().wsClient().wsConnector().call(request);
+ assertThat(response.code()).isEqualTo(200);
+ }
+
+ @Test
+ public void system_access_is_rejected_if_invalid_passcode_is_sent_through_http_header() {
+ WsRequest request = newRequest()
+ .setHeader(PASSCODE_HEADER, INVALID_PASSCODE);
+
+ WsResponse response = tester.asAnonymous().wsClient().wsConnector().call(request);
+ assertThat(response.code()).isEqualTo(401);
+ }
+
+ @Test
+ public void system_access_is_rejected_if_passcode_is_not_sent() {
+ WsRequest request = newRequest();
+
+ WsResponse response = tester.asAnonymous().wsClient().wsConnector().call(request);
+ assertThat(response.code()).isEqualTo(401);
+ }
+
+ private static GetRequest newRequest() {
+ return new GetRequest("api/system_passcode/check");
+ }
+}