From 808cba5b51d54a61ef2a01e6e78e3874ef53eea5 Mon Sep 17 00:00:00 2001 From: Antoine Vigneau Date: Tue, 30 Jul 2024 14:32:11 +0200 Subject: SONAR-22516 Retrieve SMTP configuration from internal_properties --- .../ce/container/ComputeEngineContainerImpl.java | 2 + .../sonar/server/email/EmailSmtpConfiguration.java | 128 +++++++++++++++++++++ .../email/EmailNotificationChannel.java | 15 ++- .../email/EmailNotificationChannelTest.java | 30 ++--- .../server/notification/NotificationModule.java | 2 + .../notification/NotificationModuleTest.java | 2 +- .../java/org/sonar/api/config/EmailSettings.java | 86 ++++++++++++++ 7 files changed, 245 insertions(+), 20 deletions(-) create mode 100644 server/sonar-server-common/src/main/java/org/sonar/server/email/EmailSmtpConfiguration.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/config/EmailSettings.java diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java index ff49a134101..4d9d5b47a8a 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java @@ -88,6 +88,7 @@ import org.sonar.process.Props; import org.sonar.process.logging.LogbackHelper; import org.sonar.server.component.index.EntityDefinitionIndexer; import org.sonar.server.config.ConfigurationProvider; +import org.sonar.server.email.EmailSmtpConfiguration; import org.sonar.server.es.EsModule; import org.sonar.server.es.IndexersImpl; import org.sonar.server.extension.CoreExtensionBootstraper; @@ -407,6 +408,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer { // Notifications QGChangeEmailTemplate.class, EmailSettings.class, + EmailSmtpConfiguration.class, NotificationService.class, DefaultNotificationManager.class, EmailNotificationChannel.class, 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 new file mode 100644 index 00000000000..b7873fab943 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/email/EmailSmtpConfiguration.java @@ -0,0 +1,128 @@ +/* + * 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.email; + +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.server.ServerSide; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; + +@ServerSide +@ComputeEngineSide +public class EmailSmtpConfiguration { + // Common configuration + public static final String EMAIL_CONFIG_SMTP_HOST = "email.smtp_host.secured"; + public static final String EMAIL_CONFIG_SMTP_HOST_DEFAULT = ""; + public static final String EMAIL_CONFIG_SMTP_PORT = "email.smtp_port.secured"; + public static final String EMAIL_CONFIG_SMTP_PORT_DEFAULT = "25"; + public static final String EMAIL_CONFIG_SMTP_SECURE_CONNECTION = "email.smtp_secure_connection.secured"; + public static final String EMAIL_CONFIG_SMTP_SECURE_CONNECTION_DEFAULT = ""; + public static final String EMAIL_CONFIG_SMTP_AUTH_METHOD= "email.smtp.auth.method"; + public static final String EMAIL_CONFIG_SMTP_AUTH_METHOD_DEFAULT = "BASIC"; + // Email content + public static final String EMAIL_CONFIG_FROM = "email.from"; + public static final String EMAIL_CONFIG_FROM_DEFAULT = "noreply@nowhere"; + public static final String EMAIL_CONFIG_FROM_NAME = "email.fromName"; + public static final String EMAIL_CONFIG_FROM_NAME_DEFAULT = "SonarQube"; + public static final String EMAIL_CONFIG_PREFIX = "email.prefix"; + public static final String EMAIL_CONFIG_PREFIX_DEFAULT = "[SONARQUBE]"; + // Basic Auth + public static final String EMAIL_CONFIG_SMTP_USERNAME = "email.smtp_username.secured"; + public static final String EMAIL_CONFIG_SMTP_USERNAME_DEFAULT = ""; + public static final String EMAIL_CONFIG_SMTP_PASSWORD = "email.smtp_password.secured"; + public static final String EMAIL_CONFIG_SMTP_PASSWORD_DEFAULT = ""; + // Modern auth + public static final String EMAIL_CONFIG_SMTP_OAUTH_HOST = "email.smtp.oauth.host"; + public static final String EMAIL_CONFIG_SMTP_OAUTH_HOST_DEFAULT = "https://login.microsoftonline.com"; + public static final String EMAIL_CONFIG_SMTP_OAUTH_TENANT = "email.smtp.oauth.tenant"; + public static final String EMAIL_CONFIG_SMTP_OAUTH_CLIENTID = "email.smtp.oauth.clientId"; + public static final String EMAIL_CONFIG_SMTP_OAUTH_CLIENTSECRET = "email.smtp.oauth.clientSecret"; + public static final String EMAIL_CONFIG_SMTP_OAUTH_SCOPE = "email.smtp.oauth.scope"; + public static final String EMAIL_CONFIG_SMTP_OAUTH_SCOPE_DEFAULT = "client_credentials"; + + private final DbClient dbClient; + + public EmailSmtpConfiguration(DbClient dbClient) { + this.dbClient = dbClient; + } + + public String getSmtpHost() { + return get(EMAIL_CONFIG_SMTP_HOST, EMAIL_CONFIG_SMTP_HOST_DEFAULT); + } + + public int getSmtpPort() { + return Integer.parseInt(get(EMAIL_CONFIG_SMTP_PORT, EMAIL_CONFIG_SMTP_PORT_DEFAULT)); + } + + public String getSecureConnection() { + return get(EMAIL_CONFIG_SMTP_SECURE_CONNECTION, EMAIL_CONFIG_SMTP_SECURE_CONNECTION_DEFAULT); + } + + public String getAuthMethod() { + return get(EMAIL_CONFIG_SMTP_AUTH_METHOD, EMAIL_CONFIG_SMTP_AUTH_METHOD_DEFAULT); + } + + public String getFrom() { + return get(EMAIL_CONFIG_FROM, EMAIL_CONFIG_FROM_DEFAULT); + } + + public String getFromName() { + return get(EMAIL_CONFIG_FROM_NAME, EMAIL_CONFIG_FROM_NAME_DEFAULT); + } + + public String getPrefix() { + return get(EMAIL_CONFIG_PREFIX, EMAIL_CONFIG_PREFIX_DEFAULT); + } + + public String getSmtpUsername() { + return get(EMAIL_CONFIG_SMTP_USERNAME, EMAIL_CONFIG_SMTP_USERNAME_DEFAULT); + } + + public String getSmtpPassword() { + return get(EMAIL_CONFIG_SMTP_PASSWORD, EMAIL_CONFIG_SMTP_PASSWORD_DEFAULT); + } + + public String getOAuthHost() { + return get(EMAIL_CONFIG_SMTP_OAUTH_HOST, EMAIL_CONFIG_SMTP_OAUTH_HOST_DEFAULT); + } + + public String getOAuthTenant() { + return get(EMAIL_CONFIG_SMTP_OAUTH_TENANT, ""); + } + + public String getOAuthClientId() { + return get(EMAIL_CONFIG_SMTP_OAUTH_CLIENTID, ""); + } + + public String getOAuthClientSecret() { + return get(EMAIL_CONFIG_SMTP_OAUTH_CLIENTSECRET, ""); + } + + public String getOAuthScope() { + return get(EMAIL_CONFIG_SMTP_OAUTH_SCOPE, EMAIL_CONFIG_SMTP_OAUTH_SCOPE_DEFAULT); + } + + private String get(String key, String defaultValue) { + try (DbSession dbSession = dbClient.openSession(false)) { + return dbClient.internalPropertiesDao().selectByKey(dbSession, key).orElse(defaultValue); + } + } + +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/notification/email/EmailNotificationChannel.java b/server/sonar-server-common/src/main/java/org/sonar/server/notification/email/EmailNotificationChannel.java index f91a2fef12a..4c9fcc00109 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/notification/email/EmailNotificationChannel.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/notification/email/EmailNotificationChannel.java @@ -33,13 +33,14 @@ import org.apache.commons.mail.HtmlEmail; import org.apache.commons.mail.SimpleEmail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.sonar.api.config.EmailSettings; import org.sonar.api.notifications.Notification; +import org.sonar.api.platform.Server; import org.sonar.api.user.User; import org.sonar.api.utils.SonarException; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.user.UserDto; +import org.sonar.server.email.EmailSmtpConfiguration; import org.sonar.server.issue.notification.EmailMessage; import org.sonar.server.issue.notification.EmailTemplate; import org.sonar.server.notification.NotificationChannel; @@ -97,12 +98,14 @@ public class EmailNotificationChannel extends NotificationChannel { private static final String SMTP_HOST_NOT_CONFIGURED_DEBUG_MSG = "SMTP host was not configured - email will not be sent"; private static final String MAIL_SENT_FROM = "%sMail sent from: %s"; - private final EmailSettings configuration; + private final EmailSmtpConfiguration configuration; + private final Server server; private final EmailTemplate[] templates; private final DbClient dbClient; - public EmailNotificationChannel(EmailSettings configuration, EmailTemplate[] templates, DbClient dbClient) { + public EmailNotificationChannel(EmailSmtpConfiguration configuration, Server server, EmailTemplate[] templates, DbClient dbClient) { this.configuration = configuration; + this.server = server; this.templates = templates; this.dbClient = dbClient; } @@ -260,7 +263,7 @@ public class EmailNotificationChannel extends NotificationChannel { @CheckForNull private String resolveHost() { try { - return new URL(configuration.getServerBaseURL()).getHost(); + return new URL(server.getPublicRootUrl()).getHost(); } catch (MalformedURLException e) { // ignore return null; @@ -282,7 +285,7 @@ public class EmailNotificationChannel extends NotificationChannel { } // Set headers for proper filtering email.addHeader(LIST_ID_HEADER, "SonarQube "); - email.addHeader(LIST_ARCHIVE_HEADER, configuration.getServerBaseURL()); + email.addHeader(LIST_ARCHIVE_HEADER, server.getPublicRootUrl()); } } @@ -336,7 +339,7 @@ public class EmailNotificationChannel extends NotificationChannel { } private String getServerBaseUrlFooter() { - return String.format(MAIL_SENT_FROM, "\n\n", configuration.getServerBaseURL()); + return String.format(MAIL_SENT_FROM, "\n\n", server.getPublicRootUrl()); } } diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/notification/email/EmailNotificationChannelTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/notification/email/EmailNotificationChannelTest.java index 567b8296398..e4aad4cf909 100644 --- a/server/sonar-server-common/src/test/java/org/sonar/server/notification/email/EmailNotificationChannelTest.java +++ b/server/sonar-server-common/src/test/java/org/sonar/server/notification/email/EmailNotificationChannelTest.java @@ -39,10 +39,11 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.event.Level; -import org.sonar.api.config.EmailSettings; import org.sonar.api.notifications.Notification; +import org.sonar.api.platform.Server; import org.sonar.api.testfixtures.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.server.email.EmailSmtpConfiguration; import org.sonar.server.issue.notification.EmailMessage; import org.sonar.server.issue.notification.EmailTemplate; import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest; @@ -69,7 +70,8 @@ public class EmailNotificationChannelTest { public LogTester logTester = new LogTester(); private Wiser smtpServer; - private EmailSettings configuration; + private EmailSmtpConfiguration configuration; + private Server server; private EmailNotificationChannel underTest; @Before @@ -78,8 +80,10 @@ public class EmailNotificationChannelTest { smtpServer = new Wiser(0); smtpServer.start(); - configuration = mock(EmailSettings.class); - underTest = new EmailNotificationChannel(configuration, null, null); + configuration = mock(EmailSmtpConfiguration.class); + server = mock(Server.class); + + underTest = new EmailNotificationChannel(configuration, server, null, null); } @After @@ -251,8 +255,8 @@ public class EmailNotificationChannelTest { @Test public void deliverAll_has_no_effect_if_set_is_empty() { - EmailSettings emailSettings = mock(EmailSettings.class); - EmailNotificationChannel underTest = new EmailNotificationChannel(emailSettings, null, null); + EmailSmtpConfiguration emailSettings = mock(EmailSmtpConfiguration.class); + EmailNotificationChannel underTest = new EmailNotificationChannel(emailSettings, server, null, null); int count = underTest.deliverAll(Collections.emptySet()); @@ -263,12 +267,12 @@ public class EmailNotificationChannelTest { @Test public void deliverAll_has_no_effect_if_smtp_host_is_null() { - EmailSettings emailSettings = mock(EmailSettings.class); + EmailSmtpConfiguration emailSettings = mock(EmailSmtpConfiguration.class); when(emailSettings.getSmtpHost()).thenReturn(null); Set requests = IntStream.range(0, 1 + new Random().nextInt(10)) .mapToObj(i -> new EmailDeliveryRequest("foo" + i + "@moo", mock(Notification.class))) .collect(toSet()); - EmailNotificationChannel underTest = new EmailNotificationChannel(emailSettings, null, null); + EmailNotificationChannel underTest = new EmailNotificationChannel(emailSettings, server, null, null); int count = underTest.deliverAll(requests); @@ -281,12 +285,12 @@ public class EmailNotificationChannelTest { @Test @UseDataProvider("emptyStrings") public void deliverAll_ignores_requests_which_recipient_is_empty(String emptyString) { - EmailSettings emailSettings = mock(EmailSettings.class); + EmailSmtpConfiguration emailSettings = mock(EmailSmtpConfiguration.class); when(emailSettings.getSmtpHost()).thenReturn(null); Set requests = IntStream.range(0, 1 + new Random().nextInt(10)) .mapToObj(i -> new EmailDeliveryRequest(emptyString, mock(Notification.class))) .collect(toSet()); - EmailNotificationChannel underTest = new EmailNotificationChannel(emailSettings, null, null); + EmailNotificationChannel underTest = new EmailNotificationChannel(emailSettings, server, null, null); int count = underTest.deliverAll(requests); @@ -312,7 +316,7 @@ public class EmailNotificationChannelTest { Set requests = Stream.of(notification1, notification2, notification3) .map(t -> new EmailDeliveryRequest(recipientEmail, t)) .collect(toSet()); - EmailNotificationChannel underTest = new EmailNotificationChannel(configuration, new EmailTemplate[] {template1, template3}, null); + EmailNotificationChannel underTest = new EmailNotificationChannel(configuration, server, new EmailTemplate[] {template1, template3}, null); int count = underTest.deliverAll(requests); @@ -352,7 +356,7 @@ public class EmailNotificationChannelTest { when(template11.format(notification1)).thenReturn(emailMessage11); when(template12.format(notification1)).thenReturn(emailMessage12); EmailDeliveryRequest request = new EmailDeliveryRequest(recipientEmail, notification1); - EmailNotificationChannel underTest = new EmailNotificationChannel(configuration, new EmailTemplate[] {template11, template12}, null); + EmailNotificationChannel underTest = new EmailNotificationChannel(configuration, server, new EmailTemplate[] {template11, template12}, null); int count = underTest.deliverAll(Collections.singleton(request)); @@ -377,7 +381,7 @@ public class EmailNotificationChannelTest { when(configuration.getFrom()).thenReturn("server@nowhere"); when(configuration.getFromName()).thenReturn("SonarQube from NoWhere"); when(configuration.getPrefix()).thenReturn(SUBJECT_PREFIX); - when(configuration.getServerBaseURL()).thenReturn("http://nemo.sonarsource.org"); + when(server.getPublicRootUrl()).thenReturn("http://nemo.sonarsource.org"); } } diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/notification/NotificationModule.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/notification/NotificationModule.java index f18ed2d3915..9fcd6a92918 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/notification/NotificationModule.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/notification/NotificationModule.java @@ -21,6 +21,7 @@ package org.sonar.server.notification; import org.sonar.api.config.EmailSettings; import org.sonar.core.platform.Module; +import org.sonar.server.email.EmailSmtpConfiguration; import org.sonar.server.notification.email.EmailNotificationChannel; public class NotificationModule extends Module { @@ -28,6 +29,7 @@ public class NotificationModule extends Module { protected void configureModule() { add( EmailSettings.class, + EmailSmtpConfiguration.class, NotificationService.class, DefaultNotificationManager.class, NotificationDaemon.class, diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/notification/NotificationModuleTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/notification/NotificationModuleTest.java index fa040dc7bd5..94fadfc4d61 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/notification/NotificationModuleTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/notification/NotificationModuleTest.java @@ -29,6 +29,6 @@ public class NotificationModuleTest { public void verify_count_of_added_components() { ListContainer container = new ListContainer(); new NotificationModule().configure(container); - assertThat(container.getAddedObjects()).hasSize(5); + assertThat(container.getAddedObjects()).hasSize(6); } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/EmailSettings.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/EmailSettings.java new file mode 100644 index 00000000000..63b5f43cf19 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/config/EmailSettings.java @@ -0,0 +1,86 @@ +/* + * Sonar, open source software quality management tool. + * Copyright (C) 2008-2011 SonarSource + * mailto:contact AT sonarsource DOT com + * + * Sonar 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. + * + * Sonar 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 Sonar; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 + */ +package org.sonar.plugins.email; + +import org.apache.commons.configuration.Configuration; +import org.sonar.api.CoreProperties; +import org.sonar.api.ServerExtension; + +/** + * Ruby uses constants from this class. + * + * @since 2.10 + */ +public class EmailConfiguration implements ServerExtension { + + public static final String SMTP_HOST = "email.smtp_host"; + public static final String SMTP_HOST_DEFAULT = ""; + public static final String SMTP_PORT = "email.smtp_port"; + public static final String SMTP_PORT_DEFAULT = "25"; + public static final String SMTP_USE_TLS = "email.smtp_use_tls"; + public static final boolean SMTP_USE_TLS_DEFAULT = false; + public static final String SMTP_USERNAME = "email.smtp_username"; + public static final String SMTP_USERNAME_DEFAULT = ""; + public static final String SMTP_PASSWORD = "email.smtp_password"; + public static final String SMTP_PASSWORD_DEFAULT = ""; + public static final String FROM = "email.from"; + public static final String FROM_DEFAULT = "noreply@nowhere"; + public static final String PREFIX = "email.prefix"; + public static final String PREFIX_DEFAULT = "[SONAR]"; + + private Configuration configuration; + + public EmailConfiguration(Configuration configuration) { + this.configuration = configuration; + } + + public String getSmtpHost() { + return configuration.getString(SMTP_HOST, SMTP_HOST_DEFAULT); + } + + public String getSmtpPort() { + return configuration.getString(SMTP_PORT, SMTP_PORT_DEFAULT); + } + + public boolean isUseTLS() { + return configuration.getBoolean(SMTP_USE_TLS, SMTP_USE_TLS_DEFAULT); + } + + public String getSmtpUsername() { + return configuration.getString(SMTP_USERNAME, SMTP_USERNAME_DEFAULT); + } + + public String getSmtpPassword() { + return configuration.getString(SMTP_PASSWORD, SMTP_PASSWORD_DEFAULT); + } + + public String getFrom() { + return configuration.getString(FROM, FROM_DEFAULT); + } + + public String getPrefix() { + return configuration.getString(PREFIX, PREFIX_DEFAULT); + } + + public String getServerBaseURL() { + return configuration.getString(CoreProperties.SERVER_BASE_URL, CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE); + } + +} -- cgit v1.2.3