diff options
author | Pawel Kupinski <pawel.kupinski@sonarsource.com> | 2025-02-20 09:57:54 +0100 |
---|---|---|
committer | Lukasz Jarocki <lukasz.jarocki@sonarsource.com> | 2025-02-28 09:57:47 +0100 |
commit | 20d7a52648033668ee648b3e7f875e3ea831bde1 (patch) | |
tree | 8f7c588fdc4ea69ff0cbdb733223f7f06d584dae /server | |
parent | fa85e9d726a9b42464f3c4f3383b43fc964d9e46 (diff) | |
download | sonarqube-20d7a52648033668ee648b3e7f875e3ea831bde1.tar.gz sonarqube-20d7a52648033668ee648b3e7f875e3ea831bde1.zip |
SONAR-24350 Decrypt encrypted properties from SMTP configuration
Diffstat (limited to 'server')
3 files changed, 103 insertions, 2 deletions
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/email/EmailSmtpConfiguration.java b/server/sonar-server-common/src/main/java/org/sonar/server/email/EmailSmtpConfiguration.java index fdb82e52af1..b69faf6ea80 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/email/EmailSmtpConfiguration.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/email/EmailSmtpConfiguration.java @@ -20,10 +20,14 @@ package org.sonar.server.email; import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.config.internal.Encryption; +import org.sonar.api.config.internal.Settings; import org.sonar.api.server.ServerSide; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import static org.sonar.api.CoreProperties.ENCRYPTION_SECRET_KEY_PATH; + @ServerSide @ComputeEngineSide public class EmailSmtpConfiguration { @@ -62,9 +66,11 @@ public class EmailSmtpConfiguration { public static final String EMAIL_CONFIG_SMTP_OAUTH_GRANT_DEFAULT = "client_credentials"; private final DbClient dbClient; + private final Encryption encryption; - public EmailSmtpConfiguration(DbClient dbClient) { + public EmailSmtpConfiguration(DbClient dbClient, Settings settings) { this.dbClient = dbClient; + this.encryption = new Encryption(settings.getRawString(ENCRYPTION_SECRET_KEY_PATH).orElse(null)); } public String getSmtpHost() { @@ -129,8 +135,20 @@ public class EmailSmtpConfiguration { private String get(String key, String defaultValue) { try (DbSession dbSession = dbClient.openSession(false)) { - return dbClient.internalPropertiesDao().selectByKey(dbSession, key).orElse(defaultValue); + return dbClient.internalPropertiesDao().selectByKey(dbSession, key) + .map(value -> decryptIfNeeded(key, value)) + .orElse(defaultValue); } } + private String decryptIfNeeded(String key, String value) { + if (!encryption.isEncrypted(value)) { + return value; + } + try { + return encryption.decrypt(value); + } catch (Exception e) { + throw new IllegalStateException("Fail to decrypt the property %s. Please check your secret key.".formatted(key), e); + } + } } diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/email/EmailSmtpConfigurationTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/email/EmailSmtpConfigurationTest.java new file mode 100644 index 00000000000..94a47b844b7 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/email/EmailSmtpConfigurationTest.java @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 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.email; + + +import java.io.File; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.sonar.api.config.internal.Encryption; +import org.sonar.api.config.internal.Settings; +import org.sonar.db.DbClient; +import org.sonar.db.property.InternalPropertiesDao; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.api.CoreProperties.ENCRYPTION_SECRET_KEY_PATH; +import static org.sonar.server.email.EmailSmtpConfiguration.EMAIL_CONFIG_FROM; +import static org.sonar.server.email.EmailSmtpConfiguration.EMAIL_CONFIG_SMTP_PASSWORD; + +class EmailSmtpConfigurationTest { + + DbClient dbClientMock = mock(DbClient.class); + InternalPropertiesDao internalPropertiesDaoMock = mock(InternalPropertiesDao.class); + Settings settings = mock(Settings.class); + EmailSmtpConfiguration smtpConfiguration; + + @BeforeEach + void beforeEach() { + when(dbClientMock.internalPropertiesDao()).thenReturn(internalPropertiesDaoMock); + when(settings.getRawString(ENCRYPTION_SECRET_KEY_PATH)).thenReturn(Optional.of(pathToSecretKey())); + smtpConfiguration = new EmailSmtpConfiguration(dbClientMock, settings); + } + + @Test + void getValue_whenNoEncryption_shouldReturnOriginalValue() { + when(internalPropertiesDaoMock.selectByKey(any(), eq(EMAIL_CONFIG_FROM))).thenReturn(Optional.of("email-from-value")); + var value = smtpConfiguration.getFrom(); + + assertThat(value).isEqualTo("email-from-value"); + } + + @Test + void getValue_whenStoredInEncryptedFormat_shouldReturnDecryptedValue() { + var encryptedValue = new Encryption(pathToSecretKey()).encrypt("password123"); + + when(internalPropertiesDaoMock.selectByKey(any(), eq(EMAIL_CONFIG_SMTP_PASSWORD))).thenReturn(Optional.of(encryptedValue)); + var value = smtpConfiguration.getSmtpPassword(); + + assertThat(encryptedValue).startsWith("{aes-gcm}"); + assertThat(value).isEqualTo("password123"); + } + + String pathToSecretKey() { + try { + var resource = getClass().getResource("/org/sonar/encryption/aes_secret_key.txt"); + return new File(resource.toURI()).getCanonicalPath(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/server/sonar-server-common/src/test/resources/org/sonar/encryption/aes_secret_key.txt b/server/sonar-server-common/src/test/resources/org/sonar/encryption/aes_secret_key.txt new file mode 100644 index 00000000000..65b98c522da --- /dev/null +++ b/server/sonar-server-common/src/test/resources/org/sonar/encryption/aes_secret_key.txt @@ -0,0 +1 @@ +0PZz+G+f8mjr3sPn4+AhHg==
\ No newline at end of file |