--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.picocontainer.Startable;
+import org.sonar.api.utils.log.Logger;
+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.event.AuthenticationEvent;
+import org.sonar.server.authentication.event.AuthenticationException;
+import org.sonar.server.notification.NotificationManager;
+
+import static org.sonar.server.log.ServerProcessLogging.STARTUP_LOGGER_NAME;
+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 DefaultAdminCredentialsVerifier implements Startable {
+
+ private static final Logger LOGGER = Loggers.get(STARTUP_LOGGER_NAME);
+
+ private final DbClient dbClient;
+ private final CredentialsLocalAuthentication localAuthentication;
+ private final NotificationManager notificationManager;
+
+ public DefaultAdminCredentialsVerifier(DbClient dbClient, CredentialsLocalAuthentication localAuthentication, NotificationManager notificationManager) {
+ this.dbClient = dbClient;
+ this.localAuthentication = localAuthentication;
+ this.notificationManager = notificationManager;
+ }
+
+ @Override
+ public void start() {
+ try (DbSession session = dbClient.openSession(false)) {
+ UserDto admin = dbClient.userDao().selectActiveUserByLogin(session, "admin");
+ if (admin == null || !isDefaultCredentialUser(session, admin)) {
+ return;
+ }
+ addWarningInSonarDotLog();
+ dbClient.userDao().update(session, admin.setResetPassword(true));
+ sendEmailToAdmins(session);
+ session.commit();
+ }
+ }
+
+ private static void addWarningInSonarDotLog() {
+ String highlighter = "####################################################################################################################";
+ String msg = "Default Administrator credentials are still being used. Make sure to change the password or deactivate the account.";
+
+ LOGGER.warn(highlighter);
+ LOGGER.warn(msg);
+ LOGGER.warn(highlighter);
+ }
+
+ private boolean isDefaultCredentialUser(DbSession dbSession, UserDto user) {
+ try {
+ localAuthentication.authenticate(dbSession, user, "admin", AuthenticationEvent.Method.BASIC);
+ return true;
+ } catch (AuthenticationException ex) {
+ return false;
+ }
+ }
+
+ 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
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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<DefaultAdminCredentialsVerifierNotification> {
+
+ private final DbClient dbClient;
+
+ public DefaultAdminCredentialsVerifierNotificationHandler(DbClient dbClient, EmailNotificationChannel emailNotificationChannel) {
+ super(emailNotificationChannel);
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public Optional<NotificationDispatcherMetadata> getMetadata() {
+ return Optional.empty();
+ }
+
+ @Override
+ public Class<DefaultAdminCredentialsVerifierNotification> getNotificationClass() {
+ return DefaultAdminCredentialsVerifierNotification.class;
+ }
+
+ @Override
+ public Set<EmailDeliveryRequest> toEmailDeliveryRequests(Collection<DefaultAdminCredentialsVerifierNotification> 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());
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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<Integer>) invocationOnMock -> ((Set<EmailNotificationChannel.EmailDeliveryRequest>) 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);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.");
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 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.After;
+import org.junit.Rule;
+import org.junit.Test;
+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.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 DefaultAdminCredentialsVerifierTest {
+
+ private static final String ADMIN_LOGIN = "admin";
+
+ @Rule
+ 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 DefaultAdminCredentialsVerifier underTest = new DefaultAdminCredentialsVerifier(db.getDbClient(), localAuthentication, notificationManager);
+
+ @After
+ public void after() {
+ underTest.stop();
+ }
+
+ @Test
+ public void set_reset_flag_to_true_and_add_log_when_admin_account_with_default_credential_is_detected() {
+ UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN));
+ changePassword(admin, "admin");
+
+ underTest.start();
+
+ 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
+ public void do_nothing_when_admin_is_not_using_default_credential() {
+ UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN));
+ changePassword(admin, "something_else");
+
+ underTest.start();
+
+ assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isFalse();
+ assertThat(logTester.logs()).isEmpty();
+ verifyNoMoreInteractions(notificationManager);
+ }
+
+ @Test
+ public void do_nothing_when_no_admin_account_with_default_credential_detected() {
+ UserDto otherUser = db.users().insertUser();
+ changePassword(otherUser, "admin");
+
+ underTest.start();
+
+ assertThat(db.users().selectUserByLogin(otherUser.getLogin()).get().isResetPassword()).isFalse();
+ assertThat(logTester.logs()).isEmpty();
+ verifyNoMoreInteractions(notificationManager);
+ }
+
+ @Test
+ public void do_nothing_when_admin_account_with_default_credential_is_disabled() {
+ UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN).setActive(false));
+ changePassword(admin, "admin");
+
+ underTest.start();
+
+ assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isFalse();
+ assertThat(logTester.logs()).isEmpty();
+ verifyNoMoreInteractions(notificationManager);
+ }
+
+ private void changePassword(UserDto user, String password) {
+ localAuthentication.storeHashPassword(user, password);
+ db.getDbClient().userDao().update(db.getSession(), user);
+ db.commit();
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.picocontainer.Startable;
-import org.sonar.api.utils.log.Logger;
-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.event.AuthenticationEvent;
-import org.sonar.server.authentication.event.AuthenticationException;
-import org.sonar.server.notification.NotificationManager;
-
-import static org.sonar.server.log.ServerProcessLogging.STARTUP_LOGGER_NAME;
-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 DefaultAdminCredentialsVerifier implements Startable {
-
- private static final Logger LOGGER = Loggers.get(STARTUP_LOGGER_NAME);
-
- private final DbClient dbClient;
- private final CredentialsLocalAuthentication localAuthentication;
- private final NotificationManager notificationManager;
-
- public DefaultAdminCredentialsVerifier(DbClient dbClient, CredentialsLocalAuthentication localAuthentication, NotificationManager notificationManager) {
- this.dbClient = dbClient;
- this.localAuthentication = localAuthentication;
- this.notificationManager = notificationManager;
- }
-
- @Override
- public void start() {
- try (DbSession session = dbClient.openSession(false)) {
- UserDto admin = dbClient.userDao().selectActiveUserByLogin(session, "admin");
- if (admin == null || !isDefaultCredentialUser(session, admin)) {
- return;
- }
- addWarningInSonarDotLog();
- dbClient.userDao().update(session, admin.setResetPassword(true));
- sendEmailToAdmins(session);
- session.commit();
- }
- }
-
- private static void addWarningInSonarDotLog() {
- String highlighter = "####################################################################################################################";
- String msg = "Default Administrator credentials are still being used. Make sure to change the password or deactivate the account.";
-
- LOGGER.warn(highlighter);
- LOGGER.warn(msg);
- LOGGER.warn(highlighter);
- }
-
- private boolean isDefaultCredentialUser(DbSession dbSession, UserDto user) {
- try {
- localAuthentication.authenticate(dbSession, user, "admin", AuthenticationEvent.Method.BASIC);
- return true;
- } catch (AuthenticationException ex) {
- return false;
- }
- }
-
- 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
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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);
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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<DefaultAdminCredentialsVerifierNotification> {
-
- private final DbClient dbClient;
-
- public DefaultAdminCredentialsVerifierNotificationHandler(DbClient dbClient, EmailNotificationChannel emailNotificationChannel) {
- super(emailNotificationChannel);
- this.dbClient = dbClient;
- }
-
- @Override
- public Optional<NotificationDispatcherMetadata> getMetadata() {
- return Optional.empty();
- }
-
- @Override
- public Class<DefaultAdminCredentialsVerifierNotification> getNotificationClass() {
- return DefaultAdminCredentialsVerifierNotification.class;
- }
-
- @Override
- public Set<EmailDeliveryRequest> toEmailDeliveryRequests(Collection<DefaultAdminCredentialsVerifierNotification> 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());
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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);
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.authentication;
-
-import javax.annotation.ParametersAreNonnullByDefault;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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<Integer>) invocationOnMock -> ((Set<EmailNotificationChannel.EmailDeliveryRequest>) 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);
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.");
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2021 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.After;
-import org.junit.Rule;
-import org.junit.Test;
-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.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 DefaultAdminCredentialsVerifierTest {
-
- private static final String ADMIN_LOGIN = "admin";
-
- @Rule
- 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 DefaultAdminCredentialsVerifier underTest = new DefaultAdminCredentialsVerifier(db.getDbClient(), localAuthentication, notificationManager);
-
- @After
- public void after() {
- underTest.stop();
- }
-
- @Test
- public void set_reset_flag_to_true_and_add_log_when_admin_account_with_default_credential_is_detected() {
- UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN));
- changePassword(admin, "admin");
-
- underTest.start();
-
- 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
- public void do_nothing_when_admin_is_not_using_default_credential() {
- UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN));
- changePassword(admin, "something_else");
-
- underTest.start();
-
- assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isFalse();
- assertThat(logTester.logs()).isEmpty();
- verifyNoMoreInteractions(notificationManager);
- }
-
- @Test
- public void do_nothing_when_no_admin_account_with_default_credential_detected() {
- UserDto otherUser = db.users().insertUser();
- changePassword(otherUser, "admin");
-
- underTest.start();
-
- assertThat(db.users().selectUserByLogin(otherUser.getLogin()).get().isResetPassword()).isFalse();
- assertThat(logTester.logs()).isEmpty();
- verifyNoMoreInteractions(notificationManager);
- }
-
- @Test
- public void do_nothing_when_admin_account_with_default_credential_is_disabled() {
- UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN).setActive(false));
- changePassword(admin, "admin");
-
- underTest.start();
-
- assertThat(db.users().selectUserByLogin(admin.getLogin()).get().isResetPassword()).isFalse();
- assertThat(logTester.logs()).isEmpty();
- verifyNoMoreInteractions(notificationManager);
- }
-
- private void changePassword(UserDto user, String password) {
- localAuthentication.storeHashPassword(user, password);
- db.getDbClient().userDao().update(db.getSession(), user);
- db.commit();
- }
-}