aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/MigrateSmtpConfigurationIT.java283
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/DbVersion107.java3
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/MigrateSmtpConfiguration.java165
3 files changed, 450 insertions, 1 deletions
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/MigrateSmtpConfigurationIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/MigrateSmtpConfigurationIT.java
new file mode 100644
index 00000000000..f40b7a22fc7
--- /dev/null
+++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v107/MigrateSmtpConfigurationIT.java
@@ -0,0 +1,283 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-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 org.sonar.server.platform.db.migration.version.v107;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.assertj.core.groups.Tuple;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.sonar.api.testfixtures.log.LogTesterJUnit5;
+import org.sonar.api.utils.System2;
+import org.sonar.db.MigrationDbTester;
+import org.sonar.server.platform.db.migration.step.DataChange;
+
+import static java.lang.Math.min;
+import static java.lang.String.format;
+import static java.util.stream.Collectors.joining;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.sonar.server.platform.db.migration.version.v107.MigrateSmtpConfiguration.EMAIL_AUTH_METHOD;
+import static org.sonar.server.platform.db.migration.version.v107.MigrateSmtpConfiguration.EMAIL_FROM;
+import static org.sonar.server.platform.db.migration.version.v107.MigrateSmtpConfiguration.EMAIL_FROM_NAME;
+import static org.sonar.server.platform.db.migration.version.v107.MigrateSmtpConfiguration.EMAIL_PREFIX;
+import static org.sonar.server.platform.db.migration.version.v107.MigrateSmtpConfiguration.EMAIL_SMTP_HOST_SECURED;
+import static org.sonar.server.platform.db.migration.version.v107.MigrateSmtpConfiguration.EMAIL_SMTP_PASSWORD_SECURED;
+import static org.sonar.server.platform.db.migration.version.v107.MigrateSmtpConfiguration.EMAIL_SMTP_PORT_SECURED;
+import static org.sonar.server.platform.db.migration.version.v107.MigrateSmtpConfiguration.EMAIL_SMTP_SECURE_CONNECTION_SECURED;
+import static org.sonar.server.platform.db.migration.version.v107.MigrateSmtpConfiguration.EMAIL_SMTP_USERNAME_SECURED;
+import static org.sonar.server.platform.db.migration.version.v107.MigrateSmtpConfiguration.SMTP_LEGACY_CONFIG_PROP_KEYS;
+
+class MigrateSmtpConfigurationIT {
+
+ public static final String RANDOM_PROPERTY_KEY = "random.property";
+ public static final String RANDOM_PROPERTY_VALUE = "random value";
+ public static final String RANDOM_INTERNAL_PROPERTY_KEY = "random.internal.property";
+ public static final String RANDOM_INTERNAL_PROPERTY_VALUE = "random internal value";
+
+ @RegisterExtension
+ public final LogTesterJUnit5 logger = new LogTesterJUnit5();
+
+ @RegisterExtension
+ public final MigrationDbTester db = MigrationDbTester.createForMigrationStep(MigrateSmtpConfiguration.class);
+
+ private final System2 system2 = mock();
+ private final DataChange underTest = new MigrateSmtpConfiguration(db.database(), system2);
+
+ @BeforeEach
+ public void before() {
+ logger.clear();
+ }
+
+ @Test
+ void execute_whenNoSmtpConfig_shouldDoNothing() throws SQLException {
+ insertRandomProperty();
+ insertRandomInternalProperty();
+
+ underTest.execute();
+
+ assertNoInternalPropertiesHadBeenAdded();
+ assertOtherPropertiesLeftUntouched();
+ }
+
+ private void assertNoInternalPropertiesHadBeenAdded() {
+ Map<String, String> dbRows = new HashMap<>();
+ db.select(format("select kee, text_value from internal_properties where kee in (%s)", getPropertyKeysAsSqlList()))
+ .forEach(map -> dbRows.put((String) map.get("kee"), (String) map.get("text_value")));
+ assertThat(dbRows).isEmpty();
+ }
+
+ @Test
+ void execute_whenPartialSmtpConfig_shouldMigrate() throws SQLException {
+ Map<String, String> smtpProperties = new HashMap<>();
+ smtpProperties.put(EMAIL_SMTP_HOST_SECURED, "host");
+ smtpProperties.put(EMAIL_SMTP_USERNAME_SECURED, "username");
+ smtpProperties.put(EMAIL_FROM, "from");
+
+ insertProperties(smtpProperties);
+ insertRandomProperty();
+ insertRandomInternalProperty();
+
+ underTest.execute();
+
+ assertThatPropertiesAreMigrated(smtpProperties);
+ assertOtherPropertiesLeftUntouched();
+ }
+
+ @Test
+ void execute_whenFullSmtpConfig_shouldMigrate() throws SQLException {
+ Map<String, String> smtpProperties = new HashMap<>();
+ smtpProperties.put(EMAIL_SMTP_HOST_SECURED, "host");
+ smtpProperties.put(EMAIL_SMTP_PORT_SECURED, "port");
+ smtpProperties.put(EMAIL_SMTP_SECURE_CONNECTION_SECURED, "secure connection");
+ smtpProperties.put(EMAIL_SMTP_USERNAME_SECURED, "username");
+ smtpProperties.put(EMAIL_SMTP_PASSWORD_SECURED, "password");
+ smtpProperties.put(EMAIL_FROM, "from");
+ smtpProperties.put(EMAIL_FROM_NAME, "name");
+ smtpProperties.put(EMAIL_PREFIX, "prefix");
+
+ insertProperties(smtpProperties);
+ insertRandomProperty();
+ insertRandomInternalProperty();
+
+ underTest.execute();
+
+ assertThatPropertiesAreMigrated(smtpProperties);
+ assertOtherPropertiesLeftUntouched();
+ }
+
+ @Test
+ void execute_whenDefaultValuesUsed_shouldDefineThem() throws SQLException {
+ Map<String, String> smtpProperties = new HashMap<>();
+ smtpProperties.put(EMAIL_SMTP_HOST_SECURED, "host");
+
+ insertProperties(smtpProperties);
+
+ underTest.execute();
+
+ // This method adds all default values to the properties list
+ assertThatPropertiesAreMigrated(smtpProperties);
+ }
+
+ @ParameterizedTest
+ @MethodSource("secureConnectionOldToNewValues")
+ void execute_shouldMapSecureConnectionValues(String oldValue, String newValue) throws SQLException {
+ Map<String, String> smtpProperties = new HashMap<>();
+ smtpProperties.put(EMAIL_SMTP_SECURE_CONNECTION_SECURED, oldValue);
+
+ insertProperties(smtpProperties);
+
+ underTest.execute();
+
+ assertSecureConnectionValuesIsCorrectlyMapped(newValue);
+ }
+
+ private void assertSecureConnectionValuesIsCorrectlyMapped(String newValue) {
+ Map<String, String> dbRows = new HashMap<>();
+ db.select(format("select kee, text_value from internal_properties where kee = '%s'", EMAIL_SMTP_SECURE_CONNECTION_SECURED))
+ .forEach(map -> dbRows.put((String) map.get("kee"), (String) map.get("text_value")));
+ assertThat(dbRows).containsEntry(EMAIL_SMTP_SECURE_CONNECTION_SECURED, newValue);
+ }
+
+ static Object[][] secureConnectionOldToNewValues() {
+ return new Object[][]{
+ {"ssl", "SSLTLS"},
+ {"starttls", "STARTTLS"},
+ {"", "NONE"},
+ {"null", "NONE"},
+ {"random", "NONE"}
+ };
+ }
+
+ @Test
+ void execute_shouldBeReentrant() throws SQLException {
+ Map<String, String> smtpProperties = new HashMap<>();
+ smtpProperties.put(EMAIL_SMTP_HOST_SECURED, "host");
+ smtpProperties.put(EMAIL_SMTP_PORT_SECURED, "port");
+ smtpProperties.put(EMAIL_SMTP_SECURE_CONNECTION_SECURED, "secure connection");
+ smtpProperties.put(EMAIL_SMTP_USERNAME_SECURED, "username");
+ smtpProperties.put(EMAIL_SMTP_PASSWORD_SECURED, "password");
+ smtpProperties.put(EMAIL_FROM, "from");
+ smtpProperties.put(EMAIL_FROM_NAME, "name");
+ smtpProperties.put(EMAIL_PREFIX, "prefix");
+
+ insertProperties(smtpProperties);
+ insertRandomProperty();
+ insertRandomInternalProperty();
+
+ underTest.execute();
+ underTest.execute();
+
+ assertThatPropertiesAreMigrated(smtpProperties);
+ assertOtherPropertiesLeftUntouched();
+ }
+
+ private void insertRandomProperty() {
+ insertProperty(RANDOM_PROPERTY_KEY, RANDOM_PROPERTY_VALUE);
+ }
+
+ private void insertRandomInternalProperty() {
+ insertInternalProperty(RANDOM_INTERNAL_PROPERTY_KEY, RANDOM_INTERNAL_PROPERTY_VALUE);
+ }
+
+ private void assertThatPropertiesAreMigrated(Map<String, String> properties) {
+ addDefaultProperties(properties);
+ updatePropertyValues(properties);
+ assertThatPropertiesAreInInternalProperties(properties);
+ assertThatPropertiesAreNotInProperties();
+ }
+
+ private void updatePropertyValues(Map<String, String> properties) {
+ String currentValue = properties.get(EMAIL_SMTP_SECURE_CONNECTION_SECURED);
+ String newValue = switch (currentValue) {
+ case "ssl" -> "SSLTLS";
+ case "starttls" -> "STARTTLS";
+ default -> "NONE";
+ };
+ properties.put(EMAIL_SMTP_SECURE_CONNECTION_SECURED, newValue);
+ }
+
+ private void addDefaultProperties(Map<String, String> properties) {
+ Map<String, String> defaultPropertyValues = Map.of(
+ EMAIL_SMTP_SECURE_CONNECTION_SECURED, "NONE",
+ EMAIL_FROM, "noreply@nowhere",
+ EMAIL_FROM_NAME, "SonarQube",
+ EMAIL_PREFIX, "[SONARQUBE]",
+ EMAIL_AUTH_METHOD, "BASIC"
+ );
+ defaultPropertyValues.forEach((key, value) -> {
+ if (!properties.containsKey(key)) {
+ properties.put(key, value);
+ }
+ });
+ }
+
+ private void assertThatPropertiesAreInInternalProperties(Map<String, String> properties) {
+ Map<String, String> dbRows = new HashMap<>();
+ db.select(format("select kee, text_value from internal_properties where kee in (%s)", getPropertyKeysAsSqlList()))
+ .forEach(map -> dbRows.put((String) map.get("kee"), (String) map.get("text_value")));
+ assertThat(dbRows).containsExactlyInAnyOrderEntriesOf(properties);
+ }
+
+ private void assertThatPropertiesAreNotInProperties() {
+ assertThat(db.select(format("select * from properties where prop_key in (%s)", getPropertyKeysAsSqlList()))).isEmpty();
+ }
+
+ private static String getPropertyKeysAsSqlList() {
+ return SMTP_LEGACY_CONFIG_PROP_KEYS.stream().map(key -> "'" + key + "'").collect(joining(","));
+ }
+
+ private void assertOtherPropertiesLeftUntouched() {
+ assertRandomPropertyIsIntact();
+ assertRandomInternalPropertyIsIntact();
+ }
+
+ private void assertRandomPropertyIsIntact() {
+ List<Tuple> results = db.select("select * from properties")
+ .stream().map(map -> new Tuple(map.get("prop_key"), map.get("text_value")))
+ .toList();
+ assertThat(results).containsExactly(new Tuple(RANDOM_PROPERTY_KEY, RANDOM_PROPERTY_VALUE));
+ }
+
+ private void assertRandomInternalPropertyIsIntact() {
+ List<Tuple> resultsInternal = db.select(format("select kee, text_value from internal_properties where kee in ('%s')", RANDOM_INTERNAL_PROPERTY_KEY))
+ .stream().map(map -> new Tuple(map.get("kee"), map.get("text_value")))
+ .toList();
+ assertThat(resultsInternal).containsExactly(new Tuple(RANDOM_INTERNAL_PROPERTY_KEY, RANDOM_INTERNAL_PROPERTY_VALUE));
+ }
+
+ private void insertProperties(Map<String, String> properties) {
+ properties.forEach(this::insertProperty);
+ }
+
+ private void insertProperty(String key, String value) {
+ db.executeInsert("properties", "uuid", "uuid_" + key.substring(0, min(key.length() - 1, 35)), "prop_key", key, "is_empty", false, "text_value", value, "created_at", 0);
+ }
+
+ private void insertInternalProperty(String key, String value) {
+ db.executeInsert("internal_properties", "kee", key, "is_empty", false, "text_value", value, "created_at", 0);
+ }
+
+} \ No newline at end of file
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/DbVersion107.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/DbVersion107.java
index 2e42aed367a..2b835ed9ade 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/DbVersion107.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/DbVersion107.java
@@ -41,7 +41,8 @@ public class DbVersion107 implements DbVersion {
public void addSteps(MigrationStepRegistry registry) {
registry
.add(10_7_000, "Create 'telemetry_metrics_sent' table", CreateTelemetryMetricsSentTable.class)
- .add(10_7_001, "sonar.auth.gitlab.userConsentForPermissionProvisioningRequired", AddUserConsentRequiredIfGitlabAutoProvisioningEnabled.class);
+ .add(10_7_001, "sonar.auth.gitlab.userConsentForPermissionProvisioningRequired", AddUserConsentRequiredIfGitlabAutoProvisioningEnabled.class)
+ .add(10_7_002, "Migrate SMTP configuration into internal_properties", MigrateSmtpConfiguration.class);
}
}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/MigrateSmtpConfiguration.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/MigrateSmtpConfiguration.java
new file mode 100644
index 00000000000..7eb56e75d9d
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v107/MigrateSmtpConfiguration.java
@@ -0,0 +1,165 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-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 org.sonar.server.platform.db.migration.version.v107;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.Select;
+import org.sonar.server.platform.db.migration.step.Upsert;
+
+import static java.util.stream.Collectors.joining;
+
+public class MigrateSmtpConfiguration extends DataChange {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MigrateSmtpConfiguration.class.getName());
+
+ @VisibleForTesting
+ static final String EMAIL_SMTP_HOST_SECURED = "email.smtp_host.secured";
+ static final String EMAIL_SMTP_PORT_SECURED = "email.smtp_port.secured";
+ static final String EMAIL_SMTP_SECURE_CONNECTION_SECURED = "email.smtp_secure_connection.secured";
+ static final String EMAIL_SMTP_USERNAME_SECURED = "email.smtp_username.secured";
+ static final String EMAIL_SMTP_PASSWORD_SECURED = "email.smtp_password.secured";
+ static final String EMAIL_FROM = "email.from";
+ static final String EMAIL_FROM_NAME = "email.fromName";
+ static final String EMAIL_PREFIX = "email.prefix";
+ static final String EMAIL_AUTH_METHOD = "email.smtp.auth.method";
+
+ @VisibleForTesting
+ static final List<String> SMTP_LEGACY_CONFIG_PROP_KEYS = List.of(
+ EMAIL_SMTP_HOST_SECURED,
+ EMAIL_SMTP_PORT_SECURED,
+ EMAIL_SMTP_SECURE_CONNECTION_SECURED,
+ EMAIL_SMTP_USERNAME_SECURED,
+ EMAIL_SMTP_PASSWORD_SECURED,
+ EMAIL_FROM,
+ EMAIL_FROM_NAME,
+ EMAIL_PREFIX,
+ EMAIL_AUTH_METHOD
+ );
+
+ private static final Map<String, String> defaultPropertyValues = Map.of(
+ EMAIL_SMTP_SECURE_CONNECTION_SECURED, "NONE",
+ EMAIL_FROM, "noreply@nowhere",
+ EMAIL_FROM_NAME, "SonarQube",
+ EMAIL_PREFIX, "[SONARQUBE]",
+ EMAIL_AUTH_METHOD, "BASIC"
+ );
+
+ private static final String PLACEHOLDER = "LIST_PLACEHOLDER";
+ private static final String SELECT_PROPERTIES_QUERY = """
+ select prop_key, is_empty, text_value, created_at from properties
+ where prop_key in (LIST_PLACEHOLDER)
+ """;
+ private static final String DELETE_PROPERTIES_QUERY = """
+ delete from properties
+ where prop_key in (LIST_PLACEHOLDER)
+ """;
+
+ private static final String INSERT_INTERNAL_PROPERTIES_QUERY = """
+ insert into internal_properties (kee, is_empty, text_value, created_at)
+ values (?, ?, ?, ?)
+ """;
+
+ private final System2 system2;
+
+ public MigrateSmtpConfiguration(Database db, System2 system2) {
+ super(db);
+ this.system2 = system2;
+ }
+
+ @Override
+ protected void execute(Context context) throws SQLException {
+ Map<String, PropertyDb> keyToProperties = new HashMap<>();
+ String selectQuery = getQueryWithResolvedPlaceholder(SELECT_PROPERTIES_QUERY);
+ context.prepareSelect(selectQuery).scroll(row -> keyToProperties.put(row.getString(1), getPropertyFromRow(row)));
+ if (!keyToProperties.isEmpty()) {
+ insertPropertiesIntoInternal(context, keyToProperties);
+ deleteOriginalProperties(context);
+ LOGGER.info("SMTP configuration properties successfully migrated into internal_properties");
+ }
+ }
+
+ private static PropertyDb getPropertyFromRow(Select.Row row) throws SQLException {
+ return new PropertyDb(
+ row.getString(1),
+ row.getBoolean(2),
+ row.getString(3),
+ row.getLong(4)
+ );
+ }
+
+ private void insertPropertiesIntoInternal(Context context, Map<String, PropertyDb> properties) throws SQLException {
+ addDefaultPropertiesIfNeeded(properties);
+ properties.put(EMAIL_SMTP_SECURE_CONNECTION_SECURED, getSecureConnectionWithNewValues(properties.get(EMAIL_SMTP_SECURE_CONNECTION_SECURED)));
+ Upsert insertInternalProperties = context.prepareUpsert(INSERT_INTERNAL_PROPERTIES_QUERY);
+ for (PropertyDb property : properties.values()) {
+ insertInternalProperties
+ .setString(1, property.key)
+ .setBoolean(2, property.isEmpty)
+ .setString(3, property.value)
+ .setLong(4, property.createdAt)
+ .addBatch();
+ LOGGER.debug("Migrated property: {}", property.key);
+ }
+ insertInternalProperties.execute().commit();
+ }
+
+ private void addDefaultPropertiesIfNeeded(Map<String, PropertyDb> keyToProperties) {
+ defaultPropertyValues.forEach((key, value) -> {
+ if (!keyToProperties.containsKey(key)) {
+ keyToProperties.put(key, new PropertyDb(key, false, value, system2.now()));
+ }
+ });
+ }
+
+ private static PropertyDb getSecureConnectionWithNewValues(PropertyDb currentProperty) {
+ String newValue = switch (currentProperty.value) {
+ case "ssl" -> "SSLTLS";
+ case "starttls" -> "STARTTLS";
+ default -> "NONE";
+ };
+ return new PropertyDb(currentProperty.key, currentProperty.isEmpty, newValue, currentProperty.createdAt);
+ }
+
+ private static void deleteOriginalProperties(Context context) throws SQLException {
+ context.prepareUpsert(getQueryWithResolvedPlaceholder(DELETE_PROPERTIES_QUERY))
+ .execute()
+ .commit();
+ }
+
+ private static String getQueryWithResolvedPlaceholder(String query) {
+ return query.replace(PLACEHOLDER, SMTP_LEGACY_CONFIG_PROP_KEYS.stream().map(key -> "'" + key + "'").collect(joining(",")));
+ }
+
+ private record PropertyDb(
+ String key,
+ boolean isEmpty,
+ String value,
+ long createdAt
+ ) {}
+}