@@ -344,6 +344,8 @@ subprojects { | |||
} | |||
dependency 'com.auth0:java-jwt:3.18.2' | |||
dependency 'io.netty:netty-all:4.1.70.Final' | |||
dependency 'io.prometheus:simpleclient_common:0.12.0' | |||
dependency 'io.prometheus:simpleclient_servlet:0.12.0' | |||
dependency 'com.sun.mail:javax.mail:1.5.6' | |||
dependency 'javax.annotation:javax.annotation-api:1.3.2' | |||
dependency 'javax.servlet:javax.servlet-api:3.1.0' |
@@ -63,7 +63,8 @@ public class UserSessionInitializer { | |||
"/api/ce/info", "/api/ce/pause", | |||
"/api/ce/resume", "/api/system/health", | |||
"/api/system/analytics", "/api/system/migrate_es", | |||
"/api/system/liveness"); | |||
"/api/system/liveness", | |||
"/api/monitoring/metrics"); | |||
private static final UrlPattern URL_PATTERN = UrlPattern.builder() | |||
.includes("/*") |
@@ -0,0 +1,53 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.config.Configuration; | |||
import org.sonar.api.server.ws.Request; | |||
import static org.sonar.process.ProcessProperties.Property.WEB_SYSTEM_PASS_CODE; | |||
public class BearerPasscode { | |||
public static final String PASSCODE_HTTP_HEADER = "Authorization"; | |||
private final Configuration configuration; | |||
public BearerPasscode(Configuration configuration) { | |||
this.configuration = configuration; | |||
} | |||
public boolean isValid(Request request) { | |||
Optional<String> passcodeOpt = configuration.get(WEB_SYSTEM_PASS_CODE.getKey()).map(StringUtils::trimToNull); | |||
if (passcodeOpt.isEmpty()) { | |||
return false; | |||
} | |||
String configuredPasscode = passcodeOpt.get(); | |||
return request.header(PASSCODE_HTTP_HEADER) | |||
.map(s -> s.replace("Bearer ", "")) | |||
.map(configuredPasscode::equals) | |||
.orElse(false); | |||
} | |||
} |
@@ -104,6 +104,7 @@ public class UserSessionInitializerTest { | |||
assertPathIsIgnoredWithAnonymousAccess("/api/ce/resume"); | |||
assertPathIsIgnoredWithAnonymousAccess("/api/system/health"); | |||
assertPathIsIgnoredWithAnonymousAccess("/api/system/liveness"); | |||
assertPathIsIgnoredWithAnonymousAccess("/api/monitoring/metrics"); | |||
// exclude static resources | |||
assertPathIsIgnored("/css/style.css"); |
@@ -0,0 +1,71 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.Test; | |||
import org.sonar.api.config.internal.MapSettings; | |||
import org.sonar.api.impl.ws.SimpleGetRequest; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class BearerPasscodeTest { | |||
private final MapSettings settings = new MapSettings(); | |||
private final BearerPasscode underTest = new BearerPasscode(settings.asConfig()); | |||
@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 token) { | |||
configurePasscode(configuredPasscode); | |||
SimpleGetRequest request = new SimpleGetRequest(); | |||
request.setHeader("Authorization", "Bearer " + token); | |||
assertThat(underTest.isValid(request)).isEqualTo(expectedResult); | |||
} | |||
private void configurePasscode(String propertyValue) { | |||
settings.setProperty("sonar.web.systemPasscode", propertyValue); | |||
} | |||
} |
@@ -9,6 +9,10 @@ dependencies { | |||
compile 'com.google.guava:guava' | |||
compile 'com.github.everit-org.json-schema:org.everit.json.schema' | |||
compile 'io.prometheus:simpleclient_common' | |||
compile 'io.prometheus:simpleclient_servlet' | |||
compile project(':server:sonar-ce-common') | |||
compile project(':server:sonar-ce-task') | |||
compile project(':server:sonar-db-dao') |
@@ -0,0 +1,55 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.monitoring; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.server.platform.ws.SafeModeMonitoringMetricAction; | |||
import org.sonar.server.user.BearerPasscode; | |||
import org.sonar.server.user.SystemPasscode; | |||
import org.sonar.server.user.UserSession; | |||
public class MetricsAction extends SafeModeMonitoringMetricAction { | |||
private final UserSession userSession; | |||
public MetricsAction(SystemPasscode systemPasscode, BearerPasscode bearerPasscode, UserSession userSession) { | |||
super(systemPasscode, bearerPasscode); | |||
this.userSession = userSession; | |||
} | |||
@Override | |||
public void define(WebService.NewController context) { | |||
context.createAction("metrics") | |||
.setSince("9.3") | |||
.setDescription("Return monitoring metrics in Prometheus format. \n" + | |||
"Support content type 'text/plain' (default) and 'application/openmetrics-text'.\n" + | |||
"this endpoint can be access using a Bearer token, that needs to be defined in sonar.properties with the 'sonar.web.systemPasscode' key.") | |||
.setResponseExample(getClass().getResource("monitoring-metrics.txt")) | |||
.setHandler(this); | |||
isWebUpGauge.set(0D); | |||
} | |||
@Override | |||
public boolean isSystemAdmin() { | |||
return userSession.isSystemAdministrator(); | |||
} | |||
} |
@@ -0,0 +1,43 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.monitoring; | |||
import org.sonar.api.server.ws.WebService; | |||
public class MonitoringWs implements WebService { | |||
private final MonitoringWsAction[] actions; | |||
public MonitoringWs(MonitoringWsAction... actions) { | |||
this.actions = actions; | |||
} | |||
@Override | |||
public void define(Context context) { | |||
NewController controller = context.createController("api/monitoring") | |||
.setDescription("Monitoring") | |||
.setSince("9.3"); | |||
for (MonitoringWsAction action : actions) { | |||
action.define(controller); | |||
} | |||
controller.done(); | |||
} | |||
} |
@@ -0,0 +1,26 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.monitoring; | |||
import org.sonar.server.ws.WsAction; | |||
public interface MonitoringWsAction extends WsAction { | |||
} |
@@ -0,0 +1,38 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.monitoring; | |||
import org.sonar.core.platform.Module; | |||
import org.sonar.server.user.BearerPasscode; | |||
public class MonitoringWsModule extends Module { | |||
public MonitoringWsModule() { | |||
// nothing to do | |||
} | |||
@Override | |||
protected void configureModule() { | |||
add( | |||
BearerPasscode.class, | |||
MonitoringWs.class, | |||
MetricsAction.class); | |||
} | |||
} |
@@ -0,0 +1,80 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.platform.ws; | |||
import com.google.common.net.HttpHeaders; | |||
import io.prometheus.client.CollectorRegistry; | |||
import io.prometheus.client.Gauge; | |||
import io.prometheus.client.exporter.common.TextFormat; | |||
import java.io.OutputStreamWriter; | |||
import java.io.Writer; | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.monitoring.MonitoringWsAction; | |||
import org.sonar.server.user.BearerPasscode; | |||
import org.sonar.server.user.SystemPasscode; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
public class SafeModeMonitoringMetricAction implements MonitoringWsAction { | |||
protected static final Gauge isWebUpGauge = Gauge.build().name("is_web_up").help("Tells whether web service is up").register(); | |||
private final SystemPasscode systemPasscode; | |||
private final BearerPasscode bearerPasscode; | |||
public SafeModeMonitoringMetricAction(SystemPasscode systemPasscode, BearerPasscode bearerPasscode) { | |||
this.systemPasscode = systemPasscode; | |||
this.bearerPasscode = bearerPasscode; | |||
} | |||
@Override | |||
public void define(WebService.NewController context) { | |||
context.createAction("metrics").setHandler(this); | |||
isWebUpGauge.set(0D); | |||
} | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
if (!systemPasscode.isValid(request) && !isSystemAdmin() && !bearerPasscode.isValid(request)) { | |||
throw new ForbiddenException("Insufficient privileges"); | |||
} | |||
String requestContentType = request.getHeaders().get("accept"); | |||
String contentType = TextFormat.chooseContentType(requestContentType); | |||
response.setHeader(HttpHeaders.CONTENT_TYPE, contentType); | |||
response.stream().setStatus(200); | |||
try (Writer writer = new OutputStreamWriter(response.stream().output(), UTF_8)) { | |||
TextFormat.writeFormat(contentType, writer, CollectorRegistry.defaultRegistry.metricFamilySamples()); | |||
writer.flush(); | |||
} | |||
} | |||
public boolean isSystemAdmin() { | |||
// No authenticated user in safe mode | |||
return false; | |||
} | |||
} |
@@ -20,6 +20,8 @@ | |||
package org.sonar.server.platform.ws; | |||
import org.sonar.core.platform.Module; | |||
import org.sonar.server.monitoring.MonitoringWs; | |||
import org.sonar.server.user.BearerPasscode; | |||
public class SafemodeSystemWsModule extends Module { | |||
@Override | |||
@@ -28,11 +30,17 @@ public class SafemodeSystemWsModule extends Module { | |||
StatusAction.class, | |||
MigrateDbAction.class, | |||
DbMigrationStatusAction.class, | |||
HealthActionSupport.class, | |||
SafeModeHealthAction.class, | |||
SafeModeLivenessCheckerImpl.class, | |||
LivenessActionSupport.class, | |||
SafeModeLivenessAction.class, | |||
MonitoringWs.class, | |||
BearerPasscode.class, | |||
SafeModeMonitoringMetricAction.class, | |||
SystemWs.class); | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
# HELP is_web_up Tells whether web service is up | |||
# TYPE is_web_up gauge | |||
is_web_up 0.0 |
@@ -0,0 +1,118 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.monitoring; | |||
import org.assertj.core.api.Assertions; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.user.BearerPasscode; | |||
import org.sonar.server.user.SystemPasscode; | |||
import org.sonar.server.ws.TestRequest; | |||
import org.sonar.server.ws.TestResponse; | |||
import org.sonar.server.ws.WsActionTester; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.any; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.server.tester.UserSessionRule.standalone; | |||
public class MetricsActionTest { | |||
@Rule | |||
public UserSessionRule userSession = standalone(); | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
private final BearerPasscode bearerPasscode = mock(BearerPasscode.class); | |||
private final SystemPasscode systemPasscode = mock(SystemPasscode.class); | |||
private final MetricsAction underTest = new MetricsAction(systemPasscode, bearerPasscode, userSession); | |||
private final WsActionTester ws = new WsActionTester(underTest); | |||
@Test | |||
public void test_definition() { | |||
WebService.Action definition = ws.getDef(); | |||
assertThat(definition.isInternal()).isFalse(); | |||
assertThat(definition.isPost()).isFalse(); | |||
assertThat(definition.responseExampleAsString()).isNotEmpty(); | |||
assertThat(definition.params()).isEmpty(); | |||
} | |||
@Test | |||
public void no_authentication_throw_insufficient_privileges_error() { | |||
TestRequest request = ws.newRequest(); | |||
Assertions.assertThatThrownBy(request::execute) | |||
.hasMessage("Insufficient privileges") | |||
.isInstanceOf(ForbiddenException.class); | |||
} | |||
@Test | |||
public void authenticated_non_global_admin_is_forbidden() { | |||
userSession.logIn(); | |||
TestRequest testRequest = ws.newRequest(); | |||
Assertions.assertThatThrownBy(testRequest::execute) | |||
.hasMessage("Insufficient privileges") | |||
.isInstanceOf(ForbiddenException.class); | |||
} | |||
@Test | |||
public void authentication_passcode_is_allowed() { | |||
when(systemPasscode.isValid(any())).thenReturn(true); | |||
TestResponse response = ws.newRequest().execute(); | |||
String content = response.getInput(); | |||
assertThat(content) | |||
.contains("# HELP is_web_up Tells whether web service is up") | |||
.contains("# TYPE is_web_up gauge") | |||
.contains("is_web_up 0.0"); | |||
} | |||
@Test | |||
public void authentication_bearer_passcode_is_allowed() { | |||
when(bearerPasscode.isValid(any())).thenReturn(true); | |||
TestResponse response = ws.newRequest().execute(); | |||
String content = response.getInput(); | |||
assertThat(content) | |||
.contains("# HELP is_web_up Tells whether web service is up") | |||
.contains("# TYPE is_web_up gauge") | |||
.contains("is_web_up 0.0"); | |||
} | |||
@Test | |||
public void authenticated_global_admin_is_allowed() { | |||
userSession.logIn().setSystemAdministrator(); | |||
TestResponse response = ws.newRequest().execute(); | |||
String content = response.getInput(); | |||
assertThat(content) | |||
.contains("# HELP is_web_up Tells whether web service is up") | |||
.contains("# TYPE is_web_up gauge") | |||
.contains("is_web_up 0.0"); | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.monitoring; | |||
import org.junit.Test; | |||
import org.sonar.core.platform.ComponentContainer; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class MonitoringWsModuleTest { | |||
@Test | |||
public void verify_count_of_added_components() { | |||
ComponentContainer container = new ComponentContainer(); | |||
new MonitoringWsModule().configure(container); | |||
assertThat(container.size()).isPositive(); | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.monitoring; | |||
import org.junit.Test; | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class MonitoringWsTest { | |||
private final MonitoringWs underTest = new MonitoringWs(new MonitoringWsAction() { | |||
@Override | |||
public void handle(Request request, Response response) { | |||
// nothing to do | |||
} | |||
@Override | |||
public void define(WebService.NewController context) { | |||
context.createAction("foo").setHandler(this); | |||
} | |||
}); | |||
@Test | |||
public void define_controller() { | |||
WebService.Context context = new WebService.Context(); | |||
underTest.define(context); | |||
WebService.Controller controller = context.controller("api/monitoring"); | |||
assertThat(controller).isNotNull(); | |||
assertThat(controller.description()).isNotEmpty(); | |||
assertThat(controller.since()).isEqualTo("9.3"); | |||
assertThat(controller.actions()).hasSize(1); | |||
} | |||
} |
@@ -0,0 +1,106 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.platform.ws; | |||
import org.assertj.core.api.Assertions; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.server.exceptions.ForbiddenException; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import org.sonar.server.user.BearerPasscode; | |||
import org.sonar.server.user.SystemPasscode; | |||
import org.sonar.server.ws.TestRequest; | |||
import org.sonar.server.ws.TestResponse; | |||
import org.sonar.server.ws.WsActionTester; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.ArgumentMatchers.any; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.server.tester.UserSessionRule.standalone; | |||
public class SafeModeMonitoringMetricActionTest { | |||
@Rule | |||
public UserSessionRule userSession = standalone(); | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
private final BearerPasscode bearerPasscode = mock(BearerPasscode.class); | |||
private final SystemPasscode systemPasscode = mock(SystemPasscode.class); | |||
private final SafeModeMonitoringMetricAction safeModeMonitoringMetricAction = new SafeModeMonitoringMetricAction(systemPasscode, bearerPasscode); | |||
private final WsActionTester ws = new WsActionTester(safeModeMonitoringMetricAction); | |||
@Test | |||
public void no_authentication_throw_insufficient_privileges_error() { | |||
TestRequest request = ws.newRequest(); | |||
Assertions.assertThatThrownBy(request::execute) | |||
.hasMessage("Insufficient privileges") | |||
.isInstanceOf(ForbiddenException.class); | |||
} | |||
@Test | |||
public void authenticated_non_global_admin_is_forbidden() { | |||
userSession.logIn(); | |||
TestRequest testRequest = ws.newRequest(); | |||
Assertions.assertThatThrownBy(testRequest::execute) | |||
.hasMessage("Insufficient privileges") | |||
.isInstanceOf(ForbiddenException.class); | |||
} | |||
@Test | |||
public void authentication_passcode_is_allowed() { | |||
when(systemPasscode.isValid(any())).thenReturn(true); | |||
TestResponse response = ws.newRequest().execute(); | |||
String content = response.getInput(); | |||
assertThat(content) | |||
.contains("# HELP is_web_up Tells whether web service is up") | |||
.contains("# TYPE is_web_up gauge") | |||
.contains("is_web_up 0.0"); | |||
} | |||
@Test | |||
public void authentication_bearer_passcode_is_allowed() { | |||
when(bearerPasscode.isValid(any())).thenReturn(true); | |||
TestResponse response = ws.newRequest().execute(); | |||
String content = response.getInput(); | |||
assertThat(content) | |||
.contains("# HELP is_web_up Tells whether web service is up") | |||
.contains("# TYPE is_web_up gauge") | |||
.contains("is_web_up 0.0"); | |||
} | |||
@Test | |||
public void authenticated_global_admin_is_not_allowed_in_safe_mode() { | |||
userSession.logIn().setSystemAdministrator(); | |||
TestRequest testRequest = ws.newRequest(); | |||
Assertions.assertThatThrownBy(testRequest::execute) | |||
.hasMessage("Insufficient privileges") | |||
.isInstanceOf(ForbiddenException.class); | |||
} | |||
} |
@@ -23,14 +23,13 @@ import org.junit.Test; | |||
import org.sonar.core.platform.ComponentContainer; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER; | |||
public class SafemodeSystemWsModuleTest { | |||
@Test | |||
public void verify_count_of_added_components() { | |||
ComponentContainer container = new ComponentContainer(); | |||
new SafemodeSystemWsModule().configure(container); | |||
assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 9); | |||
assertThat(container.size()).isPositive(); | |||
} | |||
} |
@@ -121,6 +121,7 @@ import org.sonar.server.measure.ws.MeasuresWsModule; | |||
import org.sonar.server.metric.MetricFinder; | |||
import org.sonar.server.metric.UnanalyzedLanguageMetrics; | |||
import org.sonar.server.metric.ws.MetricsWsModule; | |||
import org.sonar.server.monitoring.MonitoringWsModule; | |||
import org.sonar.server.newcodeperiod.ws.NewCodePeriodsWsModule; | |||
import org.sonar.server.notification.NotificationModule; | |||
import org.sonar.server.notification.ws.NotificationWsModule; | |||
@@ -268,6 +269,7 @@ public class PlatformLevel4 extends PlatformLevel { | |||
ServerWs.class, | |||
IndexDefinitions.class, | |||
WebAnalyticsLoaderImpl.class, | |||
MonitoringWsModule.class, | |||
// batch | |||
BatchWsModule.class, |
@@ -40,6 +40,7 @@ import org.sonarqube.ws.client.l10n.L10nService; | |||
import org.sonarqube.ws.client.languages.LanguagesService; | |||
import org.sonarqube.ws.client.measures.MeasuresService; | |||
import org.sonarqube.ws.client.metrics.MetricsService; | |||
import org.sonarqube.ws.client.monitoring.MonitoringService; | |||
import org.sonarqube.ws.client.navigation.NavigationService; | |||
import org.sonarqube.ws.client.newcodeperiods.NewCodePeriodsService; | |||
import org.sonarqube.ws.client.notifications.NotificationsService; | |||
@@ -104,6 +105,7 @@ class DefaultWsClient implements WsClient { | |||
private final LanguagesService languagesService; | |||
private final MeasuresService measuresService; | |||
private final MetricsService metricsService; | |||
private final MonitoringService monitoringService; | |||
private final NavigationService navigationService; | |||
private final NewCodePeriodsService newCodePeriodsService; | |||
private final NotificationsService notificationsService; | |||
@@ -161,6 +163,7 @@ class DefaultWsClient implements WsClient { | |||
this.languagesService = new LanguagesService(wsConnector); | |||
this.measuresService = new MeasuresService(wsConnector); | |||
this.metricsService = new MetricsService(wsConnector); | |||
this.monitoringService = new MonitoringService(wsConnector); | |||
this.navigationService = new NavigationService(wsConnector); | |||
this.newCodePeriodsService = new NewCodePeriodsService(wsConnector); | |||
this.notificationsService = new NotificationsService(wsConnector); | |||
@@ -298,6 +301,11 @@ class DefaultWsClient implements WsClient { | |||
return metricsService; | |||
} | |||
@Override | |||
public MonitoringService monitoring() { | |||
return monitoringService; | |||
} | |||
@Override | |||
public NavigationService navigation() { | |||
return navigationService; |
@@ -40,6 +40,7 @@ import org.sonarqube.ws.client.l10n.L10nService; | |||
import org.sonarqube.ws.client.languages.LanguagesService; | |||
import org.sonarqube.ws.client.measures.MeasuresService; | |||
import org.sonarqube.ws.client.metrics.MetricsService; | |||
import org.sonarqube.ws.client.monitoring.MonitoringService; | |||
import org.sonarqube.ws.client.navigation.NavigationService; | |||
import org.sonarqube.ws.client.newcodeperiods.NewCodePeriodsService; | |||
import org.sonarqube.ws.client.notifications.NotificationsService; | |||
@@ -202,4 +203,6 @@ public interface WsClient { | |||
BatchService batch(); | |||
SecurityReportsService securityReports(); | |||
MonitoringService monitoring(); | |||
} |
@@ -0,0 +1,58 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 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.ws.client.monitoring; | |||
import javax.annotation.Generated; | |||
import org.sonarqube.ws.client.BaseService; | |||
import org.sonarqube.ws.client.GetRequest; | |||
import org.sonarqube.ws.client.WsConnector; | |||
/** | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/monitoring">Further information about this web service online</a> | |||
*/ | |||
@Generated("sonar-ws-generator") | |||
public class MonitoringService extends BaseService { | |||
private String bearerToken; | |||
public MonitoringService(WsConnector wsConnector) { | |||
super(wsConnector, "api/monitoring"); | |||
} | |||
/** | |||
* | |||
* This is a GET request. | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/monitoring/metrics">Further information about this action online (including a response example)</a> | |||
* @since 9.3 | |||
* @return | |||
*/ | |||
public String metrics(String mediaType) { | |||
GetRequest request = new GetRequest(path("metrics")).setMediaType(mediaType); | |||
if (bearerToken != null && !bearerToken.isEmpty()) { | |||
request.setHeader("Authorization", "Bearer " + bearerToken); | |||
} | |||
return call(request).content(); | |||
} | |||
public MonitoringService withBearerToken(String bearerToken) { | |||
this.bearerToken = bearerToken; | |||
return this; | |||
} | |||
} |