]> source.dussan.org Git - sonarqube.git/commitdiff
NO-JIRA improve log to not be sensitive to line return from user input
authorPierre <pierre.guillot@sonarsource.com>
Thu, 15 Jun 2023 09:27:36 +0000 (11:27 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 15 Jun 2023 20:03:02 +0000 (20:03 +0000)
server/sonar-server-common/build.gradle
server/sonar-server-common/src/main/java/org/sonar/server/notification/email/EmailNotificationChannel.java
server/sonar-server-common/src/test/java/org/sonar/server/notification/email/EmailNotificationChannelTest.java [new file with mode: 0644]
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/event/AuthenticationEventImpl.java
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/event/AuthenticationEventImplTest.java
server/sonar-webserver-core/src/test/java/org/sonar/server/notification/email/EmailNotificationChannelTest.java [deleted file]

index 3b872c8cd3f0a7fdb06ba2424eb3d75ca5f6b39d..67dec37f8ccd4b8df8b37cad1bfe5eb4f3550d14 100644 (file)
@@ -29,6 +29,7 @@ dependencies {
   testImplementation 'org.elasticsearch.plugin:transport-netty4-client'
   testImplementation 'ch.qos.logback:logback-core'
   testImplementation 'com.google.code.findbugs:jsr305'
+  testImplementation 'org.subethamail:subethasmtp'
   testImplementation 'com.squareup.okhttp3:mockwebserver'
   testImplementation 'com.squareup.okio:okio'
   testImplementation 'com.tngtech.java:junit-dataprovider'
index 4181eada966ad0a3f11b5de5e14dfe789260e70c..3c0cf7981b92f4eed5027d4ee5bb2708c17413e2 100644 (file)
@@ -23,19 +23,20 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Objects;
 import java.util.Set;
+import java.util.regex.Pattern;
 import javax.annotation.CheckForNull;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.mail.Email;
 import org.apache.commons.mail.EmailException;
 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.notifications.NotificationChannel;
 import org.sonar.api.user.User;
 import org.sonar.api.utils.SonarException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.user.UserDto;
@@ -64,6 +65,8 @@ public class EmailNotificationChannel extends NotificationChannel {
    */
   private static final int SOCKET_TIMEOUT = 30_000;
 
+  private static final Pattern PATTERN_LINE_BREAK = Pattern.compile("[\n\r]");
+
   /**
    * Email Header Field: "List-ID".
    * Value of this field should contain mailing list identifier as specified in <a href="http://tools.ietf.org/html/rfc2919">RFC 2919</a>.
@@ -211,7 +214,9 @@ public class EmailNotificationChannel extends NotificationChannel {
     Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
 
     try {
-      LOG.trace("Sending email: {}", emailMessage);
+      LOG.atTrace().setMessage("Sending email: {}")
+        .addArgument(() -> sanitizeLog(emailMessage.getMessage()))
+        .log();
       String host = resolveHost();
 
       Email email = createEmailWithMessage(emailMessage);
@@ -226,6 +231,10 @@ public class EmailNotificationChannel extends NotificationChannel {
     }
   }
 
+  private static String sanitizeLog(String message) {
+    return PATTERN_LINE_BREAK.matcher(message).replaceAll("_");
+  }
+
   private static Email createEmailWithMessage(EmailMessage emailMessage) throws EmailException {
     if (emailMessage.isHtml()) {
       return new HtmlEmail().setHtmlMsg(emailMessage.getMessage());
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
new file mode 100644 (file)
index 0000000..05a23e9
--- /dev/null
@@ -0,0 +1,383 @@
+/*
+ * 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.notification.email;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import org.apache.commons.mail.EmailException;
+import org.junit.After;
+import org.junit.Before;
+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.testfixtures.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.server.issue.notification.EmailMessage;
+import org.sonar.server.issue.notification.EmailTemplate;
+import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
+import org.subethamail.wiser.Wiser;
+import org.subethamail.wiser.WiserMessage;
+
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
+import static junit.framework.Assert.fail;
+import static org.apache.commons.lang.RandomStringUtils.random;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(DataProviderRunner.class)
+public class EmailNotificationChannelTest {
+
+  private static final String SUBJECT_PREFIX = "[SONARQUBE]";
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private Wiser smtpServer;
+  private EmailSettings configuration;
+  private EmailNotificationChannel underTest;
+
+  @Before
+  public void setUp() {
+    logTester.setLevel(LoggerLevel.DEBUG);
+    smtpServer = new Wiser(0);
+    smtpServer.start();
+
+    configuration = mock(EmailSettings.class);
+    underTest = new EmailNotificationChannel(configuration, null, null);
+  }
+
+  @After
+  public void tearDown() {
+    smtpServer.stop();
+  }
+
+  @Test
+  public void isActivated_returns_true_if_smpt_host_is_not_empty() {
+    when(configuration.getSmtpHost()).thenReturn(random(5));
+
+    assertThat(underTest.isActivated()).isTrue();
+  }
+
+  @Test
+  public void isActivated_returns_false_if_smpt_host_is_null() {
+    when(configuration.getSmtpHost()).thenReturn(null);
+
+    assertThat(underTest.isActivated()).isFalse();
+  }
+
+  @Test
+  public void isActivated_returns_false_if_smpt_host_is_empty() {
+    when(configuration.getSmtpHost()).thenReturn("");
+
+    assertThat(underTest.isActivated()).isFalse();
+  }
+
+  @Test
+  public void shouldSendTestEmail() throws Exception {
+    configure();
+    underTest.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
+
+    List<WiserMessage> messages = smtpServer.getMessages();
+    assertThat(messages).hasSize(1);
+
+    MimeMessage email = messages.get(0).getMimeMessage();
+    assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
+    assertThat(email.getHeader("From", ",")).isEqualTo("SonarQube from NoWhere <server@nowhere>");
+    assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
+    assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Test Message from SonarQube");
+    assertThat((String) email.getContent()).startsWith("This is a test message from SonarQube.\r\n\r\nMail sent from: http://nemo.sonarsource.org");
+  }
+
+  @Test
+  public void sendTestEmailShouldSanitizeLog() throws Exception {
+    logTester.setLevel(LoggerLevel.TRACE);
+    configure();
+    underTest.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a message \n containing line breaks \r that should be sanitized when logged.");
+
+    assertThat(logTester.logs(Level.TRACE)).isNotEmpty()
+      .contains("Sending email: This is a message _ containing line breaks _ that should be sanitized when logged.__Mail sent from: http://nemo.sonarsource.org");
+
+  }
+
+  @Test
+  public void shouldThrowAnExceptionWhenUnableToSendTestEmail() {
+    configure();
+    smtpServer.stop();
+
+    try {
+      underTest.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
+      fail();
+    } catch (EmailException e) {
+      // expected
+    }
+  }
+
+  @Test
+  public void shouldNotSendEmailWhenHostnameNotConfigured() {
+    EmailMessage emailMessage = new EmailMessage()
+      .setTo("user@nowhere")
+      .setSubject("Foo")
+      .setPlainTextMessage("Bar");
+    boolean delivered = underTest.deliver(emailMessage);
+    assertThat(smtpServer.getMessages()).isEmpty();
+    assertThat(delivered).isFalse();
+  }
+
+  @Test
+  public void shouldSendThreadedEmail() throws Exception {
+    configure();
+    EmailMessage emailMessage = new EmailMessage()
+      .setMessageId("reviews/view/1")
+      .setFrom("Full Username")
+      .setTo("user@nowhere")
+      .setSubject("Review #3")
+      .setPlainTextMessage("I'll take care of this violation.");
+    boolean delivered = underTest.deliver(emailMessage);
+
+    List<WiserMessage> messages = smtpServer.getMessages();
+    assertThat(messages).hasSize(1);
+
+    MimeMessage email = messages.get(0).getMimeMessage();
+
+    assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
+
+    assertThat(email.getHeader("In-Reply-To", null)).isEqualTo("<reviews/view/1@nemo.sonarsource.org>");
+    assertThat(email.getHeader("References", null)).isEqualTo("<reviews/view/1@nemo.sonarsource.org>");
+
+    assertThat(email.getHeader("List-ID", null)).isEqualTo("SonarQube <sonar.nemo.sonarsource.org>");
+    assertThat(email.getHeader("List-Archive", null)).isEqualTo("http://nemo.sonarsource.org");
+
+    assertThat(email.getHeader("From", ",")).isEqualTo("\"Full Username (SonarQube from NoWhere)\" <server@nowhere>");
+    assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
+    assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Review #3");
+    assertThat((String) email.getContent()).startsWith("I'll take care of this violation.");
+    assertThat(delivered).isTrue();
+  }
+
+  @Test
+  public void shouldSendNonThreadedEmail() throws Exception {
+    configure();
+    EmailMessage emailMessage = new EmailMessage()
+      .setTo("user@nowhere")
+      .setSubject("Foo")
+      .setPlainTextMessage("Bar");
+    boolean delivered = underTest.deliver(emailMessage);
+
+    List<WiserMessage> messages = smtpServer.getMessages();
+    assertThat(messages).hasSize(1);
+
+    MimeMessage email = messages.get(0).getMimeMessage();
+
+    assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
+
+    assertThat(email.getHeader("In-Reply-To", null)).isNull();
+    assertThat(email.getHeader("References", null)).isNull();
+
+    assertThat(email.getHeader("List-ID", null)).isEqualTo("SonarQube <sonar.nemo.sonarsource.org>");
+    assertThat(email.getHeader("List-Archive", null)).isEqualTo("http://nemo.sonarsource.org");
+
+    assertThat(email.getHeader("From", null)).isEqualTo("SonarQube from NoWhere <server@nowhere>");
+    assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
+    assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Foo");
+    assertThat((String) email.getContent()).startsWith("Bar");
+    assertThat(delivered).isTrue();
+  }
+
+  @Test
+  public void shouldNotThrowAnExceptionWhenUnableToSendEmail() {
+    configure();
+    smtpServer.stop();
+
+    EmailMessage emailMessage = new EmailMessage()
+      .setTo("user@nowhere")
+      .setSubject("Foo")
+      .setPlainTextMessage("Bar");
+    boolean delivered = underTest.deliver(emailMessage);
+
+    assertThat(delivered).isFalse();
+  }
+
+  @Test
+  public void shouldSendTestEmailWithSTARTTLS() {
+    smtpServer.getServer().setEnableTLS(true);
+    smtpServer.getServer().setRequireTLS(true);
+    configure();
+    when(configuration.getSecureConnection()).thenReturn("STARTTLS");
+
+    try {
+      underTest.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
+      fail("An SSL exception was expected a a proof that STARTTLS is enabled");
+    } catch (EmailException e) {
+      // We don't have a SSL certificate so we are expecting a SSL error
+      assertThat(e.getCause().getMessage()).isEqualTo("Could not convert socket to TLS");
+    }
+  }
+
+  @Test
+  public void deliverAll_has_no_effect_if_set_is_empty() {
+    EmailSettings emailSettings = mock(EmailSettings.class);
+    EmailNotificationChannel underTest = new EmailNotificationChannel(emailSettings, null, null);
+
+    int count = underTest.deliverAll(Collections.emptySet());
+
+    assertThat(count).isZero();
+    verifyNoInteractions(emailSettings);
+    assertThat(smtpServer.getMessages()).isEmpty();
+  }
+
+  @Test
+  public void deliverAll_has_no_effect_if_smtp_host_is_null() {
+    EmailSettings emailSettings = mock(EmailSettings.class);
+    when(emailSettings.getSmtpHost()).thenReturn(null);
+    Set<EmailDeliveryRequest> 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);
+
+    int count = underTest.deliverAll(requests);
+
+    assertThat(count).isZero();
+    verify(emailSettings).getSmtpHost();
+    verifyNoMoreInteractions(emailSettings);
+    assertThat(smtpServer.getMessages()).isEmpty();
+  }
+
+  @Test
+  @UseDataProvider("emptyStrings")
+  public void deliverAll_ignores_requests_which_recipient_is_empty(String emptyString) {
+    EmailSettings emailSettings = mock(EmailSettings.class);
+    when(emailSettings.getSmtpHost()).thenReturn(null);
+    Set<EmailDeliveryRequest> 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);
+
+    int count = underTest.deliverAll(requests);
+
+    assertThat(count).isZero();
+    verify(emailSettings).getSmtpHost();
+    verifyNoMoreInteractions(emailSettings);
+    assertThat(smtpServer.getMessages()).isEmpty();
+  }
+
+  @Test
+  public void deliverAll_returns_count_of_request_for_which_at_least_one_formatter_accept_it() throws MessagingException, IOException {
+    String recipientEmail = "foo@donut";
+    configure();
+    Notification notification1 = mock(Notification.class);
+    Notification notification2 = mock(Notification.class);
+    Notification notification3 = mock(Notification.class);
+    EmailTemplate template1 = mock(EmailTemplate.class);
+    EmailTemplate template3 = mock(EmailTemplate.class);
+    EmailMessage emailMessage1 = new EmailMessage().setTo(recipientEmail).setSubject("sub11").setPlainTextMessage("msg11");
+    EmailMessage emailMessage3 = new EmailMessage().setTo(recipientEmail).setSubject("sub3").setPlainTextMessage("msg3");
+    when(template1.format(notification1)).thenReturn(emailMessage1);
+    when(template3.format(notification3)).thenReturn(emailMessage3);
+    Set<EmailDeliveryRequest> requests = Stream.of(notification1, notification2, notification3)
+      .map(t -> new EmailDeliveryRequest(recipientEmail, t))
+      .collect(toSet());
+    EmailNotificationChannel underTest = new EmailNotificationChannel(configuration, new EmailTemplate[] {template1, template3}, null);
+
+    int count = underTest.deliverAll(requests);
+
+    assertThat(count).isEqualTo(2);
+    assertThat(smtpServer.getMessages()).hasSize(2);
+    Map<String, MimeMessage> messagesBySubject = smtpServer.getMessages().stream()
+      .map(t -> {
+        try {
+          return t.getMimeMessage();
+        } catch (MessagingException e) {
+          throw new RuntimeException(e);
+        }
+      })
+      .collect(toMap(t -> {
+        try {
+          return t.getSubject();
+        } catch (MessagingException e) {
+          throw new RuntimeException(e);
+        }
+      }, t -> t));
+
+    assertThat((String) messagesBySubject.get(SUBJECT_PREFIX + " " + emailMessage1.getSubject()).getContent())
+      .contains(emailMessage1.getMessage());
+    assertThat((String) messagesBySubject.get(SUBJECT_PREFIX + " " + emailMessage3.getSubject()).getContent())
+      .contains(emailMessage3.getMessage());
+  }
+
+  @Test
+  public void deliverAll_ignores_multiple_templates_by_notification_and_takes_the_first_one_only() throws MessagingException, IOException {
+    String recipientEmail = "foo@donut";
+    configure();
+    Notification notification1 = mock(Notification.class);
+    EmailTemplate template11 = mock(EmailTemplate.class);
+    EmailTemplate template12 = mock(EmailTemplate.class);
+    EmailMessage emailMessage11 = new EmailMessage().setTo(recipientEmail).setSubject("sub11").setPlainTextMessage("msg11");
+    EmailMessage emailMessage12 = new EmailMessage().setTo(recipientEmail).setSubject("sub12").setPlainTextMessage("msg12");
+    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);
+
+    int count = underTest.deliverAll(Collections.singleton(request));
+
+    assertThat(count).isOne();
+    assertThat(smtpServer.getMessages()).hasSize(1);
+    assertThat((String) smtpServer.getMessages().iterator().next().getMimeMessage().getContent())
+      .contains(emailMessage11.getMessage());
+  }
+
+  @DataProvider
+  public static Object[][] emptyStrings() {
+    return new Object[][] {
+      {""},
+      {"  "},
+      {" \n "}
+    };
+  }
+
+  private void configure() {
+    when(configuration.getSmtpHost()).thenReturn("localhost");
+    when(configuration.getSmtpPort()).thenReturn(smtpServer.getServer().getPort());
+    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");
+  }
+
+}
index da58355b17988d896ebf3cd00dfcc01c8b8156f8..c40a164be2aebaf8aaf3f580e33c820a24cd7877 100644 (file)
@@ -21,10 +21,11 @@ package org.sonar.server.authentication.event;
 
 import com.google.common.base.Joiner;
 import java.util.Collections;
+import java.util.regex.Pattern;
 import javax.annotation.Nullable;
-import org.sonar.api.server.http.HttpRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.sonar.api.server.http.HttpRequest;
 import org.sonar.core.util.stream.MoreCollectors;
 
 import static java.util.Objects.requireNonNull;
@@ -32,18 +33,20 @@ import static java.util.Objects.requireNonNull;
 public class AuthenticationEventImpl implements AuthenticationEvent {
   private static final Logger LOGGER = LoggerFactory.getLogger("auth.event");
   private static final int FLOOD_THRESHOLD = 128;
+  private static final Pattern PATTERN_LINE_BREAK = Pattern.compile("[\n\r]");
 
   @Override
   public void loginSuccess(HttpRequest request, @Nullable String login, Source source) {
     checkRequest(request);
     requireNonNull(source, "source can't be null");
-    if (!LOGGER.isDebugEnabled()) {
-      return;
-    }
-    LOGGER.debug("login success [method|{}][provider|{}|{}][IP|{}|{}][login|{}]",
-      source.getMethod(), source.getProvider(), source.getProviderName(),
-      request.getRemoteAddr(), getAllIps(request),
-      preventLogFlood(emptyIfNull(login)));
+    LOGGER.atDebug().setMessage("login success [method|{}][provider|{}|{}][IP|{}|{}][login|{}]")
+      .addArgument(source::getMethod)
+      .addArgument(source::getProvider)
+      .addArgument(source::getProviderName)
+      .addArgument(request::getRemoteAddr)
+      .addArgument(() -> getAllIps(request))
+      .addArgument(() -> preventLogFlood(sanitizeLog(emptyIfNull(login))))
+      .log();
   }
 
   private static String getAllIps(HttpRequest request) {
@@ -103,4 +106,8 @@ public class AuthenticationEventImpl implements AuthenticationEvent {
     return str;
   }
 
+  private static String sanitizeLog(String message) {
+    return PATTERN_LINE_BREAK.matcher(message).replaceAll("_");
+  }
+
 }
index b47c7d0f4613bb31e14c1852600828fa0ee49992..d607ccb720e7af44420141cc5fd5e1ec912d0e5a 100644 (file)
@@ -82,7 +82,17 @@ public class AuthenticationEventImplTest {
 
     underTest.loginSuccess(request, "login", Source.sso());
 
-    verifyNoInteractions(request);
+    assertThat(logTester.logs()).isEmpty();
+  }
+
+  @Test
+  public void login_success_message_is_sanitized() {
+    logTester.setLevel(LoggerLevel.DEBUG);
+
+    underTest.loginSuccess(mockRequest("1.2.3.4"), "login with \n malicious line \r return", Source.sso());
+
+    assertThat(logTester.logs()).isNotEmpty()
+      .contains("login success [method|SSO][provider|SSO|sso][IP|1.2.3.4|][login|login with _ malicious line _ return]");
   }
 
   @Test
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/notification/email/EmailNotificationChannelTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/notification/email/EmailNotificationChannelTest.java
deleted file mode 100644 (file)
index 7409677..0000000
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * 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.notification.email;
-
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import javax.mail.MessagingException;
-import javax.mail.internet.MimeMessage;
-import org.apache.commons.mail.EmailException;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.config.EmailSettings;
-import org.sonar.api.notifications.Notification;
-import org.sonar.server.issue.notification.EmailMessage;
-import org.sonar.server.issue.notification.EmailTemplate;
-import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
-import org.subethamail.wiser.Wiser;
-import org.subethamail.wiser.WiserMessage;
-
-import static java.util.stream.Collectors.toMap;
-import static java.util.stream.Collectors.toSet;
-import static junit.framework.Assert.fail;
-import static org.apache.commons.lang.RandomStringUtils.random;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-@RunWith(DataProviderRunner.class)
-public class EmailNotificationChannelTest {
-
-  private static final String SUBJECT_PREFIX = "[SONARQUBE]";
-
-
-  private Wiser smtpServer;
-  private EmailSettings configuration;
-  private EmailNotificationChannel underTest;
-
-  @Before
-  public void setUp() {
-    smtpServer = new Wiser(0);
-    smtpServer.start();
-
-    configuration = mock(EmailSettings.class);
-    underTest = new EmailNotificationChannel(configuration, null, null);
-  }
-
-  @After
-  public void tearDown() {
-    smtpServer.stop();
-  }
-
-  @Test
-  public void isActivated_returns_true_if_smpt_host_is_not_empty() {
-    when(configuration.getSmtpHost()).thenReturn(random(5));
-
-    assertThat(underTest.isActivated()).isTrue();
-  }
-
-  @Test
-  public void isActivated_returns_false_if_smpt_host_is_null() {
-    when(configuration.getSmtpHost()).thenReturn(null);
-
-    assertThat(underTest.isActivated()).isFalse();
-  }
-
-  @Test
-  public void isActivated_returns_false_if_smpt_host_is_empty() {
-    when(configuration.getSmtpHost()).thenReturn("");
-
-    assertThat(underTest.isActivated()).isFalse();
-  }
-
-  @Test
-  public void shouldSendTestEmail() throws Exception {
-    configure();
-    underTest.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
-
-    List<WiserMessage> messages = smtpServer.getMessages();
-    assertThat(messages).hasSize(1);
-
-    MimeMessage email = messages.get(0).getMimeMessage();
-    assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
-    assertThat(email.getHeader("From", ",")).isEqualTo("SonarQube from NoWhere <server@nowhere>");
-    assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
-    assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Test Message from SonarQube");
-    assertThat((String) email.getContent()).startsWith("This is a test message from SonarQube.\r\n\r\nMail sent from: http://nemo.sonarsource.org");
-  }
-
-  @Test
-  public void shouldThrowAnExceptionWhenUnableToSendTestEmail() {
-    configure();
-    smtpServer.stop();
-
-    try {
-      underTest.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
-      fail();
-    } catch (EmailException e) {
-      // expected
-    }
-  }
-
-  @Test
-  public void shouldNotSendEmailWhenHostnameNotConfigured() {
-    EmailMessage emailMessage = new EmailMessage()
-      .setTo("user@nowhere")
-      .setSubject("Foo")
-      .setPlainTextMessage("Bar");
-    boolean delivered = underTest.deliver(emailMessage);
-    assertThat(smtpServer.getMessages()).isEmpty();
-    assertThat(delivered).isFalse();
-  }
-
-  @Test
-  public void shouldSendThreadedEmail() throws Exception {
-    configure();
-    EmailMessage emailMessage = new EmailMessage()
-      .setMessageId("reviews/view/1")
-      .setFrom("Full Username")
-      .setTo("user@nowhere")
-      .setSubject("Review #3")
-      .setPlainTextMessage("I'll take care of this violation.");
-    boolean delivered = underTest.deliver(emailMessage);
-
-    List<WiserMessage> messages = smtpServer.getMessages();
-    assertThat(messages).hasSize(1);
-
-    MimeMessage email = messages.get(0).getMimeMessage();
-
-    assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
-
-    assertThat(email.getHeader("In-Reply-To", null)).isEqualTo("<reviews/view/1@nemo.sonarsource.org>");
-    assertThat(email.getHeader("References", null)).isEqualTo("<reviews/view/1@nemo.sonarsource.org>");
-
-    assertThat(email.getHeader("List-ID", null)).isEqualTo("SonarQube <sonar.nemo.sonarsource.org>");
-    assertThat(email.getHeader("List-Archive", null)).isEqualTo("http://nemo.sonarsource.org");
-
-    assertThat(email.getHeader("From", ",")).isEqualTo("\"Full Username (SonarQube from NoWhere)\" <server@nowhere>");
-    assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
-    assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Review #3");
-    assertThat((String) email.getContent()).startsWith("I'll take care of this violation.");
-    assertThat(delivered).isTrue();
-  }
-
-  @Test
-  public void shouldSendNonThreadedEmail() throws Exception {
-    configure();
-    EmailMessage emailMessage = new EmailMessage()
-      .setTo("user@nowhere")
-      .setSubject("Foo")
-      .setPlainTextMessage("Bar");
-    boolean delivered = underTest.deliver(emailMessage);
-
-    List<WiserMessage> messages = smtpServer.getMessages();
-    assertThat(messages).hasSize(1);
-
-    MimeMessage email = messages.get(0).getMimeMessage();
-
-    assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
-
-    assertThat(email.getHeader("In-Reply-To", null)).isNull();
-    assertThat(email.getHeader("References", null)).isNull();
-
-    assertThat(email.getHeader("List-ID", null)).isEqualTo("SonarQube <sonar.nemo.sonarsource.org>");
-    assertThat(email.getHeader("List-Archive", null)).isEqualTo("http://nemo.sonarsource.org");
-
-    assertThat(email.getHeader("From", null)).isEqualTo("SonarQube from NoWhere <server@nowhere>");
-    assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
-    assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Foo");
-    assertThat((String) email.getContent()).startsWith("Bar");
-    assertThat(delivered).isTrue();
-  }
-
-  @Test
-  public void shouldNotThrowAnExceptionWhenUnableToSendEmail() {
-    configure();
-    smtpServer.stop();
-
-    EmailMessage emailMessage = new EmailMessage()
-      .setTo("user@nowhere")
-      .setSubject("Foo")
-      .setPlainTextMessage("Bar");
-    boolean delivered = underTest.deliver(emailMessage);
-
-    assertThat(delivered).isFalse();
-  }
-
-  @Test
-  public void shouldSendTestEmailWithSTARTTLS() {
-    smtpServer.getServer().setEnableTLS(true);
-    smtpServer.getServer().setRequireTLS(true);
-    configure();
-    when(configuration.getSecureConnection()).thenReturn("STARTTLS");
-
-    try {
-      underTest.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
-      fail("An SSL exception was expected a a proof that STARTTLS is enabled");
-    } catch (EmailException e) {
-      // We don't have a SSL certificate so we are expecting a SSL error
-      assertThat(e.getCause().getMessage()).isEqualTo("Could not convert socket to TLS");
-    }
-  }
-
-  @Test
-  public void deliverAll_has_no_effect_if_set_is_empty() {
-    EmailSettings emailSettings = mock(EmailSettings.class);
-    EmailNotificationChannel underTest = new EmailNotificationChannel(emailSettings, null, null);
-
-    int count = underTest.deliverAll(Collections.emptySet());
-
-    assertThat(count).isZero();
-    verifyNoInteractions(emailSettings);
-    assertThat(smtpServer.getMessages()).isEmpty();
-  }
-
-  @Test
-  public void deliverAll_has_no_effect_if_smtp_host_is_null() {
-    EmailSettings emailSettings = mock(EmailSettings.class);
-    when(emailSettings.getSmtpHost()).thenReturn(null);
-    Set<EmailDeliveryRequest> 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);
-
-    int count = underTest.deliverAll(requests);
-
-    assertThat(count).isZero();
-    verify(emailSettings).getSmtpHost();
-    verifyNoMoreInteractions(emailSettings);
-    assertThat(smtpServer.getMessages()).isEmpty();
-  }
-
-  @Test
-  @UseDataProvider("emptyStrings")
-  public void deliverAll_ignores_requests_which_recipient_is_empty(String emptyString) {
-    EmailSettings emailSettings = mock(EmailSettings.class);
-    when(emailSettings.getSmtpHost()).thenReturn(null);
-    Set<EmailDeliveryRequest> 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);
-
-    int count = underTest.deliverAll(requests);
-
-    assertThat(count).isZero();
-    verify(emailSettings).getSmtpHost();
-    verifyNoMoreInteractions(emailSettings);
-    assertThat(smtpServer.getMessages()).isEmpty();
-  }
-
-  @Test
-  public void deliverAll_returns_count_of_request_for_which_at_least_one_formatter_accept_it() throws MessagingException, IOException {
-    String recipientEmail = "foo@donut";
-    configure();
-    Notification notification1 = mock(Notification.class);
-    Notification notification2 = mock(Notification.class);
-    Notification notification3 = mock(Notification.class);
-    EmailTemplate template1 = mock(EmailTemplate.class);
-    EmailTemplate template3 = mock(EmailTemplate.class);
-    EmailMessage emailMessage1 = new EmailMessage().setTo(recipientEmail).setSubject("sub11").setPlainTextMessage("msg11");
-    EmailMessage emailMessage3 = new EmailMessage().setTo(recipientEmail).setSubject("sub3").setPlainTextMessage("msg3");
-    when(template1.format(notification1)).thenReturn(emailMessage1);
-    when(template3.format(notification3)).thenReturn(emailMessage3);
-    Set<EmailDeliveryRequest> requests = Stream.of(notification1, notification2, notification3)
-      .map(t -> new EmailDeliveryRequest(recipientEmail, t))
-      .collect(toSet());
-    EmailNotificationChannel underTest = new EmailNotificationChannel(configuration, new EmailTemplate[] {template1, template3}, null);
-
-    int count = underTest.deliverAll(requests);
-
-    assertThat(count).isEqualTo(2);
-    assertThat(smtpServer.getMessages()).hasSize(2);
-    Map<String, MimeMessage> messagesBySubject = smtpServer.getMessages().stream()
-      .map(t -> {
-        try {
-          return t.getMimeMessage();
-        } catch (MessagingException e) {
-          throw new RuntimeException(e);
-        }
-      })
-      .collect(toMap(t -> {
-        try {
-          return t.getSubject();
-        } catch (MessagingException e) {
-          throw new RuntimeException(e);
-        }
-      }, t -> t));
-
-    assertThat((String) messagesBySubject.get(SUBJECT_PREFIX + " " + emailMessage1.getSubject()).getContent())
-      .contains(emailMessage1.getMessage());
-    assertThat((String) messagesBySubject.get(SUBJECT_PREFIX + " " + emailMessage3.getSubject()).getContent())
-      .contains(emailMessage3.getMessage());
-  }
-
-  @Test
-  public void deliverAll_ignores_multiple_templates_by_notification_and_takes_the_first_one_only() throws MessagingException, IOException {
-    String recipientEmail = "foo@donut";
-    configure();
-    Notification notification1 = mock(Notification.class);
-    EmailTemplate template11 = mock(EmailTemplate.class);
-    EmailTemplate template12 = mock(EmailTemplate.class);
-    EmailMessage emailMessage11 = new EmailMessage().setTo(recipientEmail).setSubject("sub11").setPlainTextMessage("msg11");
-    EmailMessage emailMessage12 = new EmailMessage().setTo(recipientEmail).setSubject("sub12").setPlainTextMessage("msg12");
-    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);
-
-    int count = underTest.deliverAll(Collections.singleton(request));
-
-    assertThat(count).isOne();
-    assertThat(smtpServer.getMessages()).hasSize(1);
-    assertThat((String) smtpServer.getMessages().iterator().next().getMimeMessage().getContent())
-      .contains(emailMessage11.getMessage());
-  }
-
-  @DataProvider
-  public static Object[][] emptyStrings() {
-    return new Object[][] {
-      {""},
-      {"  "},
-      {" \n "}
-    };
-  }
-
-  private void configure() {
-    when(configuration.getSmtpHost()).thenReturn("localhost");
-    when(configuration.getSmtpPort()).thenReturn(smtpServer.getServer().getPort());
-    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");
-  }
-
-}