CredentialsAuthentication.class,
CredentialsExternalAuthentication.class,
CredentialsLocalAuthentication.class,
+ DefaultAdminCredentialsVerifierFilter.class,
HttpHeadersAuthentication.class,
IdentityProviderRepository.class,
InitFilter.class,
*/
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;
+public interface DefaultAdminCredentialsVerifier {
-import static org.sonar.server.log.ServerProcessLogging.STARTUP_LOGGER_NAME;
-import static org.sonar.server.property.InternalProperties.DEFAULT_ADMIN_CREDENTIAL_USAGE_EMAIL;
+ boolean hasDefaultCredentialUser();
-/**
- * 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 com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.Set;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.web.ServletFilter;
+import org.sonar.server.user.ThreadLocalUserSession;
+
+import static org.sonar.api.web.ServletFilter.UrlPattern.Builder.staticResourcePatterns;
+import static org.sonar.server.authentication.AuthenticationRedirection.redirectTo;
+
+public class DefaultAdminCredentialsVerifierFilter extends ServletFilter {
+ private static final String RESET_PASSWORD_PATH = "/account/reset_password";
+ private static final String CHANGE_ADMIN_PASSWORD_PATH = "/admin/change_admin_password";
+ // This property is used by Orchestrator to disable this force redirect. It should never be used in production, which
+ // is why this is not defined in org.sonar.process.ProcessProperties.
+ private static final String SONAR_FORCE_REDIRECT_DEFAULT_ADMIN_CREDENTIALS = "sonar.forceRedirectOnDefaultAdminCredentials";
+
+ private static final Set<String> SKIPPED_URLS = ImmutableSet.of(
+ RESET_PASSWORD_PATH,
+ CHANGE_ADMIN_PASSWORD_PATH,
+ "/batch/*", "/api/*");
+
+ private final Configuration config;
+ private final DefaultAdminCredentialsVerifier defaultAdminCredentialsVerifier;
+ private final ThreadLocalUserSession userSession;
+
+ public DefaultAdminCredentialsVerifierFilter(Configuration config, DefaultAdminCredentialsVerifier defaultAdminCredentialsVerifier, ThreadLocalUserSession userSession) {
+ this.config = config;
+ this.defaultAdminCredentialsVerifier = defaultAdminCredentialsVerifier;
+ this.userSession = userSession;
+ }
+
+ @Override
+ public UrlPattern doGetPattern() {
+ return UrlPattern.builder()
+ .includes("/*")
+ .excludes(staticResourcePatterns())
+ .excludes(SKIPPED_URLS)
+ .build();
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) {
+ // nothing to do
+ }
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ HttpServletResponse response = (HttpServletResponse) servletResponse;
+ boolean forceRedirect = config
+ .getBoolean(SONAR_FORCE_REDIRECT_DEFAULT_ADMIN_CREDENTIALS)
+ .orElse(true);
+
+ if (forceRedirect && userSession.hasSession() && userSession.isLoggedIn()
+ && userSession.isSystemAdministrator() && !"admin".equals(userSession.getLogin())
+ && defaultAdminCredentialsVerifier.hasDefaultCredentialUser()) {
+ redirectTo(response, request.getContextPath() + CHANGE_ADMIN_PASSWORD_PATH);
+ }
+
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void destroy() {
+ // 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.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 DefaultAdminCredentialsVerifierImpl implements Startable, DefaultAdminCredentialsVerifier {
+
+ private static final Logger LOGGER = Loggers.get(STARTUP_LOGGER_NAME);
+
+ private final DbClient dbClient;
+ private final CredentialsLocalAuthentication localAuthentication;
+ private final NotificationManager notificationManager;
+
+ public DefaultAdminCredentialsVerifierImpl(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 = getAdminUser(session);
+ if (admin == null || !isDefaultCredentialUser(session, admin)) {
+ return;
+ }
+ addWarningInSonarDotLog();
+ dbClient.userDao().update(session, admin.setResetPassword(true));
+ sendEmailToAdmins(session);
+ session.commit();
+ }
+ }
+
+ public boolean hasDefaultCredentialUser() {
+ try (DbSession session = dbClient.openSession(false)) {
+ UserDto admin = getAdminUser(session);
+ if (admin == null) {
+ return false;
+ } else {
+ return isDefaultCredentialUser(session, admin);
+ }
+ }
+ }
+
+ private UserDto getAdminUser(DbSession session) {
+ return dbClient.userDao().selectActiveUserByLogin(session, "admin");
+ }
+
+ 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 com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Optional;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.config.Configuration;
+import org.sonar.server.user.ThreadLocalUserSession;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+@RunWith(DataProviderRunner.class)
+public class DefaultAdminCredentialsVerifierFilterTest {
+
+ private final HttpServletRequest request = mock(HttpServletRequest.class);
+ private final HttpServletResponse response = mock(HttpServletResponse.class);
+ private final FilterChain chain = mock(FilterChain.class);
+ private final Configuration config = mock(Configuration.class);
+ private final DefaultAdminCredentialsVerifier defaultAdminCredentialsVerifier = mock(DefaultAdminCredentialsVerifier.class);
+ private final ThreadLocalUserSession session = mock(ThreadLocalUserSession.class);
+
+ private final DefaultAdminCredentialsVerifierFilter underTest = new DefaultAdminCredentialsVerifierFilter(config, defaultAdminCredentialsVerifier, session);
+
+ @Before
+ public void before() {
+ when(request.getRequestURI()).thenReturn("/");
+ when(request.getContextPath()).thenReturn("");
+
+ when(config.getBoolean("sonar.forceRedirectOnDefaultAdminCredentials")).thenReturn(Optional.of(true));
+ when(defaultAdminCredentialsVerifier.hasDefaultCredentialUser()).thenReturn(true);
+ when(session.hasSession()).thenReturn(true);
+ when(session.isLoggedIn()).thenReturn(true);
+ when(session.isSystemAdministrator()).thenReturn(true);
+ }
+
+ @Test
+ public void verify_other_methods() {
+ underTest.init(mock(FilterConfig.class));
+ underTest.destroy();
+
+ verifyNoInteractions(request, response, chain, session);
+ }
+
+ @Test
+ public void redirect_if_instance_uses_default_admin_credentials() throws Exception {
+ underTest.doFilter(request, response, chain);
+
+ verify(response).sendRedirect("/admin/change_admin_password");
+ }
+
+ @Test
+ public void redirect_if_instance_uses_default_admin_credentials_and_web_context_configured() throws Exception {
+ when(request.getContextPath()).thenReturn("/sonarqube");
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response).sendRedirect("/sonarqube/admin/change_admin_password");
+ }
+
+ @Test
+ public void redirect_if_request_uri_ends_with_slash() throws Exception {
+ when(request.getRequestURI()).thenReturn("/projects/");
+ when(request.getContextPath()).thenReturn("/sonarqube");
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response).sendRedirect("/sonarqube/admin/change_admin_password");
+ }
+
+ @Test
+ public void do_not_redirect_if_not_a_system_administrator() throws Exception {
+ when(session.isSystemAdministrator()).thenReturn(false);
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response, never()).sendRedirect(any());
+ }
+
+ @Test
+ public void do_not_redirect_if_not_logged_in() throws Exception {
+ when(session.isLoggedIn()).thenReturn(false);
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response, never()).sendRedirect(any());
+ }
+
+ @Test
+ public void do_not_redirect_if_user_is_admin() throws Exception {
+ when(session.getLogin()).thenReturn("admin");
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response, never()).sendRedirect(any());
+ }
+
+ @Test
+ public void do_not_redirect_if_instance_does_not_use_default_admin_credentials() throws Exception {
+ when(defaultAdminCredentialsVerifier.hasDefaultCredentialUser()).thenReturn(false);
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response, never()).sendRedirect(any());
+ }
+
+ @Test
+ public void do_not_redirect_if_config_says_so() throws Exception {
+ when(config.getBoolean("sonar.forceRedirectOnDefaultAdminCredentials")).thenReturn(Optional.of(false));
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response, never()).sendRedirect(any());
+ }
+
+ @Test
+ @UseDataProvider("skipped_urls")
+ public void doGetPattern_verify(String urltoSkip) throws Exception {
+ when(request.getRequestURI()).thenReturn(urltoSkip);
+ when(request.getContextPath()).thenReturn("");
+ underTest.doGetPattern().matches(urltoSkip);
+
+ verify(response, never()).sendRedirect(any());
+ }
+
+ @DataProvider
+ public static Object[][] skipped_urls() {
+ return new Object[][] {
+ {"/batch/index"},
+ {"/batch/file"},
+ {"/api/issues"},
+ {"/api/issues/"},
+ {"/api/*"},
+ {"/admin/change_admin_password"},
+ {"/account/reset_password"},
+ };
+ }
+
+}
--- /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 DefaultAdminCredentialsVerifierImplTest {
+
+ 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 DefaultAdminCredentialsVerifierImpl underTest = new DefaultAdminCredentialsVerifierImpl(db.getDbClient(), localAuthentication, notificationManager);
+
+ @After
+ public void after() {
+ underTest.stop();
+ }
+
+ @Test
+ public void correctly_detect_if_admin_account_is_used_with_default_credential() {
+ UserDto admin = db.users().insertUser(u -> u.setLogin(ADMIN_LOGIN));
+ changePassword(admin, "admin");
+ assertThat(underTest.hasDefaultCredentialUser()).isTrue();
+
+ changePassword(admin, "1234");
+ assertThat(underTest.hasDefaultCredentialUser()).isFalse();
+ }
+
+ @Test
+ public void does_not_break_if_admin_account_does_not_exist() {
+ assertThat(underTest.hasDefaultCredentialUser()).isFalse();
+ }
+
+ @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)).contains("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.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();
- }
-}
import org.sonar.server.almsettings.MultipleAlmFeatureProvider;
import org.sonar.server.almsettings.ws.AlmSettingsWsModule;
import org.sonar.server.authentication.AuthenticationModule;
+import org.sonar.server.authentication.DefaultAdminCredentialsVerifierImpl;
import org.sonar.server.authentication.DefaultAdminCredentialsVerifierNotificationHandler;
import org.sonar.server.authentication.DefaultAdminCredentialsVerifierNotificationTemplate;
import org.sonar.server.authentication.LogOAuthWarning;
GitLabModule.class,
LdapModule.class,
SamlModule.class,
+ DefaultAdminCredentialsVerifierImpl.class,
DefaultAdminCredentialsVerifierNotificationTemplate.class,
DefaultAdminCredentialsVerifierNotificationHandler.class,
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.authentication.DefaultAdminCredentialsVerifierImpl;
import org.sonar.server.ce.queue.CeQueueCleaner;
import org.sonar.server.es.IndexerStartupTask;
import org.sonar.server.platform.ServerLifecycleNotifier;
RenameDeprecatedPropertyKeys.class,
CeQueueCleaner.class,
UpgradeSuggestionsCleaner.class,
- DefaultAdminCredentialsVerifier.class);
+ DefaultAdminCredentialsVerifierImpl.class);
// RegisterServletFilters makes the WebService engine of Level4 served by the MasterServletFilter, therefore it
// must be started after all the other startup tasks