diff options
author | Dimitris Kavvathas <dimitris.kavvathas@sonarsource.com> | 2022-09-14 15:21:47 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-09-15 20:03:03 +0000 |
commit | 01350017dd20b13f0b1f3c82325d1426afeb95b2 (patch) | |
tree | 034f6ad863bfd38e0b589aa5de693168ab2af8dd /server/sonar-webserver-webapi | |
parent | 37226c00747bddecc291f56ec1b208984527931a (diff) | |
download | sonarqube-01350017dd20b13f0b1f3c82325d1426afeb95b2.tar.gz sonarqube-01350017dd20b13f0b1f3c82325d1426afeb95b2.zip |
SONAR-17296 Add SamlValidationRedirectionFilter and SamlValidationCallbackFilter to show SAML validation status page.
Diffstat (limited to 'server/sonar-webserver-webapi')
4 files changed, 181 insertions, 7 deletions
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 new file mode 100644 index 00000000000..439a1b312ca --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationCallbackFilter.java @@ -0,0 +1,76 @@ +/* + * 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/SamlValidationModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/saml/ws/SamlValidationModule.java index 2baff89a489..602ed55959a 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,7 +26,8 @@ public class SamlValidationModule extends Module { protected void configureModule() { add( SamlValidationWs.class, - SamlValidationInitAction.class + SamlValidationInitAction.class, + SamlValidationCallbackFilter.class ); } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java index d0bc2bd9e8a..704fe8f7571 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java @@ -87,7 +87,7 @@ public class QProfileBackuperImplTest { StringWriter writer = new StringWriter(); underTest.backup(db.getSession(), profile, writer); - assertThat(writer).hasToString("<?xml version='1.0' encoding='UTF-8'?>" + + assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<profile><name>" + profile.getName() + "</name>" + "<language>" + profile.getLanguage() + "</language>" + "<rules>" + @@ -96,7 +96,7 @@ public class QProfileBackuperImplTest { "<key>" + rule.getRuleKey() + "</key>" + "<type>" + RuleType.valueOf(rule.getType()).name() + "</type>" + "<priority>" + activeRule.getSeverityString() + "</priority>" + - "<parameters/>" + + "<parameters></parameters>" + "</rule>" + "</rules>" + "</profile>"); @@ -133,10 +133,10 @@ public class QProfileBackuperImplTest { StringWriter writer = new StringWriter(); underTest.backup(db.getSession(), profile, writer); - assertThat(writer).hasToString("<?xml version='1.0' encoding='UTF-8'?>" + + assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<profile><name>" + profile.getName() + "</name>" + "<language>" + profile.getLanguage() + "</language>" + - "<rules/>" + + "<rules></rules>" + "</profile>"); } @@ -156,7 +156,7 @@ public class QProfileBackuperImplTest { StringWriter writer = new StringWriter(); underTest.backup(db.getSession(), profile, writer); - assertThat(writer).hasToString("<?xml version='1.0' encoding='UTF-8'?>" + + assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<profile>" + "<name>" + profile.getName() + "</name>" + "<language>" + profile.getLanguage() + "</language>" + @@ -186,7 +186,7 @@ public class QProfileBackuperImplTest { StringWriter writer = new StringWriter(); underTest.backup(db.getSession(), profile, writer); - assertThat(writer).hasToString("<?xml version='1.0' encoding='UTF-8'?>" + + assertThat(writer).hasToString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<profile>" + "<name>" + profile.getName() + "</name>" + "<language>" + profile.getLanguage() + "</language>" + 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 new file mode 100644 index 00000000000..27fd9764302 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/saml/ws/SamlValidationCallbackFilterTest.java @@ -0,0 +1,97 @@ +/* + * 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); + } +} |