diff options
author | Steve Marion <steve.marion@sonarsource.com> | 2022-09-20 17:05:22 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-09-22 20:03:32 +0000 |
commit | 02fed618e278701358310cac4018ab81f87d5c91 (patch) | |
tree | 309a738234578667ebfef272d1b7df04c2bc932d /server/sonar-webserver-auth | |
parent | c8b5142dfe1f772f212e740cf54dc487cabac83d (diff) | |
download | sonarqube-02fed618e278701358310cac4018ab81f87d5c91.tar.gz sonarqube-02fed618e278701358310cac4018ab81f87d5c91.zip |
SONAR-17296 fix SAML validation page not being accessible with a user session.
Diffstat (limited to 'server/sonar-webserver-auth')
3 files changed, 94 insertions, 6 deletions
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 d28daf1b72a..9a76357e5f6 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 @@ -20,13 +20,19 @@ package org.sonar.server.authentication; +import com.google.common.io.Resources; import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; 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.apache.commons.lang.StringUtils; +import org.sonar.api.internal.apachecommons.lang.StringEscapeUtils; import org.sonar.api.platform.Server; import org.sonar.api.web.ServletFilter; @@ -36,6 +42,7 @@ public class SamlValidationRedirectionFilter extends ServletFilter { public static final String VALIDATION_RELAY_STATE = "validation-query"; public static final String SAML_VALIDATION_URL = "/saml/validation_callback"; + private String redirectionPageTemplate; private final Server server; public SamlValidationRedirectionFilter(Server server) { @@ -48,12 +55,34 @@ public class SamlValidationRedirectionFilter extends ServletFilter { } @Override + public void init(FilterConfig filterConfig) throws ServletException { + super.init(filterConfig); + this.redirectionPageTemplate = extractTemplate("validation-redirection.html"); + } + + String extractTemplate(String templateLocation) { + try { + URL url = Resources.getResource(templateLocation); + return Resources.toString(url, StandardCharsets.UTF_8); + } catch (IOException | IllegalArgumentException e) { + throw new IllegalStateException("Cannot read the template " + templateLocation, e); + } + } + + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; if (isSamlValidation(httpRequest)) { HttpServletResponse httpResponse = (HttpServletResponse) response; - httpResponse.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT); - httpResponse.setHeader("Location", server.getContextPath() + SAML_VALIDATION_URL); + + String samlResponse = StringEscapeUtils.escapeHtml(request.getParameter("SAMLResponse")); + + String template = StringUtils.replaceEachRepeatedly(redirectionPageTemplate, + new String[]{"%VALIDATION_URL%", "%SAML_RESPONSE%"}, + new String[]{server.getContextPath() + SAML_VALIDATION_URL, samlResponse}); + + httpResponse.setContentType("text/html"); + httpResponse.getWriter().print(template); return; } chain.doFilter(request, response); diff --git a/server/sonar-webserver-auth/src/main/resources/validation-redirection.html b/server/sonar-webserver-auth/src/main/resources/validation-redirection.html new file mode 100644 index 00000000000..df9a48a29d6 --- /dev/null +++ b/server/sonar-webserver-auth/src/main/resources/validation-redirection.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta name="application-name" content="SonarQube"/> + <meta http-equiv="content-type" content="text/html; charset=UTF-8" charset="UTF-8"/> + <title>SAML Authentication Test</title> +</head> + +<body> + <h1>SAML Authentication Validation</h1> + <form id="saml_validate" action="%VALIDATION_URL%" method="POST"> + <input name="SAMLResponse" value="%SAML_RESPONSE%" type="hidden"/> + <button>Click Here to See Result</button> + </form> +</body> +<script> + document.getElementById("saml_validate").submit() +</script> +</html> 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 ba54dddc33a..5dc41eea3cb 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 @@ -21,29 +21,36 @@ package org.sonar.server.authentication; import java.io.IOException; +import java.io.PrintWriter; import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.sonar.api.platform.Server; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.matches; import static org.mockito.Mockito.doReturn; 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 SamlValidationRedirectionFilterTest { SamlValidationRedirectionFilter underTest; @Before - public void setup() { + public void setup() throws ServletException { Server server = mock(Server.class); doReturn("").when(server).getContextPath(); underTest = new SamlValidationRedirectionFilter(server); + underTest.init(mock(FilterConfig.class)); } @Test @@ -60,11 +67,39 @@ public class SamlValidationRedirectionFilterTest { HttpServletResponse servletResponse = mock(HttpServletResponse.class); FilterChain filterChain = mock(FilterChain.class); - doReturn("validation-query").when(servletRequest).getParameter("RelayState"); + String validSample = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + when(servletRequest.getParameter(matches("SAMLResponse"))).thenReturn(validSample); + when(servletRequest.getParameter(matches("RelayState"))).thenReturn("validation-query"); + PrintWriter pw = mock(PrintWriter.class); + when(servletResponse.getWriter()).thenReturn(pw); + underTest.doFilter(servletRequest, servletResponse, filterChain); - verify(servletResponse).setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT); - verify(servletResponse).setHeader("Location", "/saml/validation_callback"); + verify(servletResponse).setContentType("text/html"); + ArgumentCaptor<String> htmlProduced = ArgumentCaptor.forClass(String.class); + verify(pw).print(htmlProduced.capture()); + assertThat(htmlProduced.getValue()).contains(validSample); + } + + @Test + public void do_filter_validation_wrong_SAML_response() throws ServletException, IOException { + HttpServletRequest servletRequest = mock(HttpServletRequest.class); + HttpServletResponse servletResponse = mock(HttpServletResponse.class); + FilterChain filterChain = mock(FilterChain.class); + + String maliciousSaml = "test\"</input><script>/*hack website*/</script><input value=\""; + + when(servletRequest.getParameter(matches("SAMLResponse"))).thenReturn(maliciousSaml); + when(servletRequest.getParameter(matches("RelayState"))).thenReturn("validation-query"); + PrintWriter pw = mock(PrintWriter.class); + when(servletResponse.getWriter()).thenReturn(pw); + + underTest.doFilter(servletRequest, servletResponse, filterChain); + + verify(servletResponse).setContentType("text/html"); + ArgumentCaptor<String> htmlProduced = ArgumentCaptor.forClass(String.class); + verify(pw).print(htmlProduced.capture()); + assertThat(htmlProduced.getValue()).doesNotContain("<script>/*hack website*/</script>"); } @Test @@ -78,4 +113,9 @@ public class SamlValidationRedirectionFilterTest { verifyNoInteractions(servletResponse); } + + @Test + public void extract_nonexistant_template() { + assertThrows(IllegalStateException.class, () -> underTest.extractTemplate("not-there")); + } } |