@@ -34,6 +34,7 @@ public class AuthenticationModule extends Module { | |||
CredentialsAuthentication.class, | |||
CredentialsExternalAuthentication.class, | |||
CredentialsLocalAuthentication.class, | |||
DefaultAdminCredentialsVerifierFilter.class, | |||
HttpHeadersAuthentication.class, | |||
IdentityProviderRepository.class, | |||
InitFilter.class, |
@@ -19,80 +19,8 @@ | |||
*/ | |||
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 | |||
} | |||
} |
@@ -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 | |||
} | |||
} |
@@ -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 | |||
} | |||
} |
@@ -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"}, | |||
}; | |||
} | |||
} |
@@ -36,7 +36,7 @@ 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 { | |||
public class DefaultAdminCredentialsVerifierImplTest { | |||
private static final String ADMIN_LOGIN = "admin"; | |||
@@ -48,13 +48,28 @@ public class DefaultAdminCredentialsVerifierTest { | |||
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); | |||
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)); | |||
@@ -64,7 +79,7 @@ public class DefaultAdminCredentialsVerifierTest { | |||
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"); | |||
assertThat(db.getDbClient().internalPropertiesDao().selectByKey(db.getSession(), DEFAULT_ADMIN_CREDENTIAL_USAGE_EMAIL)).contains("true"); | |||
verify(notificationManager).scheduleForSending(any(Notification.class)); | |||
} | |||
@@ -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, | |||
@@ -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 |