]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14586 Add a new filter to redirect system administrators to reset admin passwor...
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Thu, 11 Mar 2021 13:49:53 +0000 (14:49 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 18 Mar 2021 20:08:12 +0000 (20:08 +0000)
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/AuthenticationModule.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifier.java
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierFilter.java [new file with mode: 0644]
server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierImpl.java [new file with mode: 0644]
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierFilterTest.java [new file with mode: 0644]
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierImplTest.java [new file with mode: 0644]
server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierTest.java [deleted file]
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelStartup.java

index b70aad1de24943301a5a36706d06acea6174c9c8..fffbaeb30642dd10bb95eae353c753f5e533c94f 100644 (file)
@@ -34,6 +34,7 @@ public class AuthenticationModule extends Module {
       CredentialsAuthentication.class,
       CredentialsExternalAuthentication.class,
       CredentialsLocalAuthentication.class,
+      DefaultAdminCredentialsVerifierFilter.class,
       HttpHeadersAuthentication.class,
       IdentityProviderRepository.class,
       InitFilter.class,
index dd8aeb372c9f88fad7c63e1572f08c3b6ef13b33..85927a4ab8d268a389ba65747f7600fa456e126a 100644 (file)
  */
 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
-  }
 }
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierFilter.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierFilter.java
new file mode 100644 (file)
index 0000000..ed26219
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * 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
+  }
+}
diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierImpl.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierImpl.java
new file mode 100644 (file)
index 0000000..b7a9905
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * 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
+  }
+}
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierFilterTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierFilterTest.java
new file mode 100644 (file)
index 0000000..f154ed9
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * 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"},
+    };
+  }
+
+}
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierImplTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierImplTest.java
new file mode 100644 (file)
index 0000000..df125fd
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ * 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();
+  }
+}
diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/DefaultAdminCredentialsVerifierTest.java
deleted file mode 100644 (file)
index a307fa0..0000000
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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();
-  }
-}
index c3d821a83537b5d4e0ad9068b98732cf88b8b47c..502f560c36610f1232d442ec3b3b8e380debc957 100644 (file)
@@ -52,6 +52,7 @@ import org.sonar.server.almintegration.ws.ImportHelper;
 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;
@@ -354,6 +355,7 @@ public class PlatformLevel4 extends PlatformLevel {
       GitLabModule.class,
       LdapModule.class,
       SamlModule.class,
+      DefaultAdminCredentialsVerifierImpl.class,
       DefaultAdminCredentialsVerifierNotificationTemplate.class,
       DefaultAdminCredentialsVerifierNotificationHandler.class,
 
index e979b80816310fa91d480a9ee8c18d3f9e833d5a..a0dc5b806464ae9e8a82c140b947303b26f8c1ba 100644 (file)
@@ -23,7 +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.authentication.DefaultAdminCredentialsVerifierImpl;
 import org.sonar.server.ce.queue.CeQueueCleaner;
 import org.sonar.server.es.IndexerStartupTask;
 import org.sonar.server.platform.ServerLifecycleNotifier;
@@ -72,7 +72,7 @@ public class PlatformLevelStartup extends PlatformLevel {
       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