From 3c492694e75c91329f63f4557d7f2425afc4ea64 Mon Sep 17 00:00:00 2001 From: Julien Lancelot Date: Mon, 30 Nov 2020 10:44:46 +0100 Subject: [PATCH] SONAR-14176 Send email to admins when default admin credential is detected --- .../sonar/db/permission/AuthorizationDao.java | 3 - .../server/property/InternalProperties.java | 2 + .../DefaultAdminCredentialsVerifier.java} | 35 +++-- ...tAdminCredentialsVerifierNotification.java | 32 +++++ ...redentialsVerifierNotificationHandler.java | 62 +++++++++ ...edentialsVerifierNotificationTemplate.java | 47 +++++++ ...ntialsVerifierNotificationHandlerTest.java | 120 ++++++++++++++++++ ...tialsVerifierNotificationTemplateTest.java | 50 ++++++++ .../DefaultAdminCredentialsVerifierTest.java} | 35 ++++- .../platformlevel/PlatformLevel4.java | 4 + .../platformlevel/PlatformLevelStartup.java | 6 +- 11 files changed, 374 insertions(+), 22 deletions(-) rename server/sonar-webserver-core/src/main/java/org/sonar/server/{startup/DetectActiveAdminAccountWithDefaultCredential.java => authentication/DefaultAdminCredentialsVerifier.java} (64%) create mode 100644 server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotification.java create mode 100644 server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationHandler.java create mode 100644 server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationTemplate.java create mode 100644 server/sonar-webserver-core/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationHandlerTest.java create mode 100644 server/sonar-webserver-core/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationTemplateTest.java rename server/sonar-webserver-core/src/test/java/org/sonar/server/{startup/DetectActiveAdminAccountWithDefaultCredentialTest.java => authentication/DefaultAdminCredentialsVerifierTest.java} (67%) diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/permission/AuthorizationDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/permission/AuthorizationDao.java index 37409ba7fef..f01ecf57b55 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/permission/AuthorizationDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/permission/AuthorizationDao.java @@ -148,9 +148,6 @@ public class AuthorizationDao implements Dao { return mapper(dbSession).selectEmailSubscribersWithGlobalPermission(ADMINISTER_QUALITY_PROFILES.getKey()); } - /** - * Used by license notifications - */ public Set selectGlobalAdministerEmailSubscribers(DbSession dbSession) { return mapper(dbSession).selectEmailSubscribersWithGlobalPermission(ADMINISTER.getKey()); } diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/property/InternalProperties.java b/server/sonar-server-common/src/main/java/org/sonar/server/property/InternalProperties.java index 55cb14407d1..05b45240d5d 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/property/InternalProperties.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/property/InternalProperties.java @@ -60,6 +60,8 @@ public interface InternalProperties { String DEFAULT_PORTFOLIO_TEMPLATE = "defaultTemplate.port"; String DEFAULT_APPLICATION_TEMPLATE = "defaultTemplate.app"; + String DEFAULT_ADMIN_CREDENTIAL_USAGE_EMAIL = "default.admin.cred"; + /** * Read the value of the specified property. * diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/DetectActiveAdminAccountWithDefaultCredential.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifier.java similarity index 64% rename from server/sonar-webserver-core/src/main/java/org/sonar/server/startup/DetectActiveAdminAccountWithDefaultCredential.java rename to server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifier.java index 09249a02a36..c79cce61aae 100644 --- a/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/DetectActiveAdminAccountWithDefaultCredential.java +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifier.java @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.startup; +package org.sonar.server.authentication; import org.picocontainer.Startable; import org.sonar.api.utils.log.Logger; @@ -26,37 +26,42 @@ import org.sonar.api.utils.log.Loggers; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.CredentialsLocalAuthentication; import org.sonar.server.authentication.event.AuthenticationEvent; import org.sonar.server.authentication.event.AuthenticationException; +import org.sonar.server.notification.NotificationManager; + +import static org.sonar.server.property.InternalProperties.DEFAULT_ADMIN_CREDENTIAL_USAGE_EMAIL; /** * Detect usage of an active admin account with default credential in order to ask this account to reset its password during authentication. */ -public class DetectActiveAdminAccountWithDefaultCredential implements Startable { +public class DefaultAdminCredentialsVerifier implements Startable { - private static final Logger LOGGER = Loggers.get(DetectActiveAdminAccountWithDefaultCredential.class); + private static final Logger LOGGER = Loggers.get(DefaultAdminCredentialsVerifier.class); private final DbClient dbClient; private final CredentialsLocalAuthentication localAuthentication; + private final NotificationManager notificationManager; - public DetectActiveAdminAccountWithDefaultCredential(DbClient dbClient, CredentialsLocalAuthentication localAuthentication) { + public DefaultAdminCredentialsVerifier(DbClient dbClient, CredentialsLocalAuthentication localAuthentication, NotificationManager notificationManager) { this.dbClient = dbClient; this.localAuthentication = localAuthentication; + this.notificationManager = notificationManager; } @Override public void start() { - try (DbSession dbSession = dbClient.openSession(false)) { - UserDto admin = dbClient.userDao().selectActiveUserByLogin(dbSession, "admin"); - if (admin == null || !isDefaultCredentialUser(dbSession, admin)) { + try (DbSession session = dbClient.openSession(false)) { + UserDto admin = dbClient.userDao().selectActiveUserByLogin(session, "admin"); + if (admin == null || !isDefaultCredentialUser(session, admin)) { return; } LOGGER.warn("*******************************************************************************************************************"); LOGGER.warn("Default Administrator credentials are still being used. Make sure to change the password or deactivate the account."); LOGGER.warn("*******************************************************************************************************************"); - dbClient.userDao().update(dbSession, admin.setResetPassword(true)); - dbSession.commit(); + dbClient.userDao().update(session, admin.setResetPassword(true)); + sendEmailToAdmins(session); + session.commit(); } } @@ -69,6 +74,16 @@ public class DetectActiveAdminAccountWithDefaultCredential implements Startable } } + private void sendEmailToAdmins(DbSession session) { + if (dbClient.internalPropertiesDao().selectByKey(session, DEFAULT_ADMIN_CREDENTIAL_USAGE_EMAIL) + .map(Boolean::parseBoolean) + .orElse(false)) { + return; + } + notificationManager.scheduleForSending(new DefaultAdminCredentialsVerifierNotification()); + dbClient.internalPropertiesDao().save(session, DEFAULT_ADMIN_CREDENTIAL_USAGE_EMAIL, Boolean.TRUE.toString()); + } + @Override public void stop() { // Nothing to do diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotification.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotification.java new file mode 100644 index 00000000000..46336b4356c --- /dev/null +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotification.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.authentication; + +import org.sonar.api.notifications.Notification; + +public class DefaultAdminCredentialsVerifierNotification extends Notification { + + static final String TYPE = "default-admin-credential-verifier"; + + public DefaultAdminCredentialsVerifierNotification() { + super(TYPE); + } +} diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationHandler.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationHandler.java new file mode 100644 index 00000000000..ac8efa6fbaa --- /dev/null +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationHandler.java @@ -0,0 +1,62 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.authentication; + +import java.util.Collection; +import java.util.Optional; +import java.util.Set; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.server.notification.EmailNotificationHandler; +import org.sonar.server.notification.NotificationDispatcherMetadata; +import org.sonar.server.notification.email.EmailNotificationChannel; +import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest; + +import static java.util.stream.Collectors.toSet; + +public class DefaultAdminCredentialsVerifierNotificationHandler extends EmailNotificationHandler { + + private final DbClient dbClient; + + public DefaultAdminCredentialsVerifierNotificationHandler(DbClient dbClient, EmailNotificationChannel emailNotificationChannel) { + super(emailNotificationChannel); + this.dbClient = dbClient; + } + + @Override + public Optional getMetadata() { + return Optional.empty(); + } + + @Override + public Class getNotificationClass() { + return DefaultAdminCredentialsVerifierNotification.class; + } + + @Override + public Set toEmailDeliveryRequests(Collection notifications) { + try (DbSession session = dbClient.openSession(false)) { + return dbClient.authorizationDao().selectGlobalAdministerEmailSubscribers(session) + .stream() + .flatMap(t -> notifications.stream().map(notification -> new EmailDeliveryRequest(t.getEmail(), notification))) + .collect(toSet()); + } + } +} diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationTemplate.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationTemplate.java new file mode 100644 index 00000000000..243b00536aa --- /dev/null +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationTemplate.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.authentication; + +import javax.annotation.CheckForNull; +import org.sonar.api.notifications.Notification; +import org.sonar.server.issue.notification.EmailMessage; +import org.sonar.server.issue.notification.EmailTemplate; + +public class DefaultAdminCredentialsVerifierNotificationTemplate implements EmailTemplate { + + static final String SUBJECT = "Default Administrator credentials are still used"; + static final String BODY_FORMAT = "Hello,\n\n" + + "Your SonarQube instance is still using default administrator credentials.\n" + + "Make sure to change the password for the 'admin' account or deactivate this account."; + + @Override + @CheckForNull + public EmailMessage format(Notification notification) { + if (!DefaultAdminCredentialsVerifierNotification.TYPE.equals(notification.getType())) { + return null; + } + + return new EmailMessage() + .setMessageId(DefaultAdminCredentialsVerifierNotification.TYPE) + .setSubject(SUBJECT) + .setPlainTextMessage(BODY_FORMAT); + } + +} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationHandlerTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationHandlerTest.java new file mode 100644 index 00000000000..97bb756356a --- /dev/null +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationHandlerTest.java @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.authentication; + +import java.util.Set; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.stubbing.Answer; +import org.sonar.db.DbTester; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.notification.email.EmailNotificationChannel; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.sonar.db.permission.GlobalPermission.ADMINISTER; + +public class DefaultAdminCredentialsVerifierNotificationHandlerTest { + + @Rule + public DbTester db = DbTester.create(); + + private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class); + + private DefaultAdminCredentialsVerifierNotificationHandler underTest = new DefaultAdminCredentialsVerifierNotificationHandler(db.getDbClient(), + emailNotificationChannel); + + @Before + public void setUp() { + when(emailNotificationChannel.deliverAll(anySet())) + .then((Answer) invocationOnMock -> ((Set) invocationOnMock.getArguments()[0]).size()); + } + + @Test + public void deliver_to_all_admins_having_emails() { + when(emailNotificationChannel.isActivated()).thenReturn(true); + DefaultAdminCredentialsVerifierNotification detectActiveAdminAccountWithDefaultCredentialNotification = mock(DefaultAdminCredentialsVerifierNotification.class); + // Users granted admin permission directly + UserDto admin1 = db.users().insertUser(u -> u.setEmail("admin1")); + UserDto adminWithNoEmail = db.users().insertUser(u -> u.setEmail(null)); + db.users().insertPermissionOnUser(admin1, ADMINISTER); + db.users().insertPermissionOnUser(adminWithNoEmail, ADMINISTER); + // User granted admin permission by group membership + UserDto admin2 = db.users().insertUser(u -> u.setEmail("admin2")); + GroupDto adminGroup = db.users().insertGroup(); + db.users().insertPermissionOnGroup(adminGroup, ADMINISTER); + db.users().insertMember(adminGroup, admin2); + db.users().insertUser(u -> u.setEmail("otherUser")); + + int deliver = underTest.deliver(singletonList(detectActiveAdminAccountWithDefaultCredentialNotification)); + + // Only 2 admins have there email defined + assertThat(deliver).isEqualTo(2); + verify(emailNotificationChannel).isActivated(); + verify(emailNotificationChannel).deliverAll(anySet()); + verifyNoMoreInteractions(detectActiveAdminAccountWithDefaultCredentialNotification); + } + + @Test + public void deliver_to_no_one_when_no_admins() { + when(emailNotificationChannel.isActivated()).thenReturn(true); + DefaultAdminCredentialsVerifierNotification detectActiveAdminAccountWithDefaultCredentialNotification = mock(DefaultAdminCredentialsVerifierNotification.class); + db.users().insertUser(u -> u.setEmail("otherUser")); + + int deliver = underTest.deliver(singletonList(detectActiveAdminAccountWithDefaultCredentialNotification)); + + assertThat(deliver).isZero(); + verify(emailNotificationChannel).isActivated(); + verifyNoMoreInteractions(emailNotificationChannel); + verifyNoMoreInteractions(detectActiveAdminAccountWithDefaultCredentialNotification); + } + + @Test + public void do_nothing_if_emailNotificationChannel_is_disabled() { + when(emailNotificationChannel.isActivated()).thenReturn(false); + DefaultAdminCredentialsVerifierNotification detectActiveAdminAccountWithDefaultCredentialNotification = mock( + DefaultAdminCredentialsVerifierNotification.class); + + int deliver = underTest.deliver(singletonList(detectActiveAdminAccountWithDefaultCredentialNotification)); + + assertThat(deliver).isZero(); + verify(emailNotificationChannel).isActivated(); + verifyNoMoreInteractions(emailNotificationChannel); + verifyNoMoreInteractions(detectActiveAdminAccountWithDefaultCredentialNotification); + } + + @Test + public void getMetadata_returns_empty() { + assertThat(underTest.getMetadata()).isEmpty(); + } + + @Test + public void getNotificationClass() { + assertThat(underTest.getNotificationClass()).isEqualTo(DefaultAdminCredentialsVerifierNotification.class); + } + +} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationTemplateTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationTemplateTest.java new file mode 100644 index 00000000000..a1eb94a8548 --- /dev/null +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierNotificationTemplateTest.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 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.authentication; + +import org.junit.Test; +import org.sonar.api.notifications.Notification; +import org.sonar.server.issue.notification.EmailMessage; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DefaultAdminCredentialsVerifierNotificationTemplateTest { + + private DefaultAdminCredentialsVerifierNotificationTemplate underTest = new DefaultAdminCredentialsVerifierNotificationTemplate(); + + @Test + public void do_not_format_other_notifications() { + assertThat(underTest.format(new Notification("foo"))).isNull(); + } + + @Test + public void format_notification() { + Notification notification = new Notification(DefaultAdminCredentialsVerifierNotification.TYPE); + + EmailMessage emailMessage = underTest.format(notification); + + assertThat(emailMessage.getSubject()).isEqualTo("Default Administrator credentials are still used"); + assertThat(emailMessage.getMessage()).isEqualTo("Hello,\n\n" + + "Your SonarQube instance is still using default administrator credentials.\n" + + "Make sure to change the password for the 'admin' account or deactivate this account."); + } + +} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/DetectActiveAdminAccountWithDefaultCredentialTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierTest.java similarity index 67% rename from server/sonar-webserver-core/src/test/java/org/sonar/server/startup/DetectActiveAdminAccountWithDefaultCredentialTest.java rename to server/sonar-webserver-core/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierTest.java index 5fee1484965..db88777c60c 100644 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/DetectActiveAdminAccountWithDefaultCredentialTest.java +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierTest.java @@ -18,32 +18,38 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.startup; +package org.sonar.server.authentication; import org.junit.After; import org.junit.Rule; import org.junit.Test; -import org.sonar.api.utils.System2; +import org.sonar.api.notifications.Notification; import org.sonar.api.utils.log.LogTester; import org.sonar.api.utils.log.LoggerLevel; import org.sonar.db.DbTester; import org.sonar.db.user.UserDto; -import org.sonar.server.authentication.CredentialsLocalAuthentication; +import org.sonar.server.notification.NotificationManager; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.sonar.server.property.InternalProperties.DEFAULT_ADMIN_CREDENTIAL_USAGE_EMAIL; -public class DetectActiveAdminAccountWithDefaultCredentialTest { +public class DefaultAdminCredentialsVerifierTest { private static final String ADMIN_LOGIN = "admin"; @Rule - public DbTester db = DbTester.create(System2.INSTANCE); + public DbTester db = DbTester.create(); @Rule public LogTester logTester = new LogTester(); private final CredentialsLocalAuthentication localAuthentication = new CredentialsLocalAuthentication(db.getDbClient()); + private final NotificationManager notificationManager = mock(NotificationManager.class); - private final DetectActiveAdminAccountWithDefaultCredential underTest = new DetectActiveAdminAccountWithDefaultCredential(db.getDbClient(), localAuthentication); + private final DefaultAdminCredentialsVerifier underTest = new DefaultAdminCredentialsVerifier(db.getDbClient(), localAuthentication, notificationManager); @After public void after() { @@ -59,6 +65,20 @@ public class DetectActiveAdminAccountWithDefaultCredentialTest { assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isTrue(); assertThat(logTester.logs(LoggerLevel.WARN)).contains("Default Administrator credentials are still being used. Make sure to change the password or deactivate the account."); + assertThat(db.getDbClient().internalPropertiesDao().selectByKey(db.getSession(), DEFAULT_ADMIN_CREDENTIAL_USAGE_EMAIL).get()).isEqualTo("true"); + verify(notificationManager).scheduleForSending(any(Notification.class)); + } + + @Test + public void do_not_send_email_to_admins_when_already_sent() { + UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN)); + changePassword(admin, "admin"); + db.getDbClient().internalPropertiesDao().save(db.getSession(), DEFAULT_ADMIN_CREDENTIAL_USAGE_EMAIL, "true"); + db.commit(); + + underTest.start(); + + verifyNoMoreInteractions(notificationManager); } @Test @@ -70,6 +90,7 @@ public class DetectActiveAdminAccountWithDefaultCredentialTest { assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isFalse(); assertThat(logTester.logs()).isEmpty(); + verifyNoMoreInteractions(notificationManager); } @Test @@ -81,6 +102,7 @@ public class DetectActiveAdminAccountWithDefaultCredentialTest { assertThat(db.users().selectUserByLogin(otherUser.getLogin()).get().isResetPassword()).isFalse(); assertThat(logTester.logs()).isEmpty(); + verifyNoMoreInteractions(notificationManager); } @Test @@ -92,6 +114,7 @@ public class DetectActiveAdminAccountWithDefaultCredentialTest { assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isFalse(); assertThat(logTester.logs()).isEmpty(); + verifyNoMoreInteractions(notificationManager); } private void changePassword(UserDto user, String password) { diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 23217a8527d..42859630994 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -41,6 +41,8 @@ import org.sonar.core.platform.ComponentContainer; import org.sonar.core.platform.PlatformEditionProvider; import org.sonar.server.almsettings.MultipleAlmFeatureProvider; import org.sonar.server.authentication.AuthenticationModule; +import org.sonar.server.authentication.DefaultAdminCredentialsVerifierNotificationHandler; +import org.sonar.server.authentication.DefaultAdminCredentialsVerifierNotificationTemplate; import org.sonar.server.authentication.LogOAuthWarning; import org.sonar.server.authentication.ws.AuthenticationWsModule; import org.sonar.server.badge.ws.ProjectBadgesWsModule; @@ -351,6 +353,8 @@ public class PlatformLevel4 extends PlatformLevel { GitLabModule.class, LdapModule.class, SamlModule.class, + DefaultAdminCredentialsVerifierNotificationTemplate.class, + DefaultAdminCredentialsVerifierNotificationHandler.class, // users UserSessionFactoryImpl.class, diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java index 121acd04311..cc0ae1063ae 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java @@ -23,6 +23,7 @@ import org.sonar.api.utils.log.Loggers; import org.sonar.core.platform.EditionProvider; import org.sonar.core.platform.PlatformEditionProvider; import org.sonar.server.app.ProcessCommandWrapper; +import org.sonar.server.authentication.DefaultAdminCredentialsVerifier; import org.sonar.server.ce.queue.CeQueueCleaner; import org.sonar.server.es.IndexerStartupTask; import org.sonar.server.organization.DefaultOrganizationEnforcer; @@ -37,7 +38,6 @@ import org.sonar.server.qualityprofile.BuiltInQualityProfilesUpdateListener; import org.sonar.server.qualityprofile.RegisterQualityProfiles; import org.sonar.server.rule.RegisterRules; import org.sonar.server.rule.WebServerRuleFinder; -import org.sonar.server.startup.DetectActiveAdminAccountWithDefaultCredential; import org.sonar.server.startup.GeneratePluginIndex; import org.sonar.server.startup.RegisterMetrics; import org.sonar.server.startup.RegisterPermissionTemplates; @@ -74,9 +74,9 @@ public class PlatformLevelStartup extends PlatformLevel { RenameDeprecatedPropertyKeys.class, CeQueueCleaner.class, UpgradeSuggestionsCleaner.class, - DetectActiveAdminAccountWithDefaultCredential.class); + DefaultAdminCredentialsVerifier.class); - // RegisterServletFilters makes the WebService engine of Level4 served by the MasterServletFilter, therefor it + // RegisterServletFilters makes the WebService engine of Level4 served by the MasterServletFilter, therefore it // must be started after all the other startup tasks add(RegisterServletFilters.class); } -- 2.39.5