From: Steve Marion Date: Tue, 20 Sep 2022 14:31:42 +0000 (+0200) Subject: SONAR-17296 move validation callback under /saml controller alongside init action X-Git-Tag: 9.7.0.61563~193 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=56f847a7e4a6d4adff95b52e1db62c830d804660;p=sonarqube.git SONAR-17296 move validation callback under /saml controller alongside init action --- diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthentication.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthentication.tsx index e0ccf340bfa..f6070383c10 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthentication.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthentication.tsx @@ -49,7 +49,7 @@ interface SamlAuthenticationState { success?: boolean; } -const CONFIG_TEST_PATH = '/api/saml/validation_init'; +const CONFIG_TEST_PATH = '/saml/validation_init'; const SAML_ENABLED_FIELD = 'sonar.auth.saml.enabled'; diff --git a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SamlValidationRedirectionFilter.java b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SamlValidationRedirectionFilter.java index 9a76357e5f6..7d66c9047f0 100644 --- a/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SamlValidationRedirectionFilter.java +++ b/server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/SamlValidationRedirectionFilter.java @@ -22,6 +22,7 @@ package org.sonar.server.authentication; import com.google.common.io.Resources; import java.io.IOException; +import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; import javax.servlet.FilterChain; @@ -41,7 +42,8 @@ import static org.sonar.server.authentication.AuthenticationFilter.CALLBACK_PATH public class SamlValidationRedirectionFilter extends ServletFilter { public static final String VALIDATION_RELAY_STATE = "validation-query"; - public static final String SAML_VALIDATION_URL = "/saml/validation_callback"; + public static final String SAML_VALIDATION_CONTROLLER_CONTEXT = "saml"; + public static final String SAML_VALIDATION_KEY = "validation"; private String redirectionPageTemplate; private final Server server; @@ -76,10 +78,13 @@ public class SamlValidationRedirectionFilter extends ServletFilter { HttpServletResponse httpResponse = (HttpServletResponse) response; String samlResponse = StringEscapeUtils.escapeHtml(request.getParameter("SAMLResponse")); + URI redirectionEndpointUrl = URI.create(server.getContextPath() + "/") + .resolve(SAML_VALIDATION_CONTROLLER_CONTEXT + "/") + .resolve(SAML_VALIDATION_KEY); String template = StringUtils.replaceEachRepeatedly(redirectionPageTemplate, new String[]{"%VALIDATION_URL%", "%SAML_RESPONSE%"}, - new String[]{server.getContextPath() + SAML_VALIDATION_URL, samlResponse}); + new String[]{redirectionEndpointUrl.toString(), samlResponse}); httpResponse.setContentType("text/html"); httpResponse.getWriter().print(template); diff --git a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SamlValidationRedirectionFilterTest.java b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SamlValidationRedirectionFilterTest.java index 5dc41eea3cb..b27c68a5f1f 100644 --- a/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SamlValidationRedirectionFilterTest.java +++ b/server/sonar-webserver-auth/src/test/java/org/sonar/server/authentication/SamlValidationRedirectionFilterTest.java @@ -100,6 +100,7 @@ public class SamlValidationRedirectionFilterTest { ArgumentCaptor htmlProduced = ArgumentCaptor.forClass(String.class); verify(pw).print(htmlProduced.capture()); assertThat(htmlProduced.getValue()).doesNotContain(""); + assertThat(htmlProduced.getValue()).contains("action=\"/saml/validation\""); } @Test diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationCallbackFilter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationCallbackFilter.java deleted file mode 100644 index 439a1b312ca..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationCallbackFilter.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.saml.ws; - -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; -import javax.servlet.http.HttpServletResponse; -import org.sonar.api.web.ServletFilter; -import org.sonar.auth.saml.SamlAuthenticator; -import org.sonar.auth.saml.SamlIdentityProvider; -import org.sonar.server.authentication.AuthenticationError; -import org.sonar.server.authentication.OAuth2ContextFactory; -import org.sonar.server.user.ThreadLocalUserSession; - -import static org.sonar.server.authentication.SamlValidationRedirectionFilter.SAML_VALIDATION_URL; - -public class SamlValidationCallbackFilter extends ServletFilter { - - private final ThreadLocalUserSession userSession; - private final SamlAuthenticator samlAuthenticator; - private final OAuth2ContextFactory oAuth2ContextFactory; - - public SamlValidationCallbackFilter(ThreadLocalUserSession userSession, SamlAuthenticator samlAuthenticator, OAuth2ContextFactory oAuth2ContextFactory) { - this.samlAuthenticator = samlAuthenticator; - this.userSession = userSession; - this.oAuth2ContextFactory = oAuth2ContextFactory; - } - - @Override - public UrlPattern doGetPattern() { - return UrlPattern.create(SAML_VALIDATION_URL); - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - HttpServletResponse httpResponse = (HttpServletResponse) response; - HttpServletRequest httpRequest = (HttpServletRequest) request; - if (!userSession.hasSession() || !userSession.isSystemAdministrator()) { - AuthenticationError.handleError(httpRequest, httpResponse, "User needs to be logged in as system administrator to access this page."); - return; - } - - httpRequest = new HttpServletRequestWrapper(httpRequest) { - @Override - public StringBuffer getRequestURL() { - return new StringBuffer(oAuth2ContextFactory.generateCallbackUrl(SamlIdentityProvider.KEY)); - } - }; - - httpResponse.setContentType("text/html"); - httpResponse.getWriter().print(samlAuthenticator.getAuthenticationStatusPage(httpRequest, httpResponse)); - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationInitAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationInitAction.java deleted file mode 100644 index 6837be75755..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationInitAction.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.saml.ws; - -import java.io.IOException; -import javax.servlet.FilterChain; -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.server.ws.WebService; -import org.sonar.api.web.ServletFilter; -import org.sonar.auth.saml.SamlAuthenticator; -import org.sonar.auth.saml.SamlIdentityProvider; -import org.sonar.server.authentication.AuthenticationError; -import org.sonar.server.authentication.OAuth2ContextFactory; -import org.sonar.server.exceptions.ForbiddenException; -import org.sonar.server.user.UserSession; -import org.sonar.server.ws.ServletFilterHandler; - -public class SamlValidationInitAction extends ServletFilter implements SamlAction { - - public static final String VALIDATION_RELAY_STATE = "validation-query"; - private final SamlAuthenticator samlAuthenticator; - - private final OAuth2ContextFactory oAuth2ContextFactory; - private final UserSession userSession; - - public SamlValidationInitAction(SamlAuthenticator samlAuthenticator, OAuth2ContextFactory oAuth2ContextFactory, UserSession userSession) { - this.samlAuthenticator = samlAuthenticator; - this.oAuth2ContextFactory = oAuth2ContextFactory; - this.userSession = userSession; - } - - @Override - public UrlPattern doGetPattern() { - return UrlPattern.create("/api/saml/validation_init"); - } - - @Override - public void define(WebService.NewController controller) { - controller - .createAction("validation_init") - .setInternal(true) - .setPost(false) - .setHandler(ServletFilterHandler.INSTANCE) - .setDescription("Initiate a SAML request to the identity Provider for configuration validation purpose.") - .setSince("9.7"); - } - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest) servletRequest; - HttpServletResponse response = (HttpServletResponse) servletResponse; - - try { - userSession.checkIsSystemAdministrator(); - } catch (ForbiddenException e) { - AuthenticationError.handleError(request, response, "User needs to be logged in as system administrator to access this page."); - return; - } - - samlAuthenticator.initLogin(oAuth2ContextFactory.generateCallbackUrl(SamlIdentityProvider.KEY), - VALIDATION_RELAY_STATE, request, response); - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationModule.java index 602ed55959a..ce51a4a06fc 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationModule.java @@ -26,8 +26,8 @@ public class SamlValidationModule extends Module { protected void configureModule() { add( SamlValidationWs.class, - SamlValidationInitAction.class, - SamlValidationCallbackFilter.class + ValidationInitAction.class, + ValidationAction.class ); } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationWs.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationWs.java index 52529579be9..198e24d614b 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationWs.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationWs.java @@ -21,10 +21,11 @@ package org.sonar.server.saml.ws; import java.util.List; import org.sonar.api.server.ws.WebService; +import org.sonar.server.authentication.SamlValidationRedirectionFilter; public class SamlValidationWs implements WebService { - public static final String SAML_VALIDATION_CONTROLLER = "api/saml"; + public static final String SAML_VALIDATION_CONTROLLER = SamlValidationRedirectionFilter.SAML_VALIDATION_CONTROLLER_CONTEXT; private final List actions; public SamlValidationWs(List actions) { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/ValidationAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/ValidationAction.java new file mode 100644 index 00000000000..4b58458513c --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/ValidationAction.java @@ -0,0 +1,93 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.saml.ws; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.web.ServletFilter; +import org.sonar.auth.saml.SamlAuthenticator; +import org.sonar.auth.saml.SamlIdentityProvider; +import org.sonar.server.authentication.AuthenticationError; +import org.sonar.server.authentication.OAuth2ContextFactory; +import org.sonar.server.authentication.SamlValidationRedirectionFilter; +import org.sonar.server.user.ThreadLocalUserSession; +import org.sonar.server.ws.ServletFilterHandler; + +public class ValidationAction extends ServletFilter implements SamlAction { + + static final String VALIDATION_CALLBACK_KEY = SamlValidationRedirectionFilter.SAML_VALIDATION_KEY; + private final ThreadLocalUserSession userSession; + private final SamlAuthenticator samlAuthenticator; + private final OAuth2ContextFactory oAuth2ContextFactory; + + public ValidationAction(ThreadLocalUserSession userSession, SamlAuthenticator samlAuthenticator, OAuth2ContextFactory oAuth2ContextFactory) { + this.samlAuthenticator = samlAuthenticator; + this.userSession = userSession; + this.oAuth2ContextFactory = oAuth2ContextFactory; + } + + @Override + public UrlPattern doGetPattern() { + return UrlPattern.create("/" + SamlValidationWs.SAML_VALIDATION_CONTROLLER + "/" + VALIDATION_CALLBACK_KEY); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletResponse httpResponse = (HttpServletResponse) response; + HttpServletRequest httpRequest = (HttpServletRequest) request; + if (!userSession.hasSession() || !userSession.isSystemAdministrator()) { + AuthenticationError.handleError(httpRequest, httpResponse, "User needs to be logged in as system administrator to access this page."); + return; + } + + httpRequest = new HttpServletRequestWrapper(httpRequest) { + @Override + public StringBuffer getRequestURL() { + return new StringBuffer(oAuth2ContextFactory.generateCallbackUrl(SamlIdentityProvider.KEY)); + } + }; + + httpResponse.setContentType("text/html"); + httpResponse.getWriter().print(samlAuthenticator.getAuthenticationStatusPage(httpRequest, httpResponse)); + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller + .createAction(VALIDATION_CALLBACK_KEY) + .setInternal(true) + .setPost(true) + .setHandler(ServletFilterHandler.INSTANCE) + .setDescription("Handle the callback of a SAML assertion from the identity Provider and produces " + + "a HTML page with all information available in the assertion.") + .setSince("9.7"); + action.createParam("SAMLResponse") + .setDescription("SAML assertion value") + .setRequired(true); + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/ValidationInitAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/ValidationInitAction.java new file mode 100644 index 00000000000..f882827cd07 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/ValidationInitAction.java @@ -0,0 +1,85 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.saml.ws; + +import java.io.IOException; +import javax.servlet.FilterChain; +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.server.ws.WebService; +import org.sonar.api.web.ServletFilter; +import org.sonar.auth.saml.SamlAuthenticator; +import org.sonar.auth.saml.SamlIdentityProvider; +import org.sonar.server.authentication.AuthenticationError; +import org.sonar.server.authentication.OAuth2ContextFactory; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.user.UserSession; +import org.sonar.server.ws.ServletFilterHandler; + +public class ValidationInitAction extends ServletFilter implements SamlAction { + + public static final String VALIDATION_RELAY_STATE = "validation-query"; + public static final String VALIDATION_INIT_KEY = "validation_init"; + private final SamlAuthenticator samlAuthenticator; + + private final OAuth2ContextFactory oAuth2ContextFactory; + private final UserSession userSession; + + public ValidationInitAction(SamlAuthenticator samlAuthenticator, OAuth2ContextFactory oAuth2ContextFactory, UserSession userSession) { + this.samlAuthenticator = samlAuthenticator; + this.oAuth2ContextFactory = oAuth2ContextFactory; + this.userSession = userSession; + } + + @Override + public UrlPattern doGetPattern() { + return UrlPattern.create("/" + SamlValidationWs.SAML_VALIDATION_CONTROLLER + "/" + VALIDATION_INIT_KEY); + } + + @Override + public void define(WebService.NewController controller) { + controller + .createAction(VALIDATION_INIT_KEY) + .setInternal(true) + .setPost(false) + .setHandler(ServletFilterHandler.INSTANCE) + .setDescription("Initiate a SAML request to the identity Provider for configuration validation purpose.") + .setSince("9.7"); + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + + try { + userSession.checkIsSystemAdministrator(); + } catch (ForbiddenException e) { + AuthenticationError.handleError(request, response, "User needs to be logged in as system administrator to access this page."); + return; + } + + samlAuthenticator.initLogin(oAuth2ContextFactory.generateCallbackUrl(SamlIdentityProvider.KEY), + VALIDATION_RELAY_STATE, request, response); + } +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/SamlValidationCallbackFilterTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/SamlValidationCallbackFilterTest.java deleted file mode 100644 index 27fd9764302..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/SamlValidationCallbackFilterTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.saml.ws; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Before; -import org.junit.Test; -import org.sonar.auth.saml.SamlAuthenticator; -import org.sonar.server.authentication.OAuth2ContextFactory; -import org.sonar.server.user.ThreadLocalUserSession; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -public class SamlValidationCallbackFilterTest { - - private SamlValidationCallbackFilter underTest; - private SamlAuthenticator samlAuthenticator; - private ThreadLocalUserSession userSession; - - @Before - public void setup() { - samlAuthenticator = mock(SamlAuthenticator.class); - userSession = mock(ThreadLocalUserSession.class); - var oAuth2ContextFactory = mock(OAuth2ContextFactory.class); - underTest = new SamlValidationCallbackFilter(userSession, samlAuthenticator, oAuth2ContextFactory); - } - - @Test - public void do_get_pattern() { - assertThat(underTest.doGetPattern().matches("/saml/validation_callback")).isTrue(); - assertThat(underTest.doGetPattern().matches("/saml/validation_callback2")).isFalse(); - assertThat(underTest.doGetPattern().matches("/saml/")).isFalse(); - } - - @Test - public void do_filter_admin() throws ServletException, IOException { - HttpServletRequest servletRequest = mock(HttpServletRequest.class); - HttpServletResponse servletResponse = mock(HttpServletResponse.class); - StringWriter stringWriter = new StringWriter(); - doReturn(new PrintWriter(stringWriter)).when(servletResponse).getWriter(); - FilterChain filterChain = mock(FilterChain.class); - - doReturn(true).when(userSession).hasSession(); - doReturn(true).when(userSession).isSystemAdministrator(); - - underTest.doFilter(servletRequest, servletResponse, filterChain); - - verify(samlAuthenticator).getAuthenticationStatusPage(any(), any()); - verify(servletResponse).getWriter(); - } - - @Test - public void do_filter_not_authorized() throws ServletException, IOException { - HttpServletRequest servletRequest = spy(HttpServletRequest.class); - HttpServletResponse servletResponse = mock(HttpServletResponse.class); - StringWriter stringWriter = new StringWriter(); - doReturn(new PrintWriter(stringWriter)).when(servletResponse).getWriter(); - FilterChain filterChain = mock(FilterChain.class); - - doReturn(true).when(userSession).hasSession(); - doReturn(false).when(userSession).isSystemAdministrator(); - - underTest.doFilter(servletRequest, servletResponse, filterChain); - - verifyNoInteractions(samlAuthenticator); - } -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/SamlValidationInitActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/SamlValidationInitActionTest.java deleted file mode 100644 index fde2872433d..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/SamlValidationInitActionTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2022 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.saml.ws; - -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.server.ws.WebService; -import org.sonar.auth.saml.SamlAuthenticator; -import org.sonar.server.authentication.OAuth2ContextFactory; -import org.sonar.server.tester.UserSessionRule; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.matches; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -public class SamlValidationInitActionTest { - @Rule - public UserSessionRule userSession = UserSessionRule.standalone(); - private SamlValidationInitAction underTest; - private SamlAuthenticator samlAuthenticator; - private OAuth2ContextFactory oAuth2ContextFactory; - - @Before - public void setUp() throws Exception { - samlAuthenticator = mock(SamlAuthenticator.class); - oAuth2ContextFactory = mock(OAuth2ContextFactory.class); - underTest = new SamlValidationInitAction(samlAuthenticator, oAuth2ContextFactory, userSession); - } - - @Test - public void do_get_pattern() { - assertThat(underTest.doGetPattern().matches("/api/saml/validation_init")).isTrue(); - assertThat(underTest.doGetPattern().matches("/api/saml")).isFalse(); - assertThat(underTest.doGetPattern().matches("/api/saml/validation_init2")).isFalse(); - } - - @Test - public void do_filter_as_admin() throws IOException, ServletException { - userSession.logIn().setSystemAdministrator(); - HttpServletRequest servletRequest = mock(HttpServletRequest.class); - HttpServletResponse servletResponse = mock(HttpServletResponse.class); - FilterChain filterChain = mock(FilterChain.class); - String callbackUrl = "http://localhost:9000/api/validation_test"; - when(oAuth2ContextFactory.generateCallbackUrl(anyString())) - .thenReturn(callbackUrl); - - underTest.doFilter(servletRequest, servletResponse, filterChain); - - verify(samlAuthenticator).initLogin(matches(callbackUrl), - matches(SamlValidationInitAction.VALIDATION_RELAY_STATE), - any(), any()); - } - - @Test - public void do_filter_as_not_admin() throws IOException, ServletException { - userSession.logIn(); - HttpServletRequest servletRequest = mock(HttpServletRequest.class); - HttpServletResponse servletResponse = mock(HttpServletResponse.class); - FilterChain filterChain = mock(FilterChain.class); - String callbackUrl = "http://localhost:9000/api/validation_test"; - when(oAuth2ContextFactory.generateCallbackUrl(anyString())) - .thenReturn(callbackUrl); - - underTest.doFilter(servletRequest, servletResponse, filterChain); - - verifyNoInteractions(samlAuthenticator); - verify(servletResponse).sendRedirect(anyString()); - } - - @Test - public void do_filter_as_anonymous() throws IOException, ServletException { - userSession.anonymous(); - HttpServletRequest servletRequest = mock(HttpServletRequest.class); - HttpServletResponse servletResponse = mock(HttpServletResponse.class); - FilterChain filterChain = mock(FilterChain.class); - String callbackUrl = "http://localhost:9000/api/validation_test"; - when(oAuth2ContextFactory.generateCallbackUrl(anyString())) - .thenReturn(callbackUrl); - - underTest.doFilter(servletRequest, servletResponse, filterChain); - - verifyNoInteractions(samlAuthenticator); - verify(servletResponse).sendRedirect(anyString()); - } - - @Test - public void verify_definition() { - String controllerKey = "foo"; - WebService.Context context = new WebService.Context(); - WebService.NewController newController = context.createController(controllerKey); - underTest.define(newController); - newController.done(); - - WebService.Action validationInitAction = context.controller(controllerKey).action("validation_init"); - assertThat(validationInitAction).isNotNull(); - assertThat(validationInitAction.description()).isNotEmpty(); - assertThat(validationInitAction.handler()).isNotNull(); - } -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/ValidationActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/ValidationActionTest.java new file mode 100644 index 00000000000..a00371db1d6 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/ValidationActionTest.java @@ -0,0 +1,115 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.saml.ws; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.server.ws.WebService; +import org.sonar.auth.saml.SamlAuthenticator; +import org.sonar.server.authentication.OAuth2ContextFactory; +import org.sonar.server.user.ThreadLocalUserSession; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +public class ValidationActionTest { + + private ValidationAction underTest; + private SamlAuthenticator samlAuthenticator; + private ThreadLocalUserSession userSession; + + @Before + public void setup() { + samlAuthenticator = mock(SamlAuthenticator.class); + userSession = mock(ThreadLocalUserSession.class); + var oAuth2ContextFactory = mock(OAuth2ContextFactory.class); + underTest = new ValidationAction(userSession, samlAuthenticator, oAuth2ContextFactory); + } + + @Test + public void do_get_pattern() { + assertThat(underTest.doGetPattern().matches("/saml/validation")).isTrue(); + assertThat(underTest.doGetPattern().matches("/saml/validation2")).isFalse(); + assertThat(underTest.doGetPattern().matches("/api/saml/validation")).isFalse(); + assertThat(underTest.doGetPattern().matches("/saml/validation_callback2")).isFalse(); + assertThat(underTest.doGetPattern().matches("/saml/")).isFalse(); + } + + @Test + public void do_filter_admin() throws ServletException, IOException { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + StringWriter stringWriter = new StringWriter(); + doReturn(new PrintWriter(stringWriter)).when(servletResponse).getWriter(); + FilterChain filterChain = mock(FilterChain.class); + + doReturn(true).when(userSession).hasSession(); + doReturn(true).when(userSession).isSystemAdministrator(); + + underTest.doFilter(servletRequest, servletResponse, filterChain); + + verify(samlAuthenticator).getAuthenticationStatusPage(any(), any()); + verify(servletResponse).getWriter(); + } + + @Test + public void do_filter_not_authorized() throws ServletException, IOException { + HttpServletRequest servletRequest = spy(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + StringWriter stringWriter = new StringWriter(); + doReturn(new PrintWriter(stringWriter)).when(servletResponse).getWriter(); + FilterChain filterChain = mock(FilterChain.class); + + doReturn(true).when(userSession).hasSession(); + doReturn(false).when(userSession).isSystemAdministrator(); + + underTest.doFilter(servletRequest, servletResponse, filterChain); + + verifyNoInteractions(samlAuthenticator); + } + + @Test + public void verify_definition() { + String controllerKey = "foo"; + WebService.Context context = new WebService.Context(); + WebService.NewController newController = context.createController(controllerKey); + underTest.define(newController); + newController.done(); + + WebService.Action validationInitAction = context.controller(controllerKey) + .action(ValidationAction.VALIDATION_CALLBACK_KEY); + assertThat(validationInitAction).isNotNull(); + assertThat(validationInitAction.description()).isNotEmpty(); + assertThat(validationInitAction.handler()).isNotNull(); + } +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/ValidationInitActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/ValidationInitActionTest.java new file mode 100644 index 00000000000..618dce62ef0 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/ValidationInitActionTest.java @@ -0,0 +1,128 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.saml.ws; + +import java.io.IOException; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.server.ws.WebService; +import org.sonar.auth.saml.SamlAuthenticator; +import org.sonar.server.authentication.OAuth2ContextFactory; +import org.sonar.server.tester.UserSessionRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.matches; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +public class ValidationInitActionTest { + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + private ValidationInitAction underTest; + private SamlAuthenticator samlAuthenticator; + private OAuth2ContextFactory oAuth2ContextFactory; + + @Before + public void setUp() throws Exception { + samlAuthenticator = mock(SamlAuthenticator.class); + oAuth2ContextFactory = mock(OAuth2ContextFactory.class); + underTest = new ValidationInitAction(samlAuthenticator, oAuth2ContextFactory, userSession); + } + + @Test + public void do_get_pattern() { + assertThat(underTest.doGetPattern().matches("/saml/validation_init")).isTrue(); + assertThat(underTest.doGetPattern().matches("/api/saml")).isFalse(); + assertThat(underTest.doGetPattern().matches("/api/saml/validation_init")).isFalse(); + assertThat(underTest.doGetPattern().matches("/saml/validation_init2")).isFalse(); + } + + @Test + public void do_filter_as_admin() throws IOException, ServletException { + userSession.logIn().setSystemAdministrator(); + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + FilterChain filterChain = mock(FilterChain.class); + String callbackUrl = "http://localhost:9000/api/validation_test"; + when(oAuth2ContextFactory.generateCallbackUrl(anyString())) + .thenReturn(callbackUrl); + + underTest.doFilter(servletRequest, servletResponse, filterChain); + + verify(samlAuthenticator).initLogin(matches(callbackUrl), + matches(ValidationInitAction.VALIDATION_RELAY_STATE), + any(), any()); + } + + @Test + public void do_filter_as_not_admin() throws IOException, ServletException { + userSession.logIn(); + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + FilterChain filterChain = mock(FilterChain.class); + String callbackUrl = "http://localhost:9000/api/validation_test"; + when(oAuth2ContextFactory.generateCallbackUrl(anyString())) + .thenReturn(callbackUrl); + + underTest.doFilter(servletRequest, servletResponse, filterChain); + + verifyNoInteractions(samlAuthenticator); + verify(servletResponse).sendRedirect(anyString()); + } + + @Test + public void do_filter_as_anonymous() throws IOException, ServletException { + userSession.anonymous(); + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + FilterChain filterChain = mock(FilterChain.class); + String callbackUrl = "http://localhost:9000/api/validation_test"; + when(oAuth2ContextFactory.generateCallbackUrl(anyString())) + .thenReturn(callbackUrl); + + underTest.doFilter(servletRequest, servletResponse, filterChain); + + verifyNoInteractions(samlAuthenticator); + verify(servletResponse).sendRedirect(anyString()); + } + + @Test + public void verify_definition() { + String controllerKey = "foo"; + WebService.Context context = new WebService.Context(); + WebService.NewController newController = context.createController(controllerKey); + underTest.define(newController); + newController.done(); + + WebService.Action validationInitAction = context.controller(controllerKey).action("validation_init"); + assertThat(validationInitAction).isNotNull(); + assertThat(validationInitAction.description()).isNotEmpty(); + assertThat(validationInitAction.handler()).isNotNull(); + } +}