keycloak-uncached
Changes
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/com/googlecode/owasp-java-html-sanitizer/main/module.xml 26(+26 -0)
distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml 2(+2 -0)
pom.xml 6(+6 -0)
prod-arguments.json 1(+1 -0)
services/pom.xml 4(+4 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/MailUtils.java 8(+7 -1)
Details
diff --git a/distribution/feature-packs/server-feature-pack/pom.xml b/distribution/feature-packs/server-feature-pack/pom.xml
index 525a3a9..05613df 100644
--- a/distribution/feature-packs/server-feature-pack/pom.xml
+++ b/distribution/feature-packs/server-feature-pack/pom.xml
@@ -73,6 +73,16 @@
</exclusions>
</dependency>
<dependency>
+ <groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
+ <artifactId>owasp-java-html-sanitizer</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>*</groupId>
+ <artifactId>*</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<exclusions>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/com/googlecode/owasp-java-html-sanitizer/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/com/googlecode/owasp-java-html-sanitizer/main/module.xml
new file mode 100644
index 0000000..4a1bbbe
--- /dev/null
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/com/googlecode/owasp-java-html-sanitizer/main/module.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2018 Red Hat, Inc. and/or its affiliates
+ ~ and other contributors as indicated by the @author tags.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<module xmlns="urn:jboss:module:1.3" name="com.googlecode.owasp-java-html-sanitizer">
+ <resources>
+ <artifact name="${com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer}"/>
+ </resources>
+
+ <dependencies>
+ <module name="com.google.guava"/>
+ </dependencies>
+</module>
diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
index e85db0e..055f30f 100755
--- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
+++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml
@@ -44,6 +44,8 @@
<module name="org.keycloak.keycloak-authz-policy-common" services="import"/>
<module name="org.keycloak.keycloak-authz-policy-drools" services="import"/>
+ <module name="com.googlecode.owasp-java-html-sanitizer"/>
+ <module name="com.google.guava"/>
<module name="org.freemarker"/>
<module name="javax.ws.rs.api"/>
<module name="javax.mail.api"/>
pom.xml 6(+6 -0)
diff --git a/pom.xml b/pom.xml
index d861abc..ed7fafe 100755
--- a/pom.xml
+++ b/pom.xml
@@ -77,6 +77,7 @@
<jboss.spec.javax.xml.bind.jboss-jaxb-api_2.2_spec.version>1.0.4.Final</jboss.spec.javax.xml.bind.jboss-jaxb-api_2.2_spec.version>
<log4j.version>1.2.17</log4j.version>
<resteasy.version>3.0.26.Final</resteasy.version>
+ <owasp.html.sanitizer.version>20180219.1</owasp.html.sanitizer.version>
<slf4j.version>1.7.22</slf4j.version>
<sun.istack.version>2.21</sun.istack.version>
<sun.jaxb.version>2.2.11</sun.jaxb.version>
@@ -371,6 +372,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
+ <artifactId>owasp-java-html-sanitizer</artifactId>
+ <version>${owasp.html.sanitizer.version}</version>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
prod-arguments.json 1(+1 -0)
diff --git a/prod-arguments.json b/prod-arguments.json
index ae500ab..cd9828b 100644
--- a/prod-arguments.json
+++ b/prod-arguments.json
@@ -16,6 +16,7 @@
"dependencyExclusion.io.undertow:*@*": "1.4.18.SP2-redhat-1",
"dependencyExclusion.org.wildfly.security:*@*": "1.1.8.Final-redhat-1",
"dependencyExclusion.org.freemarker:freemarker@*": "2.3.26.incubating-redhat-1",
+ "dependencyExclusion.com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer@*": "20180219.1.0.redhat-1",
"dependencyExclusion.org.liquibase:liquibase-core@*": "$COMMONCFG_LIQUIBASE_3_4_1",
"dependencyExclusion.org.twitter4j:twitter4j-core@*": "$COMMONCFG_TWITTER4J_4_0_4",
"dependencyExclusion.com.google.zxing:core@*": "$COMMONCFG_ZXING_3_2_1",
services/pom.xml 4(+4 -0)
diff --git a/services/pom.xml b/services/pom.xml
index 77f3597..82d173e 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -137,6 +137,10 @@
<artifactId>resteasy-multipart-provider</artifactId>
</dependency>
<dependency>
+ <groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
+ <artifactId>owasp-java-html-sanitizer</artifactId>
+ </dependency>
+ <dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
diff --git a/services/src/main/java/org/keycloak/theme/FreeMarkerUtil.java b/services/src/main/java/org/keycloak/theme/FreeMarkerUtil.java
index 7415bf1..660e35a 100755
--- a/services/src/main/java/org/keycloak/theme/FreeMarkerUtil.java
+++ b/services/src/main/java/org/keycloak/theme/FreeMarkerUtil.java
@@ -27,6 +27,7 @@ import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -35,6 +36,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class FreeMarkerUtil {
private ConcurrentHashMap<String, Template> cache;
+ private final KeycloakSanitizerMethod kcSanitizeMethod = new KeycloakSanitizerMethod();
public FreeMarkerUtil() {
if (Config.scope("theme").getBoolean("cacheTemplates", true)) {
@@ -43,6 +45,10 @@ public class FreeMarkerUtil {
}
public String processTemplate(Object data, String templateName, Theme theme) throws FreeMarkerException {
+ if (data instanceof Map) {
+ ((Map)data).put("kcSanitize", kcSanitizeMethod);
+ }
+
try {
Template template;
cache = null;
diff --git a/services/src/main/java/org/keycloak/theme/KeycloakSanitizerMethod.java b/services/src/main/java/org/keycloak/theme/KeycloakSanitizerMethod.java
new file mode 100644
index 0000000..f893ff4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/theme/KeycloakSanitizerMethod.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.theme;
+
+import freemarker.template.TemplateMethodModelEx;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+import org.owasp.html.PolicyFactory;
+
+/**
+ * Allows sanitizing of html that uses Freemarker ?no_esc. This way, html
+ * can be allowed but it is still cleaned up for safety. Tags and attributes
+ * deemed unsafe will be stripped out.
+ */
+public class KeycloakSanitizerMethod implements TemplateMethodModelEx {
+
+ private static final PolicyFactory KEYCLOAK_POLICY = KeycloakSanitizerPolicy.POLICY_DEFINITION;
+
+ @Override
+ public Object exec(List list) throws TemplateModelException {
+ if ((list.isEmpty()) || (list.get(0) == null)) {
+ throw new NullPointerException("Can not escape null value.");
+ }
+
+ String html = list.get(0).toString();
+ String sanitized = KEYCLOAK_POLICY.sanitize(html);
+
+ return sanitized;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/theme/KeycloakSanitizerPolicy.java b/services/src/main/java/org/keycloak/theme/KeycloakSanitizerPolicy.java
new file mode 100644
index 0000000..bb8a08e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/theme/KeycloakSanitizerPolicy.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.theme;
+
+import java.util.regex.Pattern;
+
+import org.owasp.html.HtmlPolicyBuilder;
+import org.owasp.html.PolicyFactory;
+
+import com.google.common.base.Predicate;
+
+/**
+ * Based on the EbayPolicyExample in owasp java-html-sanitizer.
+ *
+ */
+public class KeycloakSanitizerPolicy {
+
+ // Some common regular expression definitions.
+
+ // The 16 colors defined by the HTML Spec (also used by the CSS Spec)
+ private static final Pattern COLOR_NAME = Pattern.compile(
+ "(?:aqua|black|blue|fuchsia|gray|grey|green|lime|maroon|navy|olive|purple"
+ + "|red|silver|teal|white|yellow)");
+
+ // HTML/CSS Spec allows 3 or 6 digit hex to specify color
+ private static final Pattern COLOR_CODE = Pattern.compile(
+ "(?:#(?:[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?))");
+
+ private static final Pattern NUMBER_OR_PERCENT = Pattern.compile(
+ "[0-9]+%?");
+ private static final Pattern PARAGRAPH = Pattern.compile(
+ "(?:[\\p{L}\\p{N},'\\.\\s\\-_\\(\\)]|&[0-9]{2};)*");
+ private static final Pattern HTML_ID = Pattern.compile(
+ "[a-zA-Z0-9\\:\\-_\\.]+");
+ // force non-empty with a '+' at the end instead of '*'
+ private static final Pattern HTML_TITLE = Pattern.compile(
+ "[\\p{L}\\p{N}\\s\\-_',:\\[\\]!\\./\\\\\\(\\)&]*");
+ private static final Pattern HTML_CLASS = Pattern.compile(
+ "[a-zA-Z0-9\\s,\\-_]+");
+
+ private static final Pattern ONSITE_URL = Pattern.compile(
+ "(?:[\\p{L}\\p{N}\\\\\\.\\#@\\$%\\+&;\\-_~,\\?=/!]+|\\#(\\w)+)");
+ private static final Pattern OFFSITE_URL = Pattern.compile(
+ "\\s*(?:(?:ht|f)tps?://|mailto:)[\\p{L}\\p{N}]"
+ + "[\\p{L}\\p{N}\\p{Zs}\\.\\#@\\$%\\+&;:\\-_~,\\?=/!\\(\\)]*+\\s*");
+
+ private static final Pattern NUMBER = Pattern.compile(
+ "[+-]?(?:(?:[0-9]+(?:\\.[0-9]*)?)|\\.[0-9]+)");
+
+ private static final Pattern NAME = Pattern.compile("[a-zA-Z0-9\\-_\\$]+");
+
+ private static final Pattern ALIGN = Pattern.compile(
+ "(?i)center|left|right|justify|char");
+
+ private static final Pattern VALIGN = Pattern.compile(
+ "(?i)baseline|bottom|middle|top");
+
+ private static final Predicate<String> COLOR_NAME_OR_COLOR_CODE
+ = matchesEither(COLOR_NAME, COLOR_CODE);
+
+ private static final Predicate<String> ONSITE_OR_OFFSITE_URL
+ = matchesEither(ONSITE_URL, OFFSITE_URL);
+
+ private static final Pattern HISTORY_BACK = Pattern.compile(
+ "(?:javascript:)?\\Qhistory.go(-1)\\E");
+
+ private static final Pattern ONE_CHAR = Pattern.compile(
+ ".?", Pattern.DOTALL);
+
+
+ public static final PolicyFactory POLICY_DEFINITION = new HtmlPolicyBuilder()
+ .allowWithoutAttributes("span") // this is added to ebay example to allow span without attributes
+ .allowAttributes("id").matching(HTML_ID).globally()
+ .allowAttributes("class").matching(HTML_CLASS).globally()
+ .allowAttributes("lang").matching(Pattern.compile("[a-zA-Z]{2,20}"))
+ .globally()
+ .allowAttributes("title").matching(HTML_TITLE).globally()
+ .allowStyling()
+ .allowAttributes("align").matching(ALIGN).onElements("p")
+ .allowAttributes("for").matching(HTML_ID).onElements("label")
+ .allowAttributes("color").matching(COLOR_NAME_OR_COLOR_CODE)
+ .onElements("font")
+ .allowAttributes("face")
+ .matching(Pattern.compile("[\\w;, \\-]+"))
+ .onElements("font")
+ .allowAttributes("size").matching(NUMBER).onElements("font")
+ .allowAttributes("href").matching(ONSITE_OR_OFFSITE_URL)
+ .onElements("a")
+ .allowStandardUrlProtocols()
+ .allowAttributes("nohref").onElements("a")
+ .allowAttributes("name").matching(NAME).onElements("a")
+ .allowAttributes(
+ "onfocus", "onblur", "onclick", "onmousedown", "onmouseup")
+ .matching(HISTORY_BACK).onElements("a")
+ .requireRelNofollowOnLinks()
+ .allowAttributes("src").matching(ONSITE_OR_OFFSITE_URL)
+ .onElements("img")
+ .allowAttributes("name").matching(NAME)
+ .onElements("img")
+ .allowAttributes("alt").matching(PARAGRAPH)
+ .onElements("img")
+ .allowAttributes("border", "hspace", "vspace").matching(NUMBER)
+ .onElements("img")
+ .allowAttributes("border", "cellpadding", "cellspacing")
+ .matching(NUMBER).onElements("table")
+ .allowAttributes("bgcolor").matching(COLOR_NAME_OR_COLOR_CODE)
+ .onElements("table")
+ .allowAttributes("background").matching(ONSITE_URL)
+ .onElements("table")
+ .allowAttributes("align").matching(ALIGN)
+ .onElements("table")
+ .allowAttributes("noresize").matching(Pattern.compile("(?i)noresize"))
+ .onElements("table")
+ .allowAttributes("background").matching(ONSITE_URL)
+ .onElements("td", "th", "tr")
+ .allowAttributes("bgcolor").matching(COLOR_NAME_OR_COLOR_CODE)
+ .onElements("td", "th")
+ .allowAttributes("abbr").matching(PARAGRAPH)
+ .onElements("td", "th")
+ .allowAttributes("axis", "headers").matching(NAME)
+ .onElements("td", "th")
+ .allowAttributes("scope")
+ .matching(Pattern.compile("(?i)(?:row|col)(?:group)?"))
+ .onElements("td", "th")
+ .allowAttributes("nowrap")
+ .onElements("td", "th")
+ .allowAttributes("height", "width").matching(NUMBER_OR_PERCENT)
+ .onElements("table", "td", "th", "tr", "img")
+ .allowAttributes("align").matching(ALIGN)
+ .onElements("thead", "tbody", "tfoot", "img",
+ "td", "th", "tr", "colgroup", "col")
+ .allowAttributes("valign").matching(VALIGN)
+ .onElements("thead", "tbody", "tfoot",
+ "td", "th", "tr", "colgroup", "col")
+ .allowAttributes("charoff").matching(NUMBER_OR_PERCENT)
+ .onElements("td", "th", "tr", "colgroup", "col",
+ "thead", "tbody", "tfoot")
+ .allowAttributes("char").matching(ONE_CHAR)
+ .onElements("td", "th", "tr", "colgroup", "col",
+ "thead", "tbody", "tfoot")
+ .allowAttributes("colspan", "rowspan").matching(NUMBER)
+ .onElements("td", "th")
+ .allowAttributes("span", "width").matching(NUMBER_OR_PERCENT)
+ .onElements("colgroup", "col")
+ .allowElements(
+ "a", "label", "noscript", "h1", "h2", "h3", "h4", "h5", "h6",
+ "p", "i", "b", "u", "strong", "em", "small", "big", "pre", "code",
+ "cite", "samp", "sub", "sup", "strike", "center", "blockquote",
+ "hr", "br", "col", "font", "map", "span", "div", "img",
+ "ul", "ol", "li", "dd", "dt", "dl", "tbody", "thead", "tfoot",
+ "table", "td", "th", "tr", "colgroup", "fieldset", "legend")
+ .toFactory();
+
+ private static Predicate<String> matchesEither(
+ final Pattern a, final Pattern b) {
+ return new Predicate<String>() {
+ public boolean apply(String s) {
+ return a.matcher(s).matches()|| b.matcher(s).matches();
+ }
+ };
+ }
+}
diff --git a/services/src/test/java/org/keycloak/theme/KeycloakSanitizerTest.java b/services/src/test/java/org/keycloak/theme/KeycloakSanitizerTest.java
new file mode 100644
index 0000000..9e503f5
--- /dev/null
+++ b/services/src/test/java/org/keycloak/theme/KeycloakSanitizerTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.theme;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Test the KeycloakEscape utility.
+ *
+ * @author Stan Silvert
+ */
+public class KeycloakSanitizerTest {
+ private KeycloakSanitizerMethod kcEscape = new KeycloakSanitizerMethod();
+
+ @Test
+ public void testEscapes() throws Exception {
+ List<String> html = new ArrayList();
+
+ html.add("<div class=\"kc-logo-text\"><script>alert('foo');</script><span>Keycloak</span></div>");
+ String expectedResult = "<div class=\"kc-logo-text\"><span>Keycloak</span></div>";
+ assertResult(expectedResult, html);
+
+ html.set(0, "<h1>Foo</h1>");
+ expectedResult = "<h1>Foo</h1>";
+ assertResult(expectedResult, html);
+
+ html.set(0, "<div class=\"kc-logo-text\"><span>Keycloak</span></div><svg onload=alert(document.cookie);>");
+ expectedResult = "<div class=\"kc-logo-text\"><span>Keycloak</span></div>";
+ assertResult(expectedResult, html);
+
+ html.set(0, null);
+ expectedResult = null;
+ try {
+ assertResult(expectedResult, html);
+ fail("Expected NPE");
+ } catch (NullPointerException npe) {}
+
+ html.set(0, "");
+ expectedResult = "";
+ assertResult(expectedResult, html);
+ }
+
+ private void assertResult(String expectedResult, List<String> html) throws Exception {
+ String result = kcEscape.exec(html).toString();
+ assertEquals(expectedResult, result);
+ }
+
+}
+
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/MailUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/MailUtils.java
index 7fa362d..ffa7f30 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/MailUtils.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/MailUtils.java
@@ -47,7 +47,13 @@ public class MailUtils {
public static String getPasswordResetEmailLink(EmailBody body) throws IOException, MessagingException {
final String textChangePwdUrl = getLink(body.getText());
- final String htmlChangePwdUrl = getLink(body.getHtml());
+ String htmlChangePwdUrl = getLink(body.getHtml());
+
+ // undo changes that may have been made by html sanitizer
+ htmlChangePwdUrl = htmlChangePwdUrl.replace("=", "=");
+ htmlChangePwdUrl = htmlChangePwdUrl.replace("..", ".");
+ htmlChangePwdUrl = htmlChangePwdUrl.replace("&", "&");
+
assertEquals(htmlChangePwdUrl, textChangePwdUrl);
return htmlChangePwdUrl;
diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index a6bda73..fa927b2 100755
--- a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -357,7 +357,13 @@ public abstract class AbstractIdentityProviderTest {
final String htmlBody = (String) multipart.getBodyPart(1).getContent();
- final String htmlChangePwdUrl = MailUtil.getLink(htmlBody);
+ String htmlChangePwdUrl = MailUtil.getLink(htmlBody);
+
+ // undo changes that may have been made by html sanitizer
+ htmlChangePwdUrl = htmlChangePwdUrl.replace("=", "=");
+ htmlChangePwdUrl = htmlChangePwdUrl.replace("..", ".");
+ htmlChangePwdUrl = htmlChangePwdUrl.replace("&", "&");
+
assertEquals(htmlChangePwdUrl, textVerificationUrl);
return htmlChangePwdUrl;
diff --git a/themes/src/main/resources/theme/base/account/account.ftl b/themes/src/main/resources/theme/base/account/account.ftl
index 2fc3bdb..fb9d222 100755
--- a/themes/src/main/resources/theme/base/account/account.ftl
+++ b/themes/src/main/resources/theme/base/account/account.ftl
@@ -59,7 +59,7 @@
<div class="form-group">
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
<div class="">
- <#if url.referrerURI??><a href="${url.referrerURI}">${msg("backToApplication")?no_esc}/a></#if>
+ <#if url.referrerURI??><a href="${url.referrerURI}">${kcSanitize(msg("backToApplication")?no_esc)}</a></#if>
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Save">${msg("doSave")}</button>
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Cancel">${msg("doCancel")}</button>
</div>
diff --git a/themes/src/main/resources/theme/base/account/template.ftl b/themes/src/main/resources/theme/base/account/template.ftl
index e49c664..d372ad5 100644
--- a/themes/src/main/resources/theme/base/account/template.ftl
+++ b/themes/src/main/resources/theme/base/account/template.ftl
@@ -70,7 +70,7 @@
<div class="alert alert-${message.type}">
<#if message.type=='success' ><span class="pficon pficon-ok"></span></#if>
<#if message.type=='error' ><span class="pficon pficon-error-octagon"></span><span class="pficon pficon-error-exclamation"></span></#if>
- ${message.summary?no_esc}
+ ${kcSanitize(message.summary)?no_esc}
</div>
</#if>
diff --git a/themes/src/main/resources/theme/base/email/html/email-test.ftl b/themes/src/main/resources/theme/base/email/html/email-test.ftl
index d5d18b7..3a52272 100644
--- a/themes/src/main/resources/theme/base/email/html/email-test.ftl
+++ b/themes/src/main/resources/theme/base/email/html/email-test.ftl
@@ -1,5 +1,5 @@
<html>
<body>
-${msg("emailTestBodyHtml",realmName)?no_esc}
+${kcSanitize(msg("emailTestBodyHtml",realmName))?no_esc}
</body>
</html>
diff --git a/themes/src/main/resources/theme/base/email/html/email-verification.ftl b/themes/src/main/resources/theme/base/email/html/email-verification.ftl
index bd371d9..dacabd2 100644
--- a/themes/src/main/resources/theme/base/email/html/email-verification.ftl
+++ b/themes/src/main/resources/theme/base/email/html/email-verification.ftl
@@ -1,5 +1,5 @@
<html>
<body>
-${msg("emailVerificationBodyHtml",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration))?no_esc}
+${kcSanitize(msg("emailVerificationBodyHtml",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration)))?no_esc}
</body>
</html>
diff --git a/themes/src/main/resources/theme/base/email/html/email-verification-with-code.ftl b/themes/src/main/resources/theme/base/email/html/email-verification-with-code.ftl
index b4a01c9..66e8925 100644
--- a/themes/src/main/resources/theme/base/email/html/email-verification-with-code.ftl
+++ b/themes/src/main/resources/theme/base/email/html/email-verification-with-code.ftl
@@ -1,5 +1,5 @@
<html>
<body>
-${msg("emailVerificationBodyCodeHtml",code)?no_esc}
+${kcSanitize(msg("emailVerificationBodyCodeHtml",code))?no_esc}
</body>
</html>
diff --git a/themes/src/main/resources/theme/base/email/html/event-login_error.ftl b/themes/src/main/resources/theme/base/email/html/event-login_error.ftl
index 68ba81d..022c024 100644
--- a/themes/src/main/resources/theme/base/email/html/event-login_error.ftl
+++ b/themes/src/main/resources/theme/base/email/html/event-login_error.ftl
@@ -1,5 +1,5 @@
<html>
<body>
-${msg("eventLoginErrorBodyHtml",event.date,event.ipAddress)?no_esc}
+${kcSanitize(msg("eventLoginErrorBodyHtml",event.date,event.ipAddress))?no_esc}
</body>
</html>
diff --git a/themes/src/main/resources/theme/base/email/html/event-remove_totp.ftl b/themes/src/main/resources/theme/base/email/html/event-remove_totp.ftl
index e5ddadb..9a56ed3 100644
--- a/themes/src/main/resources/theme/base/email/html/event-remove_totp.ftl
+++ b/themes/src/main/resources/theme/base/email/html/event-remove_totp.ftl
@@ -1,5 +1,5 @@
<html>
<body>
-${msg("eventRemoveTotpBodyHtml",event.date, event.ipAddress)?no_esc}
+${kcSanitize(msg("eventRemoveTotpBodyHtml",event.date, event.ipAddress))?no_esc}
</body>
</html>
diff --git a/themes/src/main/resources/theme/base/email/html/event-update_password.ftl b/themes/src/main/resources/theme/base/email/html/event-update_password.ftl
index dd5fda3..27825c7 100644
--- a/themes/src/main/resources/theme/base/email/html/event-update_password.ftl
+++ b/themes/src/main/resources/theme/base/email/html/event-update_password.ftl
@@ -1,5 +1,5 @@
<html>
<body>
-${msg("eventUpdatePasswordBodyHtml",event.date, event.ipAddress)?no_esc}
+${kcSanitize(msg("eventUpdatePasswordBodyHtml",event.date, event.ipAddress))?no_esc}
</body>
</html>
diff --git a/themes/src/main/resources/theme/base/email/html/event-update_totp.ftl b/themes/src/main/resources/theme/base/email/html/event-update_totp.ftl
index 050abab..3ed37c3 100644
--- a/themes/src/main/resources/theme/base/email/html/event-update_totp.ftl
+++ b/themes/src/main/resources/theme/base/email/html/event-update_totp.ftl
@@ -1,5 +1,5 @@
<html>
<body>
-${msg("eventUpdateTotpBodyHtml",event.date, event.ipAddress)?no_esc}
+${kcSanitize(msg("eventUpdateTotpBodyHtml",event.date, event.ipAddress))?no_esc}
</body>
</html>
diff --git a/themes/src/main/resources/theme/base/email/html/executeActions.ftl b/themes/src/main/resources/theme/base/email/html/executeActions.ftl
index 6510dfc..4c837bc 100755
--- a/themes/src/main/resources/theme/base/email/html/executeActions.ftl
+++ b/themes/src/main/resources/theme/base/email/html/executeActions.ftl
@@ -4,6 +4,6 @@
<html>
<body>
-${msg("executeActionsBodyHtml",link, linkExpiration, realmName, requiredActionsText, linkExpirationFormatter(linkExpiration))?no_esc}
+${kcSanitize(msg("executeActionsBodyHtml",link, linkExpiration, realmName, requiredActionsText, linkExpirationFormatter(linkExpiration)))?no_esc}
</body>
</html>
diff --git a/themes/src/main/resources/theme/base/email/html/identity-provider-link.ftl b/themes/src/main/resources/theme/base/email/html/identity-provider-link.ftl
index fff38fc..8b67968 100644
--- a/themes/src/main/resources/theme/base/email/html/identity-provider-link.ftl
+++ b/themes/src/main/resources/theme/base/email/html/identity-provider-link.ftl
@@ -1,5 +1,5 @@
<html>
<body>
-${msg("identityProviderLinkBodyHtml", identityProviderAlias, realmName, identityProviderContext.username, link, linkExpiration, linkExpirationFormatter(linkExpiration))?no_esc}
+${kcSanitize(msg("identityProviderLinkBodyHtml", identityProviderAlias, realmName, identityProviderContext.username, link, linkExpiration, linkExpirationFormatter(linkExpiration)))?no_esc}
</body>
</html>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/email/html/password-reset.ftl b/themes/src/main/resources/theme/base/email/html/password-reset.ftl
index e56ae1e..b2840b6 100755
--- a/themes/src/main/resources/theme/base/email/html/password-reset.ftl
+++ b/themes/src/main/resources/theme/base/email/html/password-reset.ftl
@@ -1,5 +1,5 @@
<html>
<body>
-${msg("passwordResetBodyHtml",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration))?no_esc}
+${kcSanitize(msg("passwordResetBodyHtml",link, linkExpiration, realmName, linkExpirationFormatter(linkExpiration)))?no_esc}
</body>
</html>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/login/error.ftl b/themes/src/main/resources/theme/base/login/error.ftl
index 23468c5..f237f7e 100755
--- a/themes/src/main/resources/theme/base/login/error.ftl
+++ b/themes/src/main/resources/theme/base/login/error.ftl
@@ -6,7 +6,7 @@
<div id="kc-error-message">
<p class="instruction">${message.summary}</p>
<#if client?? && client.baseUrl?has_content>
- <p><a id="backToApplication" href="${client.baseUrl}">${msg("backToApplication")?no_esc}</a></p>
+ <p><a id="backToApplication" href="${client.baseUrl}">${kcSanitize(msg("backToApplication"))?no_esc}</a></p>
</#if>
</div>
</#if>
diff --git a/themes/src/main/resources/theme/base/login/info.ftl b/themes/src/main/resources/theme/base/login/info.ftl
index 8eff9c3..fc3a1aa 100755
--- a/themes/src/main/resources/theme/base/login/info.ftl
+++ b/themes/src/main/resources/theme/base/login/info.ftl
@@ -12,11 +12,11 @@
<#if skipLink??>
<#else>
<#if pageRedirectUri??>
- <p><a href="${pageRedirectUri}">${msg("backToApplication")?no_esc}</a></p>
+ <p><a href="${pageRedirectUri}">${kcSanitize(msg("backToApplication"))?no_esc}</a></p>
<#elseif actionUri??>
- <p><a href="${actionUri}">${msg("proceedWithAction")?no_esc}</a></p>
+ <p><a href="${actionUri}">${kcSanitize(msg("proceedWithAction"))?no_esc}</a></p>
<#elseif client.baseUrl??>
- <p><a href="${client.baseUrl}">${msg("backToApplication")?no_esc}</a></p>
+ <p><a href="${client.baseUrl}">${kcSanitize(msg("backToApplication"))?no_esc}</a></p>
</#if>
</#if>
</div>
diff --git a/themes/src/main/resources/theme/base/login/login-reset-password.ftl b/themes/src/main/resources/theme/base/login/login-reset-password.ftl
index 79481bc..720c274 100755
--- a/themes/src/main/resources/theme/base/login/login-reset-password.ftl
+++ b/themes/src/main/resources/theme/base/login/login-reset-password.ftl
@@ -16,7 +16,7 @@
<div class="${properties.kcFormGroupClass!} ${properties.kcFormSettingClass!}">
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
<div class="${properties.kcFormOptionsWrapperClass!}">
- <span><a href="${url.loginUrl}">${msg("backToLogin")?no_esc}</a></span>
+ <span><a href="${url.loginUrl}">${kcSanitize(msg("backToLogin"))?no_esc}</a></span>
</div>
</div>
diff --git a/themes/src/main/resources/theme/base/login/register.ftl b/themes/src/main/resources/theme/base/login/register.ftl
index fcf8aa9..c9378da 100755
--- a/themes/src/main/resources/theme/base/login/register.ftl
+++ b/themes/src/main/resources/theme/base/login/register.ftl
@@ -73,7 +73,7 @@
<div class="${properties.kcFormGroupClass!}">
<div id="kc-form-options" class="${properties.kcFormOptionsClass!}">
<div class="${properties.kcFormOptionsWrapperClass!}">
- <span><a href="${url.loginUrl}">${msg("backToLogin")?no_esc}</a></span>
+ <span><a href="${url.loginUrl}">${kcSanitize(msg("backToLogin"))?no_esc}</a></span>
</div>
</div>
diff --git a/themes/src/main/resources/theme/base/login/template.ftl b/themes/src/main/resources/theme/base/login/template.ftl
index a713071..a09fd8f 100644
--- a/themes/src/main/resources/theme/base/login/template.ftl
+++ b/themes/src/main/resources/theme/base/login/template.ftl
@@ -34,7 +34,7 @@
<body class="${properties.kcBodyClass!}">
<div class="${properties.kcLoginClass!}">
<div id="kc-header" class="${properties.kcHeaderClass!}">
- <div id="kc-header-wrapper" class="${properties.kcHeaderWrapperClass!}">${msg("loginTitleHtml",(realm.displayNameHtml!''))?no_esc}</div>
+ <div id="kc-header-wrapper" class="${properties.kcHeaderWrapperClass!}">${kcSanitize(msg("loginTitleHtml",(realm.displayNameHtml!'')))?no_esc}</div>
</div>
<div class="${properties.kcFormCardClass!} <#if displayWide>${properties.kcFormCardAccountClass!}</#if>">
<header class="${properties.kcFormHeaderClass!}">
@@ -63,7 +63,7 @@
<#if message.type = 'warning'><span class="${properties.kcFeedbackWarningIcon!}"></span></#if>
<#if message.type = 'error'><span class="${properties.kcFeedbackErrorIcon!}"></span></#if>
<#if message.type = 'info'><span class="${properties.kcFeedbackInfoIcon!}"></span></#if>
- <span class="kc-feedback-text">${message.summary?no_esc}</span>
+ <span class="kc-feedback-text">${kcSanitize(message.summary)?no_esc}</span>
</div>
</#if>
diff --git a/themes/src/main/resources/theme/base/login/terms.ftl b/themes/src/main/resources/theme/base/login/terms.ftl
index daed9ec..687b192 100755
--- a/themes/src/main/resources/theme/base/login/terms.ftl
+++ b/themes/src/main/resources/theme/base/login/terms.ftl
@@ -4,7 +4,7 @@
${msg("termsTitle")}
<#elseif section = "form">
<div id="kc-terms-text">
- ${msg("termsText")?no_esc}
+ ${kcSanitize(msg("termsText"))?no_esc}
</div>
<form class="form-actions" action="${url.loginAction}" method="POST">
<input class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="accept" id="kc-accept" type="submit" value="${msg("doAccept")}"/>