From c54b7df81f34bd61265adb1e22fad6d53533367b Mon Sep 17 00:00:00 2001 From: Lukasz Jarocki Date: Fri, 9 Apr 2021 15:00:09 +0200 Subject: [PATCH] SONAR-14662 Redirecting admin to the risk consent page --- .../plugins/PluginsRiskConsentFilter.java | 93 +++++++++ .../plugins/PluginsRiskConsentFilterTest.java | 176 ++++++++++++++++++ .../platformlevel/PlatformLevel4.java | 7 +- 3 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 server/sonar-webserver-core/src/main/java/org/sonar/server/plugins/PluginsRiskConsentFilter.java create mode 100644 server/sonar-webserver-core/src/test/java/org/sonar/server/plugins/PluginsRiskConsentFilterTest.java diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/plugins/PluginsRiskConsentFilter.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/plugins/PluginsRiskConsentFilter.java new file mode 100644 index 00000000000..998c932f07a --- /dev/null +++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/plugins/PluginsRiskConsentFilter.java @@ -0,0 +1,93 @@ +/* + * 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.plugins; + +import com.google.common.collect.ImmutableSet; +import org.sonar.api.config.Configuration; +import org.sonar.api.web.ServletFilter; +import org.sonar.core.extension.PluginRiskConsent; +import org.sonar.server.user.ThreadLocalUserSession; + +import static org.sonar.api.web.ServletFilter.UrlPattern.Builder.staticResourcePatterns; +import static org.sonar.core.config.CorePropertyDefinitions.PLUGINS_RISK_CONSENT; +import static org.sonar.core.extension.PluginRiskConsent.NOT_ACCEPTED; +import static org.sonar.core.extension.PluginRiskConsent.REQUIRED; +import static org.sonar.server.authentication.AuthenticationRedirection.redirectTo; +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 java.io.IOException; +import java.util.Set; + +public class PluginsRiskConsentFilter extends ServletFilter { + + private static final String PLUGINS_RISK_CONSENT_PATH = "/admin/plugin_risk_consent"; //NOSONAR this path will be the same in every environment + + private static final Set SKIPPED_URLS = ImmutableSet.of( + PLUGINS_RISK_CONSENT_PATH, + "/account/reset_password", + "/admin/change_admin_password", + "/batch/*", "/api/*"); + private final ThreadLocalUserSession userSession; + private final Configuration config; + + public PluginsRiskConsentFilter(Configuration config, ThreadLocalUserSession userSession) { + this.userSession = userSession; + this.config = config; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + //nothing to do + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + PluginRiskConsent riskConsent = PluginRiskConsent.valueOf(config.get(PLUGINS_RISK_CONSENT).orElse(NOT_ACCEPTED.name())); + + if (userSession.hasSession() && userSession.isLoggedIn() + && userSession.isSystemAdministrator() && riskConsent == REQUIRED) { + redirectTo(response, request.getContextPath() + PLUGINS_RISK_CONSENT_PATH); + } + + chain.doFilter(request, response); + } + + @Override + public UrlPattern doGetPattern() { + return UrlPattern.builder() + .includes("/*") + .excludes(staticResourcePatterns()) + .excludes(SKIPPED_URLS) + .build(); + } + + @Override + public void destroy() { + //nothing to do + } +} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/plugins/PluginsRiskConsentFilterTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/plugins/PluginsRiskConsentFilterTest.java new file mode 100644 index 00000000000..523f65ce9fb --- /dev/null +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/plugins/PluginsRiskConsentFilterTest.java @@ -0,0 +1,176 @@ +/* + * 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.plugins; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.sonar.api.config.Configuration; +import org.sonar.api.web.ServletFilter; +import org.sonar.core.extension.PluginRiskConsent; +import org.sonar.server.plugins.PluginsRiskConsentFilter; +import org.sonar.server.user.ThreadLocalUserSession; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.sonar.core.config.CorePropertyDefinitions.PLUGINS_RISK_CONSENT; +import javax.servlet.FilterChain; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Optional; + +public class PluginsRiskConsentFilterTest { + + private Configuration configuration; + private ThreadLocalUserSession userSession; + + private ServletRequest servletRequest; + private HttpServletResponse servletResponse; + private FilterChain chain; + + @Before + public void before() { + configuration = mock(Configuration.class); + when(configuration.get(PLUGINS_RISK_CONSENT)).thenReturn(Optional.of(PluginRiskConsent.REQUIRED.name())); + userSession = mock(ThreadLocalUserSession.class); + + servletRequest = mock(HttpServletRequest.class); + servletResponse = mock(HttpServletResponse.class); + chain = mock(FilterChain.class); + } + + @Test + public void doFilter_givenNoUserSession_dontRedirect() throws Exception { + PluginsRiskConsentFilter consentFilter = new PluginsRiskConsentFilter(configuration, userSession); + + when(userSession.hasSession()).thenReturn(true); + + consentFilter.doFilter(servletRequest, servletResponse, chain); + + verify(servletResponse, times(0)).sendRedirect(Mockito.anyString()); + } + + @Test + public void doFilter_givenNotLoggedIn_dontRedirect() throws Exception { + PluginsRiskConsentFilter consentFilter = new PluginsRiskConsentFilter(configuration, userSession); + + when(userSession.hasSession()).thenReturn(true); + when(userSession.isLoggedIn()).thenReturn(false); + + consentFilter.doFilter(servletRequest, servletResponse, chain); + + verify(servletResponse, times(0)).sendRedirect(Mockito.anyString()); + } + + @Test + public void doFilter_givenNotLoggedInAndRequired_dontRedirect() throws Exception { + PluginsRiskConsentFilter consentFilter = new PluginsRiskConsentFilter(configuration, userSession); + + when(userSession.hasSession()).thenReturn(true); + when(userSession.isLoggedIn()).thenReturn(false); + when(configuration.get(PLUGINS_RISK_CONSENT)).thenReturn(Optional.of(PluginRiskConsent.REQUIRED.name())); + + consentFilter.doFilter(servletRequest, servletResponse, chain); + + verify(servletResponse, times(0)).sendRedirect(Mockito.anyString()); + } + + @Test + public void doFilter_givenNotLoggedInAndConsentAccepted_dontRedirect() throws Exception { + PluginsRiskConsentFilter consentFilter = new PluginsRiskConsentFilter(configuration, userSession); + + when(userSession.hasSession()).thenReturn(true); + when(userSession.isLoggedIn()).thenReturn(false); + when(configuration.get(PLUGINS_RISK_CONSENT)).thenReturn(Optional.of(PluginRiskConsent.ACCEPTED.name())); + + consentFilter.doFilter(servletRequest, servletResponse, chain); + + verify(servletResponse, times(0)).sendRedirect(Mockito.anyString()); + } + + @Test + public void doFilter_givenLoggedInNotAdmin_dontRedirect() throws Exception { + PluginsRiskConsentFilter consentFilter = new PluginsRiskConsentFilter(configuration, userSession); + + when(userSession.hasSession()).thenReturn(true); + when(userSession.isLoggedIn()).thenReturn(true); + when(userSession.isSystemAdministrator()).thenReturn(false); + + consentFilter.doFilter(servletRequest, servletResponse, chain); + + verify(servletResponse, times(0)).sendRedirect(Mockito.anyString()); + } + + @Test + public void doFilter_givenLoggedInNotAdminAndRequiredConsent_dontRedirect() throws Exception { + PluginsRiskConsentFilter consentFilter = new PluginsRiskConsentFilter(configuration, userSession); + + when(userSession.hasSession()).thenReturn(true); + when(userSession.isLoggedIn()).thenReturn(true); + when(userSession.isSystemAdministrator()).thenReturn(false); + when(configuration.get(PLUGINS_RISK_CONSENT)).thenReturn(Optional.of(PluginRiskConsent.REQUIRED.name())); + + consentFilter.doFilter(servletRequest, servletResponse, chain); + + verify(servletResponse, times(0)).sendRedirect(Mockito.anyString()); + } + + @Test + public void doFilter_givenLoggedInAdminAndConsentRequired_redirect() throws Exception { + PluginsRiskConsentFilter consentFilter = new PluginsRiskConsentFilter(configuration, userSession); + + when(userSession.hasSession()).thenReturn(true); + when(userSession.isLoggedIn()).thenReturn(true); + when(userSession.isSystemAdministrator()).thenReturn(true); + + consentFilter.doFilter(servletRequest, servletResponse, chain); + + verify(servletResponse, times(1)).sendRedirect(Mockito.anyString()); + } + + @Test + public void doFilter_givenLoggedInAdminAndConsentNotRequired_dontRedirect() throws Exception { + PluginsRiskConsentFilter consentFilter = new PluginsRiskConsentFilter(configuration, userSession); + + when(userSession.hasSession()).thenReturn(true); + when(userSession.isLoggedIn()).thenReturn(true); + when(userSession.isSystemAdministrator()).thenReturn(true); + when(configuration.get(PLUGINS_RISK_CONSENT)).thenReturn(Optional.of(PluginRiskConsent.ACCEPTED.name())); + + consentFilter.doFilter(servletRequest, servletResponse, chain); + + verify(servletResponse, times(0)).sendRedirect(Mockito.anyString()); + } + + @Test + public void doGetPattern_excludesNotEmpty() { + PluginsRiskConsentFilter consentFilter = new PluginsRiskConsentFilter(configuration, userSession); + + ServletFilter.UrlPattern urlPattern = consentFilter.doGetPattern(); + + assertThat(urlPattern.getExclusions()).isNotEmpty(); + + } +} diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 502f560c366..0b2cdeb0393 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -19,7 +19,6 @@ */ package org.sonar.server.platform.platformlevel; -import java.util.List; import org.sonar.alm.client.TimeoutConfigurationImpl; import org.sonar.alm.client.azure.AzureDevOpsHttpClient; import org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient; @@ -181,6 +180,7 @@ import org.sonar.server.qualityprofile.QProfileTreeImpl; import org.sonar.server.qualityprofile.RuleActivator; import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; import org.sonar.server.qualityprofile.ws.QProfilesWsModule; +import org.sonar.server.plugins.PluginsRiskConsentFilter; import org.sonar.server.root.ws.RootWsModule; import org.sonar.server.rule.CommonRuleDefinitionsImpl; import org.sonar.server.rule.RuleCreator; @@ -234,6 +234,7 @@ import org.sonar.server.ws.ws.WebServicesWsModule; import static org.sonar.core.extension.CoreExtensionsInstaller.noAdditionalSideFilter; import static org.sonar.core.extension.PlatformLevelPredicates.hasPlatformLevel4OrNone; +import java.util.List; public class PlatformLevel4 extends PlatformLevel { @@ -557,7 +558,9 @@ public class PlatformLevel4 extends PlatformLevel { TelemetryDataLoaderImpl.class, TelemetryDataJsonWriter.class, TelemetryDaemon.class, - TelemetryClient.class + TelemetryClient.class, + + PluginsRiskConsentFilter.class ); -- 2.39.5