Skip to content

Commit

Permalink
[RESTEASY-2033 and RESTEASY-2034] Changes to
Browse files Browse the repository at this point in the history
MessageSanitizerContainerResponseFilter

1. It handles MediaType.
2. It can be disabled.

[RESTEASY-2033 and RESTEASY-2034] Minor correction.
  • Loading branch information
ronsigal authored and asoldano committed Oct 18, 2018
1 parent bd8ea91 commit 41f164f
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 0 deletions.
14 changes: 14 additions & 0 deletions docbook/reference/en/en-US/modules/Installation_Configuration.xml
Expand Up @@ -753,6 +753,20 @@ public class MyApplication extends Application
for discussion.
</entry>
</row>
<row>
<entry>
resteasy.disable.html.sanitizer
</entry>
<entry>
false
</entry>
<entry>
Normally, a response with media type "text/html" and a status of 400 will be processed
so that the characters "/", "&lt;", "&gt;", "&amp;", """ (double quote), and "'" (single quote)
are escaped to prevent an XSS attack. If this parameter is set to "true", escaping
will not occur.
</entry>
</row>
</tbody>
</tgroup>
</table>
Expand Down
Expand Up @@ -9,7 +9,12 @@
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
import org.jboss.resteasy.spi.ResteasyDeployment;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.util.HttpResponseCodes;

/**
Expand All @@ -26,6 +31,15 @@ public class MessageSanitizerContainerResponseFilter implements ContainerRespons
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {

ResteasyDeployment deployment = ResteasyProviderFactory.getContextData(ResteasyDeployment.class);
if (deployment != null)
{
Boolean disable = (Boolean) deployment.getProperty(ResteasyContextParameters.RESTEASY_DISABLE_HTML_SANITIZER);
if (disable != null && disable)
{
return;
}
}
if (HttpResponseCodes.SC_BAD_REQUEST == responseContext.getStatus()) {
Object entity = responseContext.getEntity();
if (entity != null && entity instanceof String) {
Expand Down Expand Up @@ -76,6 +90,9 @@ private String escapeXml(String str) {

private boolean containsHtmlText(ArrayList<Object> list) {
for (Object o : list) {
if (o instanceof MediaType && MediaType.TEXT_HTML_TYPE.isCompatible((MediaType) o)) {
return true;
}
if (o instanceof String) {
String mediaType = (String) o;
String[] partsType = mediaType.split("/");
Expand Down
Expand Up @@ -253,6 +253,13 @@ public ResteasyDeployment createDeployment()
deployment.setAddCharset(add);
}

String disableHtmlSanitizer = getParameter(ResteasyContextParameters.RESTEASY_DISABLE_HTML_SANITIZER);
if (disableHtmlSanitizer != null)
{
boolean b = parseBooleanParam(ResteasyContextParameters.RESTEASY_DISABLE_HTML_SANITIZER, disableHtmlSanitizer);
deployment.setProperty(ResteasyContextParameters.RESTEASY_DISABLE_HTML_SANITIZER, b);
}

String injectorFactoryClass = getParameter("resteasy.injector.factory");
if (injectorFactoryClass != null)
{
Expand Down
Expand Up @@ -37,6 +37,7 @@ public interface ResteasyContextParameters
String RESTEASY_GZIP_MAX_INPUT = "resteasy.gzip.max.input";
String RESTEASY_SECURE_RANDOM_MAX_USE = "resteasy.secure.random.max.use";
String RESTEASY_ADD_CHARSET = "resteasy.add.charset";
String RESTEASY_DISABLE_HTML_SANITIZER = "resteasy.disable.html.sanitizer";

// these scanned variables are provided by a deployer
String RESTEASY_SCANNED_RESOURCES = "resteasy.scanned.resources";
Expand Down
Expand Up @@ -27,6 +27,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
* This class is used to configure and initialize the core components of RESTEasy.
Expand Down Expand Up @@ -77,6 +78,7 @@ public class ResteasyDeployment
protected ResteasyProviderFactory providerFactory;
protected ThreadLocalResteasyProviderFactory threadLocalProviderFactory;
protected String paramMapping;
private Map<String, Object> properties = new TreeMap<String, Object>();

@SuppressWarnings(value = "unchecked")
public void start()
Expand Down Expand Up @@ -1023,4 +1025,12 @@ public void setInjectorFactory(InjectorFactory injectorFactory)
{
this.injectorFactory = injectorFactory;
}

public Object getProperty(String key) {
return properties.get(key);
}

public void setProperty(String key, Object value) {
properties.put(key, value);
}
}
@@ -0,0 +1,108 @@
package org.jboss.resteasy.test.providers.html;

import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Response;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.test.providers.html.resource.HtmlSanitizerOptionalResource;
import org.jboss.resteasy.utils.PortProviderUtil;
import org.jboss.resteasy.utils.TestUtil;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
* @tpSubChapter
* @tpChapter Integration tests
* @tpTestCaseDetails Regression test for RESTEASY-2034
* @tpSince RESTEasy 4.0.0
*/
@RunWith(Arquillian.class)
@RunAsClient
public class HtmlSanitizerOptionalTest {

static ResteasyClient client;

private static final String ENABLED = "_enabled";
private static final String DISABLED = "_disabled";
private static final String DEFAULT = "_default";

static public final String input = "<html &lt;\"abc\" 'xyz'&gt;/>";
static private final String output = "&lt;html &amp;lt;&quot;abc&quot; &#x27;xyz&#x27;&amp;gt;&#x2F;&gt;";

@Deployment(name = ENABLED, order = 1)
public static Archive<?> createTestArchive1() {
WebArchive war = TestUtil.prepareArchive(HtmlSanitizerOptionalTest.class.getSimpleName() + ENABLED);
war.addAsWebInfResource(HtmlSanitizerOptionalTest.class.getPackage(), "HtmlSanitizerOptional_Enabled_web.xml", "web.xml");
return TestUtil.finishContainerPrepare(war, null, HtmlSanitizerOptionalResource.class);
}

@Deployment(name = DISABLED, order = 2)
public static Archive<?> createTestArchive2() {
WebArchive war = TestUtil.prepareArchive(HtmlSanitizerOptionalTest.class.getSimpleName() + DISABLED);
war.addAsWebInfResource(HtmlSanitizerOptionalTest.class.getPackage(), "HtmlSanitizerOptional_Disabled_web.xml", "web.xml");
return TestUtil.finishContainerPrepare(war, null, HtmlSanitizerOptionalResource.class);
}

@Deployment(name = DEFAULT, order = 3)
public static Archive<?> createTestArchive3() {
WebArchive war = TestUtil.prepareArchive(HtmlSanitizerOptionalTest.class.getSimpleName() + DEFAULT);
war.addAsWebInfResource(HtmlSanitizerOptionalTest.class.getPackage(), "HtmlSanitizerOptional_Default_web.xml", "web.xml");
return TestUtil.finishContainerPrepare(war, null, HtmlSanitizerOptionalResource.class);
}

private String generateURL(String path, String version) {
return PortProviderUtil.generateURL(path, HtmlSanitizerOptionalTest.class.getSimpleName() + version);
}

@Before
public void init() {
client = (ResteasyClient)ClientBuilder.newClient();
}

@After
public void after() throws Exception {
client.close();
}

/**
* @tpTestDetails Context parameter "resteasy.disable.html.sanitizer" is set to "true".
* @tpPassCrit Input string should be unchanged.
* @tpSince RESTEasy 4.0.0
*/
@Test
public void testHtmlSanitizerDisabled() throws Exception {
Response response = client.target(generateURL("/test", DISABLED)).request().get();
Assert.assertEquals(input, response.readEntity(String.class));
}

/**
* @tpTestDetails Context parameter "resteasy.disable.html.sanitizer" is set to "false"
* @tpPassCrit Input string should be sanitized.
* @tpSince RESTEasy 4.0.0
*/
@Test
public void testHtmlSanitizerEnabled() throws Exception {
Response response = client.target(generateURL("/test", ENABLED)).request().get();
Assert.assertEquals(output, response.readEntity(String.class));
}

/**
* @tpTestDetails Context parameter "resteasy.disable.html.sanitizer" is not set.
* @tpPassCrit Input string should be sanitized.
* @tpSince RESTEasy 4.0.0
*/
@Test
public void testHtmlSanitizerDefault() throws Exception {
Response response = client.target(generateURL("/test", DEFAULT)).request().get();
Assert.assertEquals(output, response.readEntity(String.class));
}
}
@@ -0,0 +1,20 @@
package org.jboss.resteasy.test.providers.html.resource;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;

import org.jboss.resteasy.test.providers.html.HtmlSanitizerOptionalTest;
import org.jboss.resteasy.util.HttpResponseCodes;

@Path("")
public class HtmlSanitizerOptionalResource {

@Path("test")
@GET
@Produces("text/html")
public Response test() {
return Response.status(HttpResponseCodes.SC_BAD_REQUEST).entity(HtmlSanitizerOptionalTest.input).build();
}
}
@@ -0,0 +1,24 @@
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

<display-name>HtmlSanitizerOptionalTest_Default</display-name>

<servlet>
<servlet-name>Resteasy</servlet-name>

<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.jboss.resteasy.utils.TestApplication</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

</web-app>
@@ -0,0 +1,29 @@
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

<display-name>HtmlSanitizerOptionalTest_Disabled</display-name>

<context-param>
<param-name>resteasy.disable.html.sanitizer</param-name>
<param-value>true</param-value>
</context-param>

<servlet>
<servlet-name>Resteasy</servlet-name>

<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.jboss.resteasy.utils.TestApplication</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

</web-app>
@@ -0,0 +1,29 @@
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

<display-name>HtmlSanitizerOptionalTest_Disabled</display-name>

<context-param>
<param-name>resteasy.disable.html.sanitizer</param-name>
<param-value>false</param-value>
</context-param>

<servlet>
<servlet-name>Resteasy</servlet-name>

<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.jboss.resteasy.utils.TestApplication</param-value>
</init-param>
</servlet>

<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

</web-app>
@@ -0,0 +1,51 @@
package org.jboss.resteasy.test.interception;

import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.interception.ContainerResponseContextImpl;
import org.jboss.resteasy.plugins.interceptors.encoding.MessageSanitizerContainerResponseFilter;
import org.jboss.resteasy.specimpl.BuiltResponse;
import org.jboss.resteasy.util.HttpResponseCodes;
import org.junit.Assert;
import org.junit.Test;


/**
* @tpSubChapter Providers - MessageSanitizerContainerResponseFilter
* @tpChapter Unit tests
* @tpTestCaseDetails Regression test for RESTEASY-2033
* @tpSince RESTEasy 4.0.0
*/
public class MessageSanitizerMediaTypeTest {

static private final String input = "<html &lt;\"abc\" 'xyz'&gt;/>";
static private final String output = "&lt;html &amp;lt;&quot;abc&quot; &#x27;xyz&#x27;&amp;gt;&#x2F;&gt;";

public static class TestContainerResponseContext extends ContainerResponseContextImpl {
public TestContainerResponseContext(BuiltResponse builtResponse) {
super(null, null, builtResponse, null, null, null, null);
}
}

@Test
public void testMessageSanitizerText() throws Exception {
doTestMessageSanitizerMediaType("text/html");
}

@Test
public void testMessageSanitizerMediaType() throws Exception {
doTestMessageSanitizerMediaType(MediaType.TEXT_HTML_TYPE);
}

void doTestMessageSanitizerMediaType(Object mediaType) throws Exception {
Headers<Object> headers = new Headers<Object>();
headers.add("Content-Type", mediaType);
BuiltResponse response = new BuiltResponse(HttpResponseCodes.SC_BAD_REQUEST, "", headers, input, null);
ContainerResponseContext responseContext = new TestContainerResponseContext(response);
MessageSanitizerContainerResponseFilter filter = new MessageSanitizerContainerResponseFilter();
filter.filter(null, responseContext);
Assert.assertEquals(output, responseContext.getEntity());
}
}

0 comments on commit 41f164f

Please sign in to comment.