--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.db.migration.version.v102;
+
+import java.sql.SQLException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.issue.impact.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.rule.internal.ImpactMapper;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+public class PopulateDefaultImpactsInRules extends DataChange {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PopulateDefaultImpactsInRules.class);
+
+ private static final String SELECT_QUERY = """
+ SELECT r.uuid, rule_type, priority, ad_hoc_type, ad_hoc_severity, is_ad_hoc
+ FROM rules r
+ LEFT JOIN rules_default_impacts rdi ON rdi.rule_uuid = r.uuid
+ WHERE rdi.uuid IS NULL
+ """;
+
+ private static final String INSERT_QUERY = """
+ INSERT INTO rules_default_impacts (uuid, rule_uuid, software_quality, severity)
+ VALUES (?, ?, ?, ?)
+ """;
+
+ public PopulateDefaultImpactsInRules(Database db) {
+ super(db);
+ }
+
+ @Override
+ protected void execute(Context context) throws SQLException {
+ MassUpdate massUpdate = context.prepareMassUpdate();
+ massUpdate.select(SELECT_QUERY);
+ massUpdate.update(INSERT_QUERY);
+
+ massUpdate.execute((row, update, index) -> {
+ String ruleUuid = row.getString(1);
+ String ruleType = row.getString(2);
+ String severity = row.getString(3);
+ String adhocType = row.getString(4);
+ String adhocSeverity = row.getString(5);
+ boolean isAdhoc = row.getBoolean(6);
+
+ SoftwareQuality softwareQuality;
+ Severity impactSeverity;
+
+ try {
+ RuleType effectiveType = null;
+ String effectiveSeverity = null;
+ if (isAdhoc && adhocType != null && adhocSeverity != null) {
+ effectiveType = RuleType.valueOf(Integer.valueOf(adhocType));
+ effectiveSeverity = adhocSeverity;
+ } else if (!isAdhoc && ruleType != null && !ruleType.equals("0") && severity != null) {
+ effectiveType = RuleType.valueOf(Integer.valueOf(ruleType));
+ effectiveSeverity = org.sonar.api.rule.Severity.ALL.get(Integer.valueOf(severity));
+ } else if (!isAdhoc) {
+ //When type and severity are missing, we are in the case of a "placeholder" adhoc_rule that was created as default with an external issue.
+ //In that case, we want to set default values for the impact. Otherwise, we don't populate the impact
+ return false;
+ }
+ if (effectiveType == RuleType.SECURITY_HOTSPOT) {
+ return false;
+ }
+ if (effectiveType != null && effectiveSeverity != null) {
+ softwareQuality = ImpactMapper.convertToSoftwareQuality(effectiveType);
+ impactSeverity = ImpactMapper.convertToImpactSeverity(effectiveSeverity);
+ } else {
+ softwareQuality = SoftwareQuality.MAINTAINABILITY;
+ impactSeverity = Severity.MEDIUM;
+ }
+ } catch (Exception e) {
+ LOG.warn("Error while mapping type to impact for rule '%s'".formatted(ruleUuid));
+ LOG.debug("Error while mapping type to impact for rule '%s'".formatted(ruleUuid), e);
+ return false;
+ }
+
+ update.setString(1, Uuids.create())
+ .setString(2, ruleUuid)
+ .setString(3, softwareQuality.name())
+ .setString(4, impactSeverity.name());
+ return true;
+ });
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.db.migration.version.v102;
+
+import java.sql.SQLException;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.batch.rule.Severity;
+import org.sonar.api.issue.impact.SoftwareQuality;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.testfixtures.log.LogTester;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.CoreDbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class PopulateDefaultImpactsInRulesTest {
+ private static final String TABLE_NAME = "rules";
+
+ @Rule
+ public final CoreDbTester db = CoreDbTester.createForSchema(PopulateDefaultImpactsInRulesTest.class, "schema.sql");
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ private final PopulateDefaultImpactsInRules underTest = new PopulateDefaultImpactsInRules(db.database());
+
+ @Test
+ public void execute_whenRulesDoNotExist_shouldNotFail() {
+ assertThatCode(underTest::execute).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void execute_whenRulesHasTypeAndSeverity_shouldCreateImpact() throws SQLException {
+ insertRuleWithType("uuid", RuleType.CODE_SMELL, Severity.MAJOR);
+ underTest.execute();
+
+ assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts"))
+ .extracting(stringObjectMap -> stringObjectMap.get("SOFTWARE_QUALITY"),
+ stringObjectMap -> stringObjectMap.get("SEVERITY"))
+ .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY.name(), org.sonar.api.issue.impact.Severity.MEDIUM.name()));
+ }
+
+ @Test
+ public void execute_shouldBeReentrant() throws SQLException {
+ insertRuleWithType("uuid", RuleType.CODE_SMELL, Severity.MAJOR);
+ underTest.execute();
+ underTest.execute();
+
+ assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts"))
+ .hasSize(1)
+ .extracting(stringObjectMap -> stringObjectMap.get("SOFTWARE_QUALITY"),
+ stringObjectMap -> stringObjectMap.get("SEVERITY"))
+ .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY.name(), org.sonar.api.issue.impact.Severity.MEDIUM.name()));
+
+ }
+
+ @Test
+ public void execute_whenAdhocRulesHasTypeAndSeverity_shouldCreateImpact() throws SQLException {
+ insertRuleWithAdHocType("uuid", RuleType.CODE_SMELL, Severity.MAJOR);
+ underTest.execute();
+
+ assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts"))
+ .hasSize(1)
+ .extracting(stringObjectMap -> stringObjectMap.get("SOFTWARE_QUALITY"),
+ stringObjectMap -> stringObjectMap.get("SEVERITY"))
+ .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY.name(), org.sonar.api.issue.impact.Severity.MEDIUM.name()));
+
+ }
+
+ @Test
+ public void execute_whenAdhocRulesHasImpactAlready_shouldNotCreateImpact() throws SQLException {
+ insertRuleWithAdHocType("uuid", RuleType.CODE_SMELL, Severity.MAJOR);
+ insertImpact("uuid", SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.HIGH);
+ underTest.execute();
+
+ assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts"))
+ .hasSize(1)
+ .extracting(stringObjectMap -> stringObjectMap.get("SOFTWARE_QUALITY"),
+ stringObjectMap -> stringObjectMap.get("SEVERITY"))
+ .containsExactly(tuple(SoftwareQuality.SECURITY.name(), org.sonar.api.issue.impact.Severity.HIGH.name()));
+
+ }
+
+ @Test
+ public void execute_whenNoTypeAndSeverityDefined_shouldNotCreateImpact() throws SQLException {
+ insertRuleWithType("uuid", null, null);
+ underTest.execute();
+
+ assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts"))
+ .isEmpty();
+
+ }
+
+ @Test
+ public void execute_whenInvalidValueDefined_shouldNotCreateImpactAndLog() throws SQLException {
+ insertInvalidRule("uuid");
+ underTest.execute();
+
+ assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts"))
+ .isEmpty();
+ assertThat(logTester.logs()).contains("Error while mapping type to impact for rule 'uuid'");
+
+ }
+
+ @Test
+ public void execute_whenTypeIsHotspot_shouldNotCreateImpactAndLog() throws SQLException {
+ insertRuleWithType("uuid", RuleType.SECURITY_HOTSPOT, Severity.MAJOR);
+ underTest.execute();
+
+ assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts"))
+ .isEmpty();
+ assertThat(logTester.logs()).doesNotContain("Error while mapping type to impact for rule 'uuid'");
+ }
+
+ @Test
+ public void execute_whenRuleHasEmptyFields_shouldCreateADefaultImpact() throws SQLException {
+ insertPlaceholderAdhocRule("uuid");
+ underTest.execute();
+
+ assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts"))
+ .hasSize(1)
+ .extracting(stringObjectMap -> stringObjectMap.get("SOFTWARE_QUALITY"),
+ stringObjectMap -> stringObjectMap.get("SEVERITY"))
+ .containsExactly(tuple(SoftwareQuality.MAINTAINABILITY.name(), org.sonar.api.issue.impact.Severity.MEDIUM.name()));
+ }
+
+ @Test
+ public void execute_whenStandardRuleHasBothAdhocAndStandardTypeAndSeverity_shouldCreateADefaultImpactWithAdhocTypes() throws SQLException {
+ insertRule("uuid", RuleType.CODE_SMELL, Severity.CRITICAL, RuleType.VULNERABILITY, Severity.MINOR, true);
+ underTest.execute();
+
+ assertThat(db.select("select SOFTWARE_QUALITY, SEVERITY from rules_default_impacts"))
+ .hasSize(1)
+ .extracting(stringObjectMap -> stringObjectMap.get("SOFTWARE_QUALITY"),
+ stringObjectMap -> stringObjectMap.get("SEVERITY"))
+ .containsExactly(tuple(SoftwareQuality.SECURITY.name(), org.sonar.api.issue.impact.Severity.LOW.name()));
+ }
+
+ private void insertRuleWithType(String uuid, @Nullable RuleType ruleType, @Nullable Severity severity) {
+ insertRule(uuid, ruleType, severity, null, null);
+ }
+
+ private void insertRuleWithAdHocType(String uuid, @Nullable RuleType adHocType, @Nullable Severity adHocseverity) {
+ insertRule(uuid, null, null, adHocType, adHocseverity);
+ }
+
+
+ private void insertRule(String uuid, @Nullable RuleType ruleType, @Nullable Severity severity, @Nullable RuleType adHocType, @Nullable Severity adHocseverity) {
+ insertRule(uuid, ruleType, severity, adHocType, adHocseverity, adHocType != null);
+ }
+
+ private void insertRule(String uuid, @Nullable RuleType ruleType, @Nullable Severity severity, @Nullable RuleType adHocType, @Nullable Severity adHocseverity, boolean isAdhoc) {
+ db.executeInsert(TABLE_NAME,
+ "UUID", uuid,
+ "PLUGIN_RULE_KEY", "key",
+ "PLUGIN_NAME", "name",
+ "SCOPE", "1",
+ "RULE_TYPE", ruleType != null ? ruleType.getDbConstant() : null,
+ "PRIORITY", severity != null ? org.sonar.api.rule.Severity.ALL.indexOf(severity.name()) : null,
+ "AD_HOC_TYPE", adHocType != null ? adHocType.getDbConstant() : null,
+ "AD_HOC_SEVERITY", adHocseverity != null ? adHocseverity.name() : null,
+ "IS_TEMPLATE", false,
+ "IS_AD_HOC", isAdhoc,
+ "IS_EXTERNAL", isAdhoc);
+ }
+
+ private void insertInvalidRule(String uuid) {
+ db.executeInsert(TABLE_NAME,
+ "UUID", uuid,
+ "PLUGIN_RULE_KEY", "key",
+ "PLUGIN_NAME", "name",
+ "SCOPE", "1",
+ "RULE_TYPE", "-1",
+ "PRIORITY", "-1",
+ "AD_HOC_TYPE", "-1",
+ "AD_HOC_SEVERITY", "-1",
+ "IS_TEMPLATE", false,
+ "IS_AD_HOC", false,
+ "IS_EXTERNAL", false);
+ }
+
+ private void insertPlaceholderAdhocRule(String uuid) {
+ db.executeInsert(TABLE_NAME,
+ "UUID", uuid,
+ "PLUGIN_RULE_KEY", "key",
+ "PLUGIN_NAME", "name",
+ "SCOPE", "1",
+ "IS_TEMPLATE", false,
+ "IS_AD_HOC", true,
+ "IS_EXTERNAL", false);
+ }
+
+ private void insertImpact(String ruleUuid, SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity) {
+ db.executeInsert("RULES_DEFAULT_IMPACTS",
+ "UUID", Uuids.create(),
+ "RULE_UUID", ruleUuid,
+ "SOFTWARE_QUALITY", softwareQuality.name(),
+ "SEVERITY", severity.name());
+ }
+
+}
--- /dev/null
+CREATE TABLE "RULES"
+(
+ "UUID" CHARACTER VARYING(40) NOT NULL,
+ "NAME" CHARACTER VARYING(200),
+ "PLUGIN_RULE_KEY" CHARACTER VARYING(200) NOT NULL,
+ "PLUGIN_KEY" CHARACTER VARYING(200),
+ "PLUGIN_CONFIG_KEY" CHARACTER VARYING(200),
+ "PLUGIN_NAME" CHARACTER VARYING(255) NOT NULL,
+ "SCOPE" CHARACTER VARYING(20) NOT NULL,
+ "PRIORITY" INTEGER,
+ "STATUS" CHARACTER VARYING(40),
+ "LANGUAGE" CHARACTER VARYING(20),
+ "DEF_REMEDIATION_FUNCTION" CHARACTER VARYING(20),
+ "DEF_REMEDIATION_GAP_MULT" CHARACTER VARYING(20),
+ "DEF_REMEDIATION_BASE_EFFORT" CHARACTER VARYING(20),
+ "GAP_DESCRIPTION" CHARACTER VARYING(4000),
+ "SYSTEM_TAGS" CHARACTER VARYING(4000),
+ "IS_TEMPLATE" BOOLEAN DEFAULT FALSE NOT NULL,
+ "DESCRIPTION_FORMAT" CHARACTER VARYING(20),
+ "RULE_TYPE" TINYINT,
+ "SECURITY_STANDARDS" CHARACTER VARYING(4000),
+ "IS_AD_HOC" BOOLEAN NOT NULL,
+ "IS_EXTERNAL" BOOLEAN NOT NULL,
+ "CREATED_AT" BIGINT,
+ "UPDATED_AT" BIGINT,
+ "TEMPLATE_UUID" CHARACTER VARYING(40),
+ "NOTE_DATA" CHARACTER LARGE OBJECT,
+ "NOTE_USER_UUID" CHARACTER VARYING(255),
+ "NOTE_CREATED_AT" BIGINT,
+ "NOTE_UPDATED_AT" BIGINT,
+ "REMEDIATION_FUNCTION" CHARACTER VARYING(20),
+ "REMEDIATION_GAP_MULT" CHARACTER VARYING(20),
+ "REMEDIATION_BASE_EFFORT" CHARACTER VARYING(20),
+ "TAGS" CHARACTER VARYING(4000),
+ "AD_HOC_NAME" CHARACTER VARYING(200),
+ "AD_HOC_DESCRIPTION" CHARACTER LARGE OBJECT,
+ "AD_HOC_SEVERITY" CHARACTER VARYING(10),
+ "AD_HOC_TYPE" TINYINT,
+ "EDUCATION_PRINCIPLES" CHARACTER VARYING(255),
+ "CLEAN_CODE_ATTRIBUTE" CHARACTER VARYING(40)
+);
+ALTER TABLE "RULES"
+ ADD CONSTRAINT "PK_RULES" PRIMARY KEY ("UUID");
+CREATE UNIQUE INDEX "RULES_REPO_KEY" ON "RULES" ("PLUGIN_RULE_KEY" NULLS FIRST, "PLUGIN_NAME" NULLS FIRST);
+
+CREATE TABLE "RULES_DEFAULT_IMPACTS"
+(
+ "UUID" CHARACTER VARYING(40) NOT NULL,
+ "RULE_UUID" CHARACTER VARYING(40) NOT NULL,
+ "SOFTWARE_QUALITY" CHARACTER VARYING(40) NOT NULL,
+ "SEVERITY" CHARACTER VARYING(40) NOT NULL
+);
+ALTER TABLE "RULES_DEFAULT_IMPACTS"
+ ADD CONSTRAINT "PK_RULES_DEFAULT_IMPACTS" PRIMARY KEY ("UUID");
+CREATE UNIQUE INDEX "UNIQ_RUL_UUID_SOF_QUAL" ON "RULES_DEFAULT_IMPACTS" ("RULE_UUID" NULLS FIRST, "SOFTWARE_QUALITY" NULLS FIRST);
+