diff options
author | Matteo Mara <matteo.mara@sonarsource.com> | 2022-07-05 15:52:26 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-07-06 20:03:56 +0000 |
commit | 6354fe3096b07caf9eb017ab7b4e0565f6d303fc (patch) | |
tree | 593d31be7e2902f186e85d6d0a588288162a73fe /sonar-scanner-engine/src | |
parent | 6a401f73236a70f702b64646d8bdec7c5a90e15d (diff) | |
download | sonarqube-6354fe3096b07caf9eb017ab7b4e0565f6d303fc.tar.gz sonarqube-6354fe3096b07caf9eb017ab7b4e0565f6d303fc.zip |
SONAR-16567 add an analysis warning when using a token expiring in less than 7 days.
Diffstat (limited to 'sonar-scanner-engine/src')
5 files changed, 100 insertions, 21 deletions
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/DefaultScannerWsClient.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/DefaultScannerWsClient.java index 8dc5dc3a39f..0be294a4f99 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/DefaultScannerWsClient.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/DefaultScannerWsClient.java @@ -23,11 +23,18 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.annotation.CheckForNull; import org.apache.commons.lang.StringUtils; import org.sonar.api.CoreProperties; +import org.sonar.api.notifications.AnalysisWarnings; import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; @@ -41,21 +48,28 @@ import org.sonarqube.ws.client.WsResponse; import static java.lang.String.format; import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_FORBIDDEN; +import static java.net.HttpURLConnection.HTTP_OK; import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; +import static org.sonar.api.utils.DateUtils.DATETIME_FORMAT; import static org.sonar.api.utils.Preconditions.checkState; public class DefaultScannerWsClient implements ScannerWsClient { private static final int MAX_ERROR_MSG_LEN = 128; + private static final String SQ_TOKEN_EXPIRATION_HEADER = "sq-authentication-token-expiration"; private static final Logger LOG = Loggers.get(DefaultScannerWsClient.class); + private final Set<String> warningMessages = new HashSet<>(); + private final WsClient target; private final boolean hasCredentials; private final GlobalAnalysisMode globalMode; + private final AnalysisWarnings analysisWarnings; - public DefaultScannerWsClient(WsClient target, boolean hasCredentials, GlobalAnalysisMode globalMode) { + public DefaultScannerWsClient(WsClient target, boolean hasCredentials, GlobalAnalysisMode globalMode, AnalysisWarnings analysisWarnings) { this.target = target; this.hasCredentials = hasCredentials; this.globalMode = globalMode; + this.analysisWarnings = analysisWarnings; } /** @@ -73,6 +87,7 @@ public class DefaultScannerWsClient implements ScannerWsClient { WsResponse response = target.wsConnector().call(request); profiler.stopDebug(format("%s %d %s", request.getMethod(), response.code(), response.requestUrl())); failIfUnauthorized(response); + checkAuthenticationWarnings(response); return response; } @@ -96,7 +111,6 @@ public class DefaultScannerWsClient implements ScannerWsClient { // not authenticated - see https://jira.sonarsource.com/browse/SONAR-4048 throw MessageException.of(format("Not authorized. Analyzing this project requires authentication. " + "Please provide a user token in %s or other credentials in %s and %s.", CoreProperties.LOGIN, CoreProperties.LOGIN, CoreProperties.PASSWORD)); - } if (code == HTTP_FORBIDDEN) { throw MessageException.of("You're not authorized to run analysis. Please contact the project administrator."); @@ -112,6 +126,33 @@ public class DefaultScannerWsClient implements ScannerWsClient { response.failIfNotSuccessful(); } + private void checkAuthenticationWarnings(WsResponse response) { + if (response.code() == HTTP_OK) { + response.header(SQ_TOKEN_EXPIRATION_HEADER).ifPresent(expirationDate -> { + if (isTokenExpiringInOneWeek(expirationDate)) { + addAnalysisWarning(expirationDate); + } + }); + } + } + + private static boolean isTokenExpiringInOneWeek(String expirationDate) { + ZonedDateTime localDateTime = ZonedDateTime.now(ZoneOffset.UTC); + ZonedDateTime headerDateTime = LocalDateTime.from(DateTimeFormatter.ofPattern(DATETIME_FORMAT) + .parse(expirationDate)).minusDays(7).atZone(ZoneOffset.UTC); + return localDateTime.isAfter(headerDateTime); + } + + private void addAnalysisWarning(String tokenExpirationDate) { + String warningMessage = "The token used for this analysis will expire on: " + tokenExpirationDate; + if (!warningMessages.contains(warningMessage)) { + warningMessages.add(warningMessage); + LOG.warn(warningMessage); + LOG.warn("Analysis executed with this token after the expiration date will fail."); + } + analysisWarnings.addUnique(warningMessage + "\nAnalysis executed with this token after the expiration date will fail."); + } + /** * Tries to form a short and relevant error message from the exception, to be displayed in the console. */ diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClientProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClientProvider.java index 8e86aa8b0b4..7a45f5cc35a 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClientProvider.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerWsClientProvider.java @@ -20,6 +20,7 @@ package org.sonar.scanner.bootstrap; import org.sonar.api.CoreProperties; +import org.sonar.api.notifications.AnalysisWarnings; import org.sonar.api.utils.System2; import org.sonar.batch.bootstrapper.EnvironmentInformation; import org.sonarqube.ws.client.HttpConnector; @@ -37,7 +38,7 @@ public class ScannerWsClientProvider { @Bean("DefaultScannerWsClient") public DefaultScannerWsClient provide(ScannerProperties scannerProps, EnvironmentInformation env, GlobalAnalysisMode globalMode, - System2 system) { + System2 system, AnalysisWarnings analysisWarnings) { String url = defaultIfBlank(scannerProps.property("sonar.host.url"), "http://localhost:9000"); HttpConnector.Builder connectorBuilder = HttpConnector.newBuilder(); @@ -58,6 +59,6 @@ public class ScannerWsClientProvider { } return new DefaultScannerWsClient(WsClientFactories.getDefault().newClient(connectorBuilder.build()), login != null, - globalMode); + globalMode, analysisWarnings); } } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/DefaultScannerWsClientTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/DefaultScannerWsClientTest.java index 752b6ef0891..3da98be8901 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/DefaultScannerWsClientTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/DefaultScannerWsClientTest.java @@ -19,12 +19,16 @@ */ package org.sonar.scanner.bootstrap; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.Collections; import java.util.List; import org.apache.commons.lang.StringUtils; import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; +import org.sonar.api.notifications.AnalysisWarnings; import org.sonar.api.utils.MessageException; import org.sonar.api.utils.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; @@ -39,6 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.api.utils.DateUtils.DATETIME_FORMAT; public class DefaultScannerWsClientTest { @@ -47,6 +52,8 @@ public class DefaultScannerWsClientTest { private final WsClient wsClient = mock(WsClient.class, Mockito.RETURNS_DEEP_STUBS); + private final AnalysisWarnings analysisWarnings = mock(AnalysisWarnings.class); + @Test public void log_and_profile_request_if_debug_level() { WsRequest request = newRequest(); @@ -54,7 +61,7 @@ public class DefaultScannerWsClientTest { when(wsClient.wsConnector().call(request)).thenReturn(response); logTester.setLevel(LoggerLevel.DEBUG); - DefaultScannerWsClient underTest = new DefaultScannerWsClient(wsClient, false, new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap()))); + DefaultScannerWsClient underTest = new DefaultScannerWsClient(wsClient, false, new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap())), analysisWarnings); WsResponse result = underTest.call(request); @@ -92,10 +99,10 @@ public class DefaultScannerWsClientTest { when(wsClient.wsConnector().call(request)).thenReturn(response); assertThatThrownBy(() -> new DefaultScannerWsClient(wsClient, false, - new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap()))).call(request)) - .isInstanceOf(MessageException.class) - .hasMessage("Not authorized. Analyzing this project requires authentication. Please provide a user token in sonar.login or other " + - "credentials in sonar.login and sonar.password."); + new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap())), analysisWarnings).call(request)) + .isInstanceOf(MessageException.class) + .hasMessage("Not authorized. Analyzing this project requires authentication. Please provide a user token in sonar.login or other " + + "credentials in sonar.login and sonar.password."); } @Test @@ -105,9 +112,9 @@ public class DefaultScannerWsClientTest { when(wsClient.wsConnector().call(request)).thenReturn(response); assertThatThrownBy(() -> new DefaultScannerWsClient(wsClient, /* credentials are configured */true, - new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap()))).call(request)) - .isInstanceOf(MessageException.class) - .hasMessage("Not authorized. Please check the properties sonar.login and sonar.password."); + new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap())), analysisWarnings).call(request)) + .isInstanceOf(MessageException.class) + .hasMessage("Not authorized. Please check the properties sonar.login and sonar.password."); } @Test @@ -118,9 +125,33 @@ public class DefaultScannerWsClientTest { when(wsClient.wsConnector().call(request)).thenReturn(response); assertThatThrownBy(() -> new DefaultScannerWsClient(wsClient, true, - new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap()))).call(request)) - .isInstanceOf(MessageException.class) - .hasMessage("You're not authorized to run analysis. Please contact the project administrator."); + new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap())), analysisWarnings).call(request)) + .isInstanceOf(MessageException.class) + .hasMessage("You're not authorized to run analysis. Please contact the project administrator."); + } + + @Test + public void warnings_are_added_when_expiration_approaches() { + WsRequest request = newRequest(); + String expirationDate = DateTimeFormatter + .ofPattern(DATETIME_FORMAT) + .format(LocalDateTime.now().atOffset(ZoneOffset.UTC).plusDays(5)); + WsResponse response = newResponse() + .setCode(200) + .setExpirationDate(expirationDate); + when(wsClient.wsConnector().call(request)).thenReturn(response); + + logTester.setLevel(LoggerLevel.DEBUG); + DefaultScannerWsClient underTest = new DefaultScannerWsClient(wsClient, false, new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap())), analysisWarnings); + underTest.call(request); + //the second call should not add the same warning twice + underTest.call(request); + + // check logs + List<String> warningLogs = logTester.logs(LoggerLevel.WARN); + assertThat(warningLogs).hasSize(2); + assertThat(warningLogs.get(0)).contains("The token used for this analysis will expire on: " + expirationDate); + assertThat(warningLogs.get(1)).contains("Analysis executed with this token after the expiration date will fail."); } @Test @@ -132,9 +163,9 @@ public class DefaultScannerWsClientTest { when(wsClient.wsConnector().call(request)).thenReturn(response); assertThatThrownBy(() -> new DefaultScannerWsClient(wsClient, true, - new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap()))).call(request)) - .isInstanceOf(MessageException.class) - .hasMessage("Boo! bad request! bad!"); + new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap())), analysisWarnings).call(request)) + .isInstanceOf(MessageException.class) + .hasMessage("Boo! bad request! bad!"); } private MockWsResponse newResponse() { diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PluginFilesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PluginFilesTest.java index d2b7cd4840d..233028242bd 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PluginFilesTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PluginFilesTest.java @@ -48,6 +48,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.notifications.AnalysisWarnings; import org.sonar.scanner.bootstrap.ScannerPluginInstaller.InstalledPlugin; import org.sonarqube.ws.client.HttpConnector; import org.sonarqube.ws.client.WsClientFactories; @@ -56,6 +57,7 @@ import static org.apache.commons.io.FileUtils.moveFile; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import static org.mockito.Mockito.mock; public class PluginFilesTest { @@ -64,6 +66,8 @@ public class PluginFilesTest { @Rule public MockWebServer server = new MockWebServer(); + private final AnalysisWarnings analysisWarnings = mock(AnalysisWarnings.class); + private File userHome; private PluginFiles underTest; @@ -72,7 +76,7 @@ public class PluginFilesTest { HttpConnector connector = HttpConnector.newBuilder().url(server.url("/").toString()).build(); GlobalAnalysisMode analysisMode = new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap())); DefaultScannerWsClient wsClient = new DefaultScannerWsClient(WsClientFactories.getDefault().newClient(connector), false, - analysisMode); + analysisMode, analysisWarnings); userHome = temp.newFolder(); MapSettings settings = new MapSettings(); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerWsClientProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerWsClientProviderTest.java index e6e6d3ba627..c4db3d9258b 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerWsClientProviderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerWsClientProviderTest.java @@ -40,7 +40,8 @@ public class ScannerWsClientProviderTest { ScannerProperties settings = new ScannerProperties(new HashMap<>()); DefaultScannerWsClient client = underTest.provide(settings, env, new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap())), - mock(System2.class)); + mock(System2.class),warning -> { + }); assertThat(client).isNotNull(); assertThat(client.baseUrl()).isEqualTo("http://localhost:9000/"); @@ -61,7 +62,8 @@ public class ScannerWsClientProviderTest { ScannerProperties settings = new ScannerProperties(props); DefaultScannerWsClient client = underTest.provide(settings, env, new GlobalAnalysisMode(new ScannerProperties(Collections.emptyMap())), - mock(System2.class)); + mock(System2.class),warning -> { + }); assertThat(client).isNotNull(); HttpConnector httpConnector = (HttpConnector) client.wsConnector(); |