<div id="response" data-response="%SAML_AUTHENTICATION_STATUS%"></div>
</div>
- <script>
+ <script nonce="%NONCE%">
window.addEventListener('DOMContentLoaded', (event) => {
function createBox() {
*/
package org.sonar.auth.saml;
-import com.google.common.io.Resources;
-import java.io.IOException;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import org.junit.Test;
-import static org.junit.Assert.assertEquals;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.auth.saml.SamlAuthStatusPageGenerator.getSamlAuthStatusHtml;
public class SamlAuthStatusPageGeneratorTest {
- private static final String EMPTY_HTML_TEMPLATE_NAME = "samlAuthResultEmpty.html";
+ private static final String EMPTY_DATA_RESPONSE = "eyJ3YXJuaW5ncyI6W10sImF2YWlsYWJsZUF0dHJpYnV0ZXMiOnt9LCJlcnJvcnMiOltdLCJtYXBwZWRBdHRyaWJ1dGVzIjp7fX0=";
@Test
- public void test_full_html_generation_with_empty_values() {
+ public void getSamlAuthStatusHtml_whenCalled_shouldGeneratePageWithData() {
SamlAuthenticationStatus samlAuthenticationStatus = mock(SamlAuthenticationStatus.class);
HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
when(httpServletRequest.getContextPath()).thenReturn("context");
String completeHtmlTemplate = getSamlAuthStatusHtml(httpServletRequest, samlAuthenticationStatus);
- String expectedTemplate = loadTemplateFromResources(EMPTY_HTML_TEMPLATE_NAME);
- assertEquals(expectedTemplate, completeHtmlTemplate);
-
- }
-
- private String loadTemplateFromResources(String templateName) {
- URL url = Resources.getResource(templateName);
- try {
- return Resources.toString(url, StandardCharsets.UTF_8);
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
+ assertThat(completeHtmlTemplate).contains(EMPTY_DATA_RESPONSE);
}
-
}
+++ /dev/null
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" charset="UTF-8" />
- <meta http-equiv="X-UA-Compatible" content="IE=edge" />
- <link rel="apple-touch-icon" href="context/apple-touch-icon.png" />
- <link rel="apple-touch-icon" sizes="57x57" href="context/apple-touch-icon-57x57.png" />
- <link rel="apple-touch-icon" sizes="60x60" href="context/apple-touch-icon-60x60.png" />
- <link rel="apple-touch-icon" sizes="72x72" href="context/apple-touch-icon-72x72.png" />
- <link rel="apple-touch-icon" sizes="76x76" href="context/apple-touch-icon-76x76.png" />
- <link
- rel="apple-touch-icon"
- sizes="114x114"
- href="context/apple-touch-icon-114x114.png"
- />
- <link
- rel="apple-touch-icon"
- sizes="120x120"
- href="context/apple-touch-icon-120x120.png"
- />
- <link
- rel="apple-touch-icon"
- sizes="144x144"
- href="context/apple-touch-icon-144x144.png"
- />
- <link
- rel="apple-touch-icon"
- sizes="152x152"
- href="context/apple-touch-icon-152x152.png"
- />
- <link
- rel="apple-touch-icon"
- sizes="180x180"
- href="context/apple-touch-icon-180x180.png"
- />
- <link rel="icon" type="image/x-icon" href="context/favicon.ico" />
- <meta name="application-name" content="SonarQube" />
- <meta name="msapplication-TileColor" content="#FFFFFF" />
- <meta name="msapplication-TileImage" content="context/mstile-512x512.png" />
- <title>SAML Authentication Test</title>
-
- <style>
- body {
- background-color: #f3f3f3;
- }
-
- h1 {
- margin: 0 8px 8px;
- }
- h2 {
- margin: 0 0 8px;
- }
-
- ul {
- list-style: none;
- margin: 0 8px;
- padding: 0;
- }
-
- li + li {
- padding-top: 12px;
- margin-top: 12px;
- border-top: 1px solid rgba(150, 150, 150, 0.5);
- }
-
- table {
- border-collapse: collapse;
- }
-
- tr:nth-child(2n) {
- background-color: #e6e6e6;
- }
-
- td {
- border: 1px solid #a3a3a3;
- padding: 4px 24px 4px 8px;
- vertical-align: top;
- font-family: "Courier New", Courier, monospace;
- white-space: pre-line;
- }
-
- #content {
- padding: 16px;
- }
-
- .box {
- padding: 8px;
- margin: 8px;
- border: 1px solid #e6e6e6;
- background-color: white;
- box-shadow: 2px 2px 3px 0px rgba(0, 0, 0, 0.5);
- }
-
- #status {
- padding: 16px 8px;
- font-size: large;
- color: white;
- }
-
- .error {
- background-color: #d02f3a;
- }
-
- .success {
- background-color: #008a25;
- }
- </style>
- </head>
-
- <body>
- <div id="content">
- <h1>SAML Authentication Test</h1>
- <div class="box">
- <div id="status"></div>
- </div>
- <div id="response" data-response="eyJ3YXJuaW5ncyI6W10sImF2YWlsYWJsZUF0dHJpYnV0ZXMiOnt9LCJlcnJvcnMiOltdLCJtYXBwZWRBdHRyaWJ1dGVzIjp7fX0="></div>
- </div>
-
- <script>
- window.addEventListener('DOMContentLoaded', (event) => {
-
- function createBox() {
- const box = document.createElement("div");
- box.className = "box";
- return box;
- }
-
- function createSectionTitle(title) {
- const node = document.createElement("h2");
- node.textContent = title;
- return node;
- }
-
- function createList(arr, className = "") {
- const list = document.createElement("ul");
-
- arr.forEach((item) => {
- const message = document.createElement("li");
- message.className = className;
- message.textContent = item;
- list.appendChild(message);
- });
-
- return list;
- }
-
- function createTable(obj) {
- const table = document.createElement("table");
- const tbody = document.createElement("tbody");
- table.appendChild(tbody);
-
- Object.keys(obj).forEach((key) => {
- const row = document.createElement("tr");
-
- const keyNode = document.createElement("td");
- keyNode.textContent = key;
- row.appendChild(keyNode);
-
- const valueNode = document.createElement("td");
- // wrap in array, to handle single values as well
- valueNode.textContent = [].concat(obj[key]).join("\r\n");
- row.appendChild(valueNode);
-
- tbody.appendChild(row);
- });
-
- return table;
- }
-
- function addSection(container, title, contents) {
- const box = createBox();
-
- box.appendChild(createSectionTitle(title));
- box.appendChild(contents);
-
- container.appendChild(box);
- }
-
- const variables = document.querySelector("#response");
- const response = variables.dataset.response;
- const decodedStatus = JSON.parse(atob(response));
- const status = decodedStatus.status;
- const attributes = decodedStatus.availableAttributes;
- const mappings = decodedStatus.mappedAttributes;
- const errors = decodedStatus.errors;
- const warnings = decodedStatus.warnings;
-
- // Switch status class
- const statusNode = document.querySelector("#status");
- statusNode.classList.add(status);
- statusNode.textContent = status;
-
- // generate content
- const container = document.querySelector("#content");
-
- if (warnings && warnings.length > 0) {
- addSection(container, "Warnings", createList(warnings));
- }
-
- if (status === "error" && errors && errors.length > 0) {
- addSection(container, "Errors", createList(errors));
- }
-
- if (status === "success") {
- if (attributes && Object.keys(attributes).length > 0) {
- addSection(container, "Available attributes", createTable(attributes));
- }
-
- if (mappings && Object.keys(mappings).length > 0) {
- addSection(container, "Attribute mappings", createTable(mappings));
- }
- }
- });
- </script>
- </body>
-</html>
*/
package org.sonar.server.authentication;
-import java.util.Base64;
+import java.math.BigInteger;
+import java.security.SecureRandom;
import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import javax.servlet.http.HttpServletResponse;
-import org.apache.commons.codec.digest.DigestUtils;
public class SamlValidationCspHeaders {
- private static final Pattern SCRIPT_PATTERN = Pattern.compile("(?<=<script>)(?s).*(?=</script>)");
-
private SamlValidationCspHeaders() {
throw new IllegalStateException("Utility class, cannot be instantiated");
}
- public static void addCspHeadersToResponse(HttpServletResponse httpResponse, String hash) {
+ public static String addCspHeadersWithNonceToResponse(HttpServletResponse httpResponse) {
+ final String nonce = getNonce();
+
List<String> cspPolicies = List.of(
"default-src 'self'",
"base-uri 'none'",
"connect-src 'self' http: https:",
"img-src * data: blob:",
"object-src 'none'",
- "script-src 'self' '" + hash + "'",
+ "script-src 'nonce-" + nonce + "'",
"style-src 'self' 'unsafe-inline'",
"worker-src 'none'");
String policies = String.join("; ", cspPolicies).trim();
List<String> cspHeaders = List.of("Content-Security-Policy", "X-Content-Security-Policy", "X-WebKit-CSP");
cspHeaders.forEach(header -> httpResponse.setHeader(header, policies));
+ return nonce;
}
- public static String getHashForInlineScript(String html) {
- Matcher matcher = SCRIPT_PATTERN.matcher(html);
- if (matcher.find()) {
- return getBase64Sha256(matcher.group(0));
- }
- return "";
+ private static String getNonce() {
+ // this code is the same as in org.sonar.server.authentication.JwtCsrfVerifier.generateState
+ return new BigInteger(130, new SecureRandom()).toString(32);
}
-
- private static String getBase64Sha256(String string) {
- return "sha256-" + Base64.getEncoder().encodeToString(DigestUtils.sha256(string));
- }
-
}
import org.sonar.api.web.ServletFilter;
import static org.sonar.server.authentication.AuthenticationFilter.CALLBACK_PATH;
-import static org.sonar.server.authentication.SamlValidationCspHeaders.addCspHeadersToResponse;
-import static org.sonar.server.authentication.SamlValidationCspHeaders.getHashForInlineScript;
public class SamlValidationRedirectionFilter extends ServletFilter {
String samlResponse = StringEscapeUtils.escapeHtml(request.getParameter(SAML_RESPONSE_PARAMETER));
String csrfToken = getCsrfTokenFromRelayState(relayState);
+ String nonce = SamlValidationCspHeaders.addCspHeadersWithNonceToResponse(httpResponse);
+
String template = StringUtils.replaceEachRepeatedly(redirectionPageTemplate,
- new String[]{"%WEB_CONTEXT%", "%VALIDATION_URL%", "%SAML_RESPONSE%", "%CSRF_TOKEN%"},
- new String[]{server.getContextPath(), redirectionEndpointUrl.toString(), samlResponse, csrfToken});
+ new String[]{"%NONCE%", "%WEB_CONTEXT%", "%VALIDATION_URL%", "%SAML_RESPONSE%", "%CSRF_TOKEN%"},
+ new String[]{nonce, server.getContextPath(), redirectionEndpointUrl.toString(), samlResponse, csrfToken});
httpResponse.setContentType("text/html");
- addCspHeadersToResponse(httpResponse, getHashForInlineScript(template));
httpResponse.getWriter().print(template);
return;
}
<button>Click Here to See Result</button>
</form>
</body>
-<script>
- document.getElementById("saml_validate").submit()
+<script nonce="%NONCE%">
+ document.getElementById('saml_validate').submit();
</script>
</html>
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
-import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.sonar.server.authentication.SamlValidationCspHeaders.addCspHeadersToResponse;
-import static org.sonar.server.authentication.SamlValidationCspHeaders.getHashForInlineScript;
+import static org.sonar.server.authentication.SamlValidationCspHeaders.addCspHeadersWithNonceToResponse;
public class SamlValidationCspHeadersTest {
@Test
- public void CspHeaders_are_correctly_added_to_response() {
+ public void addCspHeadersWithNonceToResponse_whenCalled_shouldAddNonceToCspHeaders() {
HttpServletResponse httpServletResponse = mock(HttpServletResponse.class);
- addCspHeadersToResponse(httpServletResponse, "hash");
- verify(httpServletResponse).setHeader("Content-Security-Policy", "default-src 'self'; base-uri 'none'; connect-src 'self' http: https:; img-src * data: blob:; object-src 'none'; script-src 'self' 'hash'; style-src 'self' 'unsafe-inline'; worker-src 'none'");
- verify(httpServletResponse).setHeader("X-Content-Security-Policy", "default-src 'self'; base-uri 'none'; connect-src 'self' http: https:; img-src * data: blob:; object-src 'none'; script-src 'self' 'hash'; style-src 'self' 'unsafe-inline'; worker-src 'none'");
- verify(httpServletResponse).setHeader("X-WebKit-CSP", "default-src 'self'; base-uri 'none'; connect-src 'self' http: https:; img-src * data: blob:; object-src 'none'; script-src 'self' 'hash'; style-src 'self' 'unsafe-inline'; worker-src 'none'");
- }
-
- @Test
- public void hash_is_properly_calculated_for_an_inline_script() {
- String hash = getHashForInlineScript(getBasicHtmlWithScript());
- assertEquals("sha256-jRoPhEx/vXxIUUkuTwJJ2ww4OPlo7B2ZK/wDVC4IXUs=", hash);
- }
-
- @Test
- public void hash_is_empty_when_no_inline_script_available() {
- String hash = getHashForInlineScript(getBasicHtmlWithoutScript());
- assertEquals("", hash);
- }
-
- private String getBasicHtmlWithScript() {
- return """
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <title>SAML Authentication Test</title>
- </head>
- <body>
- <script>
- function createBox() {
- const box = document.createElement("div");
- box.className = "box";
- return box;
- });
- </script>
- </body>
- </html>
- """;
- }
+ String nonce = addCspHeadersWithNonceToResponse(httpServletResponse);
- private String getBasicHtmlWithoutScript() {
- return """
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <title>SAML Authentication Test</title>
- </head>
- <body>
- <div id="content">
- <h1>SAML Authentication Test</h1>
- <div class="box">
- <div id="status"></div>
- </div>
- <div id="response" data-response="%SAML_AUTHENTICATION_STATUS%"></div>
- </div>
- </body>
- </html>
- """;
+ verify(httpServletResponse).setHeader(eq("Content-Security-Policy"), contains("script-src 'nonce-" + nonce + "'"));
+ verify(httpServletResponse).setHeader(eq("X-Content-Security-Policy"), contains("script-src 'nonce-" + nonce + "'"));
+ verify(httpServletResponse).setHeader(eq("X-WebKit-CSP"), contains("script-src 'nonce-" + nonce + "'"));
}
}
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.internal.verification.VerificationModeFactory;
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.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.matches;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@RunWith(DataProviderRunner.class)
public class SamlValidationRedirectionFilterTest {
+ public static final List<String> CSP_HEADERS = List.of("Content-Security-Policy", "X-Content-Security-Policy", "X-WebKit-CSP");
+
SamlValidationRedirectionFilter underTest;
@Before
underTest.init(mock(FilterConfig.class));
}
+
@Test
public void do_get_pattern() {
assertThat(underTest.doGetPattern().matches("/oauth2/callback/saml")).isTrue();
ArgumentCaptor<String> htmlProduced = ArgumentCaptor.forClass(String.class);
verify(pw).print(htmlProduced.capture());
- verifyResponseContentTypeAndCSPHeaders(servletResponse, "sha256-TClpsoWi64Z74Xuk4Fa3bdt7mY/7K+A2jHOgNpxDy2I=");
+ CSP_HEADERS.forEach(h -> verify(servletResponse).setHeader(eq(h), anyString()));
assertThat(htmlProduced.getValue()).contains(validSample);
assertThat(htmlProduced.getValue()).contains("action=\"contextPath/saml/validation\"");
assertThat(htmlProduced.getValue()).contains("value=\"CSRF_TOKEN\"");
ArgumentCaptor<String> htmlProduced = ArgumentCaptor.forClass(String.class);
verify(pw).print(htmlProduced.capture());
- verifyResponseContentTypeAndCSPHeaders(servletResponse, "sha256-TClpsoWi64Z74Xuk4Fa3bdt7mY/7K+A2jHOgNpxDy2I=");
+ CSP_HEADERS.forEach(h -> verify(servletResponse).setHeader(eq(h), anyString()));
assertThat(htmlProduced.getValue()).contains(validSample);
assertThat(htmlProduced.getValue()).doesNotContain("<script>/*Malicious Token*/</script>");
underTest.doFilter(servletRequest, servletResponse, filterChain);
ArgumentCaptor<String> htmlProduced = ArgumentCaptor.forClass(String.class);
-
verify(pw).print(htmlProduced.capture());
- verifyResponseContentTypeAndCSPHeaders(servletResponse, "sha256-TClpsoWi64Z74Xuk4Fa3bdt7mY/7K+A2jHOgNpxDy2I=");
+ CSP_HEADERS.forEach(h -> verify(servletResponse).setHeader(eq(h), anyString()));
assertThat(htmlProduced.getValue()).doesNotContain("<script>/*hack website*/</script>");
assertThat(htmlProduced.getValue()).contains("action=\"contextPath/saml/validation\"");
}
HttpServletRequest servletRequest = mock(HttpServletRequest.class);
HttpServletResponse servletResponse = mock(HttpServletResponse.class);
FilterChain filterChain = mock(FilterChain.class);
-
doReturn(relayStateValue).when(servletRequest).getParameter("RelayState");
+
underTest.doFilter(servletRequest, servletResponse, filterChain);
verifyNoInteractions(servletResponse);
public static Object[] invalidRelayStateValues() {
return new Object[]{"random_query", "validation-query", null};
}
-
- private static void verifyResponseContentTypeAndCSPHeaders(HttpServletResponse servletResponse, String hash) {
- verify(servletResponse).setContentType("text/html");
- verify(servletResponse).setHeader("Content-Security-Policy", "default-src 'self'; base-uri 'none'; connect-src 'self' http: https:; img-src * data: blob:; object-src 'none'; script-src 'self' '" + hash + "'; style-src 'self' 'unsafe-inline'; worker-src 'none'");
- verify(servletResponse).setHeader("X-Content-Security-Policy", "default-src 'self'; base-uri 'none'; connect-src 'self' http: https:; img-src * data: blob:; object-src 'none'; script-src 'self' '" + hash + "'; style-src 'self' 'unsafe-inline'; worker-src 'none'");
- verify(servletResponse).setHeader("X-WebKit-CSP", "default-src 'self'; base-uri 'none'; connect-src 'self' http: https:; img-src * data: blob:; object-src 'none'; script-src 'self' '" + hash + "'; style-src 'self' 'unsafe-inline'; worker-src 'none'");
- }
-
}
import org.sonar.server.authentication.AuthenticationError;
import org.sonar.server.authentication.OAuth2ContextFactory;
import org.sonar.server.authentication.OAuthCsrfVerifier;
+import org.sonar.server.authentication.SamlValidationCspHeaders;
import org.sonar.server.authentication.SamlValidationRedirectionFilter;
import org.sonar.server.authentication.event.AuthenticationException;
import org.sonar.server.user.ThreadLocalUserSession;
import org.sonar.server.ws.ServletFilterHandler;
-import static org.sonar.server.authentication.SamlValidationCspHeaders.addCspHeadersToResponse;
-import static org.sonar.server.authentication.SamlValidationCspHeaders.getHashForInlineScript;
import static org.sonar.server.saml.ws.SamlValidationWs.SAML_VALIDATION_CONTROLLER;
public class ValidationAction extends ServletFilter implements SamlAction {
httpResponse.setContentType("text/html");
String htmlResponse = samlAuthenticator.getAuthenticationStatusPage(httpRequest, httpResponse);
- addCspHeadersToResponse(httpResponse, getHashForInlineScript(htmlResponse));
+ String nonce = SamlValidationCspHeaders.addCspHeadersWithNonceToResponse(httpResponse);
+ htmlResponse = htmlResponse.replace("%NONCE%", nonce);
httpResponse.getWriter().print(htmlResponse);
}
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.sonar.auth.saml.SamlIdentityProvider;
import org.sonar.server.authentication.OAuth2ContextFactory;
import org.sonar.server.authentication.OAuthCsrfVerifier;
+import org.sonar.server.authentication.SamlValidationCspHeaders;
import org.sonar.server.authentication.event.AuthenticationEvent;
import org.sonar.server.authentication.event.AuthenticationException;
import org.sonar.server.user.ThreadLocalUserSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
-import static org.sonar.server.authentication.SamlValidationCspHeaders.getHashForInlineScript;
public class ValidationActionTest {
private ValidationAction underTest;
private SamlAuthenticator samlAuthenticator;
private ThreadLocalUserSession userSession;
-
private OAuthCsrfVerifier oAuthCsrfVerifier;
-
private SamlIdentityProvider samlIdentityProvider;
+ public static final List<String> CSP_HEADERS = List.of("Content-Security-Policy", "X-Content-Security-Policy", "X-WebKit-CSP");
@Before
public void setup() {
underTest = new ValidationAction(userSession, samlAuthenticator, oAuth2ContextFactory, samlIdentityProvider, oAuthCsrfVerifier);
}
+
@Test
public void do_get_pattern() {
assertThat(underTest.doGetPattern().matches("/saml/validation")).isTrue();
doReturn(true).when(userSession).hasSession();
doReturn(true).when(userSession).isSystemAdministrator();
- doReturn(getBasicHtmlWithScript()).when(samlAuthenticator).getAuthenticationStatusPage(any(), any());
+ final String mockedHtmlContent = "mocked html content";
+ doReturn(mockedHtmlContent).when(samlAuthenticator).getAuthenticationStatusPage(any(), any());
underTest.doFilter(servletRequest, servletResponse, filterChain);
verify(samlAuthenticator).getAuthenticationStatusPage(any(), any());
verify(servletResponse).getWriter();
- verifyResponseTypeAndCSPHeaders(servletResponse, getHashForInlineScript(getBasicHtmlWithScript()));
- assertEquals(stringWriter.toString(), getBasicHtmlWithScript());
+ CSP_HEADERS.forEach(h -> verify(servletResponse).setHeader(eq(h), anyString()));
+ assertEquals(mockedHtmlContent, stringWriter.toString());
}
@Test
assertThat(validationInitAction.description()).isNotEmpty();
assertThat(validationInitAction.handler()).isNotNull();
}
-
- private static void verifyResponseTypeAndCSPHeaders(HttpServletResponse servletResponse, String hash) {
- verify(servletResponse).setContentType("text/html");
- verify(servletResponse).setHeader("Content-Security-Policy", "default-src 'self'; base-uri 'none'; connect-src 'self' http: https:; img-src * data: blob:; object-src 'none'; script-src 'self' '" + hash + "'; style-src 'self' 'unsafe-inline'; worker-src 'none'");
- verify(servletResponse).setHeader("X-Content-Security-Policy", "default-src 'self'; base-uri 'none'; connect-src 'self' http: https:; img-src * data: blob:; object-src 'none'; script-src 'self' '" + hash + "'; style-src 'self' 'unsafe-inline'; worker-src 'none'");
- verify(servletResponse).setHeader("X-WebKit-CSP", "default-src 'self'; base-uri 'none'; connect-src 'self' http: https:; img-src * data: blob:; object-src 'none'; script-src 'self' '" + hash + "'; style-src 'self' 'unsafe-inline'; worker-src 'none'");
- }
-
- private String getBasicHtmlWithScript() {
- return """
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" charset="UTF-8" />
- <link rel="icon" type="image/x-icon" href="%WEB_CONTEXT%/favicon.ico" />
- <meta name="application-name" content="SonarQube" />
- <meta name="msapplication-TileColor" content="#FFFFFF" />
- <meta name="msapplication-TileImage" content="%WEB_CONTEXT%/mstile-512x512.png" />
- <title>SAML Authentication Test</title>
-
- <style>
- .error {
- background-color: #d02f3a;
- }
-
- .success {
- background-color: #008a25;
- }
- </style>
- </head>
-
- <body>
- <div id="content">
- <h1>SAML Authentication Test</h1>
- <div class="box">
- <div id="status"></div>
- </div>
- <div id="response" data-response="%SAML_AUTHENTICATION_STATUS%"></div>
- </div>
-
- <script>
- window.addEventListener('DOMContentLoaded', (event) => {
-
- function createBox() {
- const box = document.createElement("div");
- box.className = "box";
- return box;
- }
- });
- </script>
- </body>
- </html>
- """;
- }
}