]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23205 Genereate CSP hash of assetsPath script based WEB_CONTEXT value
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Wed, 16 Oct 2024 13:56:06 +0000 (15:56 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 22 Oct 2024 20:03:10 +0000 (20:03 +0000)
server/sonar-webserver/src/main/java/org/sonar/server/platform/web/CspFilter.java
server/sonar-webserver/src/test/java/org/sonar/server/platform/web/CspFilterTest.java

index a0f7b100dbcaab56f3dae77ce10552611269b94d..2a6372101d98538c6e98dbf61049d766e829f784 100644 (file)
 package org.sonar.server.platform.web;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
+import java.util.Base64;
 import java.util.List;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -47,7 +51,7 @@ public class CspFilter implements Filter {
     cspPolicies.add("img-src * data: blob:");
     cspPolicies.add("object-src 'none'");
     // the hash below corresponds to the window.__assetsPath script in index.html
-    cspPolicies.add("script-src 'self' 'sha256-D1jaqcDDM2TM2STrzE42NNqyKR9PlptcHDe6tyaBcuM='");
+    cspPolicies.add("script-src 'self' " + getAssetsPathScriptCSPHash(filterConfig.getServletContext().getContextPath()));
     cspPolicies.add("style-src 'self' 'unsafe-inline'");
     cspPolicies.add("worker-src 'none'");
     this.policies = String.join("; ", cspPolicies).trim();
@@ -68,4 +72,26 @@ public class CspFilter implements Filter {
     // Not used
   }
 
+  private static String getAssetsPathScriptCSPHash(String contextPath) {
+    final String WEB_CONTEXT_PLACEHOLDER = "WEB_CONTEXT";
+    final String ASSETS_PATH_SCRIPT = "\n" +
+      "      window.__assetsPath = function (filename) {\n" +
+      "        return 'WEB_CONTEXT/' + filename;\n" +
+      "      };\n" +
+      "    ";
+
+    String assetsPathScriptWithContextPath = ASSETS_PATH_SCRIPT.replace(WEB_CONTEXT_PLACEHOLDER, contextPath);
+    return generateCSPHash(assetsPathScriptWithContextPath);
+  }
+
+  private static String generateCSPHash(String str) {
+    try {
+      byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
+      byte[] digestBytes = MessageDigest.getInstance("SHA-256").digest(bytes);
+      String rawHash = Base64.getMimeEncoder().encodeToString(digestBytes);
+      return String.format("'%s-%s'", "sha256", rawHash);
+    } catch (NoSuchAlgorithmException e) {
+      return "";
+    }
+  }
 }
index 77290ec96d5647d291e803808c8ef83c9c85d472..5cf592360a455157e7cfe76f691c4b61c3a00b54 100644 (file)
@@ -28,6 +28,8 @@ import javax.servlet.http.HttpServletResponse;
 import org.junit.Before;
 import org.junit.Test;
 
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.RETURNS_MOCKS;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -42,7 +44,7 @@ public class CspFilterTest {
     "font-src 'self' data:; " +
     "img-src * data: blob:; " +
     "object-src 'none'; " +
-    "script-src 'self' 'sha256-D1jaqcDDM2TM2STrzE42NNqyKR9PlptcHDe6tyaBcuM='; " +
+    "script-src 'self' 'sha256-hK8SVWFNHY0UhP61DBzX/3fvT74EI8u6/jRQvUKeZoU='; " +
     "style-src 'self' 'unsafe-inline'; " +
     "worker-src 'none'";
   private final ServletContext servletContext = mock(ServletContext.class, RETURNS_MOCKS);
@@ -53,11 +55,12 @@ public class CspFilterTest {
 
   @Before
   public void setUp() throws ServletException {
-    when(servletContext.getContextPath()).thenReturn(TEST_CONTEXT);
+    when(config.getServletContext()).thenReturn(servletContext);
   }
 
   @Test
   public void set_content_security_headers() throws Exception {
+    when(servletContext.getContextPath()).thenReturn(TEST_CONTEXT);
     doInit();
     HttpServletRequest request = newRequest("/");
     underTest.doFilter(request, response, chain);
@@ -65,6 +68,16 @@ public class CspFilterTest {
     verify(chain).doFilter(request, response);
   }
 
+  @Test
+  public void csp_hash_should_be_correct_without_a_context_path() throws Exception {
+    when(servletContext.getContextPath()).thenReturn("");
+    doInit();
+    HttpServletRequest request = newRequest("/");
+    underTest.doFilter(request, response, chain);
+    verify(response).setHeader(eq("Content-Security-Policy"), contains("script-src 'self' 'sha256-D1jaqcDDM2TM2STrzE42NNqyKR9PlptcHDe6tyaBcuM='; "));
+    verify(chain).doFilter(request, response);
+  }
+
   private void doInit() throws ServletException {
     underTest.init(config);
   }