]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17296 sanitize SAML response fields
authorMatteo Mara <matteo.mara@sonarsource.com>
Mon, 26 Sep 2022 12:01:18 +0000 (14:01 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 26 Sep 2022 20:03:17 +0000 (20:03 +0000)
server/sonar-auth-saml/src/main/java/org/sonar/auth/saml/SamlAuthStatusPageGenerator.java
server/sonar-auth-saml/src/main/resources/samlAuthResult.html
server/sonar-auth-saml/src/test/java/org/sonar/auth/saml/SamlAuthStatusPageGeneratorTest.java
server/sonar-auth-saml/src/test/resources/samlAuthResultComplete.html [deleted file]
server/sonar-auth-saml/src/test/resources/samlAuthResultEmpty.html

index 82a3a8ae0352a263f9f1c36b492f6ad954b90057..c6093ebd0a5c1842ef9f037bb283f37b38bc31d0 100644 (file)
@@ -23,20 +23,14 @@ import com.google.common.io.Resources;
 import java.io.IOException;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
-import java.util.List;
+import java.util.Base64;
 import java.util.Map;
-import java.util.stream.Collectors;
-import javax.annotation.Nullable;
 import org.json.JSONObject;
 
 public final class SamlAuthStatusPageGenerator {
 
   private static final String WEB_CONTEXT = "%WEB_CONTEXT%";
-  private static final String STATUS = "%STATUS%";
-  private static final String ERRORS = "%ERRORS%";
-  private static final String WARNINGS = "%WARNINGS%";
-  private static final String AVAILABLE_ATTRIBUTES = "%AVAILABLE_ATTRIBUTES%";
-  private static final String ATTRIBUTE_MAPPINGS = "%ATTRIBUTE_MAPPINGS%";
+  private static final String SAML_AUTHENTICATION_STATUS = "%SAML_AUTHENTICATION_STATUS%";
 
   private static final String HTML_TEMPLATE_NAME = "samlAuthResult.html";
 
@@ -57,21 +51,12 @@ public final class SamlAuthStatusPageGenerator {
   private static Map<String, String> getSubstitutionsMap(SamlAuthenticationStatus samlAuthenticationStatus) {
     return Map.of(
       WEB_CONTEXT, "",
-      STATUS, toJsString(samlAuthenticationStatus.getStatus()),
-      ERRORS, toJsArrayFromList(samlAuthenticationStatus.getErrors()),
-      WARNINGS, toJsArrayFromList(samlAuthenticationStatus.getWarnings()),
-      AVAILABLE_ATTRIBUTES, new JSONObject(samlAuthenticationStatus.getAvailableAttributes()).toString(),
-      ATTRIBUTE_MAPPINGS, new JSONObject(samlAuthenticationStatus.getMappedAttributes()).toString());
+      SAML_AUTHENTICATION_STATUS, getBase64EncodedStatus(samlAuthenticationStatus));
   }
 
-  private static String toJsString(@Nullable String inputString) {
-    return String.format("'%s'", inputString != null ? inputString.replace("'", "\\'") : "");
-  }
-
-  private static String toJsArrayFromList(List<String> inputArray) {
-    return "[" + inputArray.stream()
-      .map(SamlAuthStatusPageGenerator::toJsString)
-      .collect(Collectors.joining(",")) + "]";
+  private static String getBase64EncodedStatus(SamlAuthenticationStatus samlAuthenticationStatus) {
+    byte[] bytes = new JSONObject(samlAuthenticationStatus).toString().getBytes(StandardCharsets.UTF_8);
+    return String.format("'%s'", Base64.getEncoder().encodeToString(bytes));
   }
 
   private static String getPlainTemplate() {
index 398266b2d29de2177d57ca63f564294d74853e88..85e987b33caac6a9347603c485e52f037359bb97 100644 (file)
 
       function createSectionTitle(title) {
         const node = document.createElement("h2");
-        node.innerText = title;
+        node.textContent = title;
         return node;
       }
 
         arr.forEach((item) => {
           const message = document.createElement("li");
           message.className = className;
-          message.innerText = item;
+          message.textContent = item;
           list.appendChild(message);
         });
 
           const row = document.createElement("tr");
 
           const keyNode = document.createElement("td");
-          keyNode.innerText = key;
+          keyNode.textContent = key;
           row.appendChild(keyNode);
 
           const valueNode = document.createElement("td");
         container.appendChild(box);
       }
 
-      const status = %STATUS%;
-      const attributes = %AVAILABLE_ATTRIBUTES%;
-      const mappings = %ATTRIBUTE_MAPPINGS%;
-      const errors = %ERRORS%;
-      const warnings = %WARNINGS%;
+      const response = %SAML_AUTHENTICATION_STATUS%;
+      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.innerText = status;
+      statusNode.textContent = status;
 
       // generate content
       const container = document.querySelector("#content");
index 966d61d440f5160cd69f82b6b6270e1a039690f9..7e5e43953c2e896d0aa6fdd961d0fea6ad003723 100644 (file)
@@ -25,8 +25,6 @@ import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
@@ -35,30 +33,11 @@ import static org.mockito.Mockito.when;
 import static org.sonar.auth.saml.SamlAuthStatusPageGenerator.getSamlAuthStatusHtml;
 
 public class SamlAuthStatusPageGeneratorTest {
-
-  private final SamlAuthenticationStatus samlAuthenticationStatus = mock(SamlAuthenticationStatus.class);
-
-  private static final String HTML_TEMPLATE_NAME = "samlAuthResultComplete.html";
   private static final String EMPTY_HTML_TEMPLATE_NAME = "samlAuthResultEmpty.html";
 
-  @Test
-  public void test_full_html_generation() {
-
-    when(samlAuthenticationStatus.getStatus()).thenReturn("success");
-    when(samlAuthenticationStatus.getErrors()).thenReturn(List.of("error1", "error2 'with message'"));
-    when(samlAuthenticationStatus.getWarnings()).thenReturn(List.of("warning1", "warning2 'with message'"));
-    when(samlAuthenticationStatus.getAvailableAttributes()).thenReturn(Map.of("key1", List.of("value1", "value2 with weird chars \n\t\"\\"), "key2", List.of("value3", "value4")));
-    when(samlAuthenticationStatus.getMappedAttributes()).thenReturn(Map.of("key1", List.of("value1", "value2"), "key2", List.of("value3", "value4")));
-
-    String completeHtmlTemplate = getSamlAuthStatusHtml(samlAuthenticationStatus);
-    String expectedTemplate = loadTemplateFromResources(HTML_TEMPLATE_NAME);
-
-    assertEquals(expectedTemplate, completeHtmlTemplate);
-
-  }
-
   @Test
   public void test_full_html_generation_with_empty_values() {
+    SamlAuthenticationStatus samlAuthenticationStatus = mock(SamlAuthenticationStatus.class);
 
     when(samlAuthenticationStatus.getStatus()).thenReturn(null);
     when(samlAuthenticationStatus.getErrors()).thenReturn(new ArrayList<>());
diff --git a/server/sonar-auth-saml/src/test/resources/samlAuthResultComplete.html b/server/sonar-auth-saml/src/test/resources/samlAuthResultComplete.html
deleted file mode 100644 (file)
index f12552e..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-<!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="/apple-touch-icon.png" />
-    <link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png" />
-    <link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png" />
-    <link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png" />
-    <link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png" />
-    <link
-      rel="apple-touch-icon"
-      sizes="114x114"
-      href="/apple-touch-icon-114x114.png"
-    />
-    <link
-      rel="apple-touch-icon"
-      sizes="120x120"
-      href="/apple-touch-icon-120x120.png"
-    />
-    <link
-      rel="apple-touch-icon"
-      sizes="144x144"
-      href="/apple-touch-icon-144x144.png"
-    />
-    <link
-      rel="apple-touch-icon"
-      sizes="152x152"
-      href="/apple-touch-icon-152x152.png"
-    />
-    <link
-      rel="apple-touch-icon"
-      sizes="180x180"
-      href="/apple-touch-icon-180x180.png"
-    />
-    <link rel="icon" type="image/x-icon" href="/favicon.ico" />
-    <meta name="application-name" content="SonarQube" />
-    <meta name="msapplication-TileColor" content="#FFFFFF" />
-    <meta name="msapplication-TileImage" content="/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>
-
-    <script>
-      function createBox() {
-        const box = document.createElement("div");
-        box.className = "box";
-        return box;
-      }
-
-      function createSectionTitle(title) {
-        const node = document.createElement("h2");
-        node.innerText = title;
-        return node;
-      }
-
-      function createList(arr, className = "") {
-        const list = document.createElement("ul");
-
-        arr.forEach((item) => {
-          const message = document.createElement("li");
-          message.className = className;
-          message.innerText = 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.innerText = 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 status = 'success';
-      const attributes = {"key1":["value1","value2 with weird chars \n\t\"\\"],"key2":["value3","value4"]};
-      const mappings = {"key1":["value1","value2"],"key2":["value3","value4"]};
-      const errors = ['error1','error2 \'with message\''];
-      const warnings = ['warning1','warning2 \'with message\''];
-
-      // Switch status class
-      const statusNode = document.querySelector("#status");
-      statusNode.classList.add(status);
-      statusNode.innerText = 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>
index 6ae670910bdb753e85218b28ed9f36dc22844695..de4879d8ce34f2a2a2c949255c6f3f826320b5b5 100644 (file)
 
       function createSectionTitle(title) {
         const node = document.createElement("h2");
-        node.innerText = title;
+        node.textContent = title;
         return node;
       }
 
         arr.forEach((item) => {
           const message = document.createElement("li");
           message.className = className;
-          message.innerText = item;
+          message.textContent = item;
           list.appendChild(message);
         });
 
           const row = document.createElement("tr");
 
           const keyNode = document.createElement("td");
-          keyNode.innerText = key;
+          keyNode.textContent = key;
           row.appendChild(keyNode);
 
           const valueNode = document.createElement("td");
         container.appendChild(box);
       }
 
-      const status = '';
-      const attributes = {};
-      const mappings = {};
-      const errors = [];
-      const warnings = [];
+      const response = 'eyJ3YXJuaW5ncyI6W10sImF2YWlsYWJsZUF0dHJpYnV0ZXMiOnt9LCJlcnJvcnMiOltdLCJtYXBwZWRBdHRyaWJ1dGVzIjp7fX0=';
+      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.innerText = status;
+      statusNode.textContent = status;
 
       // generate content
       const container = document.querySelector("#content");