Skip to content

Commit

Permalink
SLCORE-668 Add textenterprise to the plugin allow list in connected m…
Browse files Browse the repository at this point in the history
…ode (#837)

Chicken-egg problem with sonar-text-enterprise: For a proper integration test we have to await their release and implementation into SonarQube, then we exchange the medium with an integration test.

---------

Co-authored-by: Tobi Hahnen <tobias.hahnen@sonarsource.com>
  • Loading branch information
jblievremont and thahnen committed Jan 24, 2024
1 parent 5730996 commit c0f20dd
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,22 @@ private PluginsLoadResult loadPlugins() {
Map<String, Path> pluginsToLoadByKey = new HashMap<>();
// order is important as e.g. embedded takes precedence over stored
pluginsToLoadByKey.putAll(serverConnection.getStoredPluginPathsByKey());
pluginsToLoadByKey.putAll(globalConfig.getEmbeddedPluginPathsByKey());
pluginsToLoadByKey.putAll(getEmbeddedPluginPathsByKey());
Set<Path> plugins = new HashSet<>(pluginsToLoadByKey.values());

var config = new Configuration(plugins, globalConfig.getEnabledLanguages(), globalConfig.isDataflowBugDetectionEnabled(), Optional.ofNullable(globalConfig.getNodeJsVersion()));
return new PluginsLoader().load(config);
}

private Map<String, Path> getEmbeddedPluginPathsByKey() {
if (serverConnection.supportsCustomSecrets()) {
var embeddedPluginsExceptSecrets = new HashMap<>(globalConfig.getEmbeddedPluginPathsByKey());
embeddedPluginsExceptSecrets.remove(Language.SECRETS.getPluginKey());
return embeddedPluginsExceptSecrets;
}
return globalConfig.getEmbeddedPluginPathsByKey();
}

private static class ActiveRulesContext {
private final boolean shouldSkipCleanCodeTaxonomy;
private final List<ActiveRule> activeRules = new ArrayList<>();
Expand Down Expand Up @@ -280,8 +289,7 @@ private ActiveRulesContext buildActiveRulesContext(ConnectedAnalysisConfiguratio
}
});

var supportSecretAnalysis = serverConnection.supportsSecretAnalysis();
if (!supportSecretAnalysis) {
if (!serverConnection.supportsSecretAnalysis()) {
analysisContext.get().allRulesDefinitionsByKey.values().stream()
.filter(ruleDefinition -> ruleDefinition.getLanguage() == Language.SECRETS)
.filter(this::shouldIncludeRuleForAnalysis)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* SonarLint Core - Medium Tests
* Copyright (C) 2016-2024 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 mediumtest;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import mediumtest.fixtures.SonarLintTestBackend;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
import org.sonarsource.sonarlint.core.ConnectedSonarLintEngineImpl;
import org.sonarsource.sonarlint.core.analysis.api.ClientInputFile;
import org.sonarsource.sonarlint.core.client.api.common.analysis.Issue;
import org.sonarsource.sonarlint.core.client.api.connected.ConnectedAnalysisConfiguration;
import org.sonarsource.sonarlint.core.client.api.connected.ConnectedGlobalConfiguration;
import org.sonarsource.sonarlint.core.commons.IssueSeverity;
import org.sonarsource.sonarlint.core.commons.Language;
import testutils.MockWebServerExtensionWithProtobuf;
import testutils.TestUtils;

import static mediumtest.fixtures.SonarLintBackendFixture.newBackend;
import static mediumtest.fixtures.SonarLintBackendFixture.newFakeClient;
import static mediumtest.fixtures.storage.StorageFixture.newStorage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;

/**
* INFO: This test is only a placeholder for coverage on SLCORE-668 and will be replaced by a proper integration test
* once `sonar-text-enterprise` 2.8 is released and part of SonarQube 10.4!
*/
class ConnectedCustomSecretsMediumTests {
@RegisterExtension
private final MockWebServerExtensionWithProtobuf mockWebServerExtension = new MockWebServerExtensionWithProtobuf();

private static final String CONNECTION_ID = StringUtils.repeat("secret-connection", 30);
private static final String PROJECT_KEY = "secret-connected-project";
private static ConnectedSonarLintEngineImpl sonarlint;
private SonarLintTestBackend backend;

@BeforeAll
static void prepare(@TempDir Path slHome) {
var storage = newStorage(CONNECTION_ID)
.withServerVersion("10.4")
.withTextPlugin()
.withProject(PROJECT_KEY)
.withProject(PROJECT_KEY, project -> project
.withRuleSet("secrets", ruleSet -> ruleSet
.withActiveRule("secrets:S6290", "BLOCKER")))
.create(slHome);

var config = ConnectedGlobalConfiguration.sonarQubeBuilder()
.setConnectionId(CONNECTION_ID)
.setSonarLintUserHome(slHome)
.setStorageRoot(storage.getPath())
.setLogOutput((m, l) -> System.out.println(m))
.addEnabledLanguages(Language.SECRETS)
.build();
sonarlint = new ConnectedSonarLintEngineImpl(config);
}

@AfterAll
static void stop() throws ExecutionException, InterruptedException {
if (sonarlint != null) {
sonarlint.stop(true);
sonarlint = null;
}
}

@BeforeEach
void prepareBackend() {
var fakeClient = newFakeClient()
.build();
backend = newBackend()
.withSonarQubeConnection(CONNECTION_ID, mockWebServerExtension.url("/"))
.build(fakeClient);
}

@AfterEach
void stopBackend() throws ExecutionException, InterruptedException {
if (backend != null) {
backend.shutdown().get();
}
}

@Test
void test_analysis_with_text_analyzer_from_connection(@TempDir Path baseDir) throws Exception {
var inputFile = prepareInputFile(baseDir, "Foo.java",
"package com;\n"
+ "public class Foo {\n"
+ " public static final String KEY = \"AKIAIGKECZXA7AEIJLMQ\";\n"
+ "}", false);
final List<Issue> issues = new ArrayList<>();
sonarlint.analyze(ConnectedAnalysisConfiguration.builder()
.setProjectKey(PROJECT_KEY)
.setBaseDir(baseDir)
.addInputFile(inputFile)
.setModuleKey("key")
.build(),
new ConnectedIssueMediumTests.StoreIssueListener(issues), null, null);

assertThat(issues).extracting("ruleKey", "startLine", "inputFile.path", "severity").containsOnly(
tuple("secrets:S6290", 3, inputFile.getPath(), IssueSeverity.BLOCKER));
}

private ClientInputFile prepareInputFile(Path baseDir, String relativePath, String content, final boolean isTest) throws IOException {
final var file = new File(baseDir.toFile(), relativePath);
FileUtils.write(file, content, StandardCharsets.UTF_8);
return TestUtils.createInputFile(file.toPath(), relativePath, isTest);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import static testutils.PluginLocator.SONAR_JAVASCRIPT_PLUGIN_JAR_HASH;
import static testutils.PluginLocator.SONAR_JAVA_PLUGIN_JAR;
import static testutils.PluginLocator.SONAR_JAVA_PLUGIN_JAR_HASH;
import static testutils.PluginLocator.SONAR_TEXT_PLUGIN_JAR;

public class StorageFixture {
public static StorageBuilder newStorage(String connectionId) {
Expand Down Expand Up @@ -89,6 +90,10 @@ public StorageBuilder withJavaPlugin() {
return withPlugin(PluginLocator.getJavaPluginPath(), SONAR_JAVA_PLUGIN_JAR, SONAR_JAVA_PLUGIN_JAR_HASH, "java");
}

public StorageBuilder withTextPlugin() {
return withPlugin(PluginLocator.getTextPluginPath(), SONAR_TEXT_PLUGIN_JAR, "No-valid-hash", "secrets");
}

private StorageBuilder withPlugin(Path path, String jarName, String hash, String key) {
plugins.add(new Plugin(path, jarName, hash, key));
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -79,10 +81,17 @@ public PluginsLoadResult load(Configuration configuration) {
var instancesLoader = new PluginInstancesLoader();
var pluginInstancesByKeys = instancesLoader.instantiatePluginClasses(nonSkippedPlugins);

return new PluginsLoadResult(new LoadedPlugins(pluginInstancesByKeys, instancesLoader, maybeDbdAllowedPlugins(configuration.enableDataflowBugDetection)),
return new PluginsLoadResult(new LoadedPlugins(pluginInstancesByKeys, instancesLoader, additionalAllowedPlugins(configuration)),
pluginCheckResultByKeys);
}

private static Set<String> additionalAllowedPlugins(Configuration configuration) {
var allowedPluginsIds = new HashSet<String>();
allowedPluginsIds.add("textenterprise");
allowedPluginsIds.addAll(maybeDbdAllowedPlugins(configuration.enableDataflowBugDetection));
return Collections.unmodifiableSet(allowedPluginsIds);
}

private static Set<String> maybeDbdAllowedPlugins(boolean enableDataflowBugDetection) {
return DataflowBugDetection.getPluginAllowList(enableDataflowBugDetection);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.IntPredicate;
import java.util.stream.Collectors;
import org.apache.commons.io.FilenameUtils;
import org.sonarsource.sonarlint.core.commons.Language;
Expand All @@ -50,6 +51,7 @@
public class ServerConnection {
private static final SonarLintLogger LOG = SonarLintLogger.get();
private static final Version SECRET_ANALYSIS_MIN_SQ_VERSION = Version.create("9.9");
private static final Version CUSTOM_SECRETS_MIN_SQ_VERSION = Version.create("10.4");

private static final Version CLEAN_CODE_TAXONOMY_MIN_SQ_VERSION = Version.create("10.2");

Expand Down Expand Up @@ -207,16 +209,22 @@ public boolean permitsHotspotTracking() {
}

public boolean supportsSecretAnalysis() {
// when storage is not present, assume that secrets are not supported by server
return isSonarCloud || storage.serverInfo().read()
.map(serverInfo -> serverInfo.getVersion().compareToIgnoreQualifier(SECRET_ANALYSIS_MIN_SQ_VERSION) >= 0)
.orElse(false);
return isSonarCloud || compareSynchronizedServerVersion(SECRET_ANALYSIS_MIN_SQ_VERSION, i -> i >= 0);
}

public boolean supportsCustomSecrets() {
return !isSonarCloud && compareSynchronizedServerVersion(CUSTOM_SECRETS_MIN_SQ_VERSION, i -> i >= 0);
}

public boolean shouldSkipCleanCodeTaxonomy() {
// In connected mode, Clean Code taxonomy is skipped if the server is SonarQube < 10.2
return !isSonarCloud && storage.serverInfo().read()
.map(serverInfo -> serverInfo.getVersion().compareToIgnoreQualifier(CLEAN_CODE_TAXONOMY_MIN_SQ_VERSION) < 0)
return !isSonarCloud && compareSynchronizedServerVersion(CLEAN_CODE_TAXONOMY_MIN_SQ_VERSION, i -> i < 0);
}

private boolean compareSynchronizedServerVersion(Version version, IntPredicate comparisonPredicate) {
return storage.serverInfo().read()
.map(serverInfo -> serverInfo.getVersion().compareToIgnoreQualifier(version))
.map(comparisonPredicate::test)
.orElse(false);
}

Expand Down

0 comments on commit c0f20dd

Please sign in to comment.