Allow skipping JSON prefix in MockMvc result matchers
JSON payloads are sometimes prepended with a static string prefix to prevent Cross Site Scripting Inclusion attacks (XSSI). Prior to this commit, doing so would fail the MockMvc `JsonPathResultMatchers` since they're considering the whole response as the JSON payload. This commit adds a new `JsonPathResultMatchers.prefix` method that configures the matchers to check for the presence of that string (i.e. fail if it's not there) and only consider the rest of the response body as the JSON payload for other assertions. Issue: SPR-13577
This commit is contained in:
parent
4a6c2dbb15
commit
7ae44c2565
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -16,12 +16,17 @@
|
|||
|
||||
package org.springframework.test.web.servlet.result;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.hamcrest.core.StringStartsWith;
|
||||
|
||||
import org.springframework.test.util.JsonPathExpectationsHelper;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Factory for assertions on the response content using
|
||||
|
|
@ -33,12 +38,14 @@ import org.springframework.test.web.servlet.ResultMatcher;
|
|||
* @author Rossen Stoyanchev
|
||||
* @author Craig Andrews
|
||||
* @author Sam Brannen
|
||||
* @author Brian Clozel
|
||||
* @since 3.2
|
||||
*/
|
||||
public class JsonPathResultMatchers {
|
||||
|
||||
private final JsonPathExpectationsHelper jsonPathHelper;
|
||||
|
||||
private String prefix;
|
||||
|
||||
/**
|
||||
* Protected constructor.
|
||||
|
|
@ -48,10 +55,23 @@ public class JsonPathResultMatchers {
|
|||
* @param args arguments to parameterize the {@code JsonPath} expression with,
|
||||
* using formatting specifiers defined in {@link String#format(String, Object...)}
|
||||
*/
|
||||
protected JsonPathResultMatchers(String expression, Object ... args) {
|
||||
protected JsonPathResultMatchers(String expression, Object... args) {
|
||||
this.jsonPathHelper = new JsonPathExpectationsHelper(expression, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the current {@code JsonPathResultMatchers} instance
|
||||
* to verify that the JSON payload is prepended with the given prefix.
|
||||
* <p>Use this method if the JSON payloads are prefixed to avoid
|
||||
* Cross Site Script Inclusion (XSSI) attacks.
|
||||
* @param prefix the string prefix prepended to the actual JSON payload
|
||||
* @since 4.3.0
|
||||
*/
|
||||
public JsonPathResultMatchers prefix(String prefix) {
|
||||
this.prefix = prefix;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Evaluate the JSON path expression against the response content and
|
||||
|
|
@ -61,7 +81,7 @@ public class JsonPathResultMatchers {
|
|||
return new ResultMatcher() {
|
||||
@Override
|
||||
public void match(MvcResult result) throws Exception {
|
||||
String content = result.getResponse().getContentAsString();
|
||||
String content = getContent(result);
|
||||
jsonPathHelper.assertValue(content, matcher);
|
||||
}
|
||||
};
|
||||
|
|
@ -75,7 +95,7 @@ public class JsonPathResultMatchers {
|
|||
return new ResultMatcher() {
|
||||
@Override
|
||||
public void match(MvcResult result) throws Exception {
|
||||
jsonPathHelper.assertValue(result.getResponse().getContentAsString(), expectedValue);
|
||||
jsonPathHelper.assertValue(getContent(result), expectedValue);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -91,7 +111,7 @@ public class JsonPathResultMatchers {
|
|||
return new ResultMatcher() {
|
||||
@Override
|
||||
public void match(MvcResult result) throws Exception {
|
||||
String content = result.getResponse().getContentAsString();
|
||||
String content = getContent(result);
|
||||
jsonPathHelper.exists(content);
|
||||
}
|
||||
};
|
||||
|
|
@ -108,7 +128,7 @@ public class JsonPathResultMatchers {
|
|||
return new ResultMatcher() {
|
||||
@Override
|
||||
public void match(MvcResult result) throws Exception {
|
||||
String content = result.getResponse().getContentAsString();
|
||||
String content = getContent(result);
|
||||
jsonPathHelper.doesNotExist(content);
|
||||
}
|
||||
};
|
||||
|
|
@ -128,7 +148,7 @@ public class JsonPathResultMatchers {
|
|||
return new ResultMatcher() {
|
||||
@Override
|
||||
public void match(MvcResult result) throws Exception {
|
||||
String content = result.getResponse().getContentAsString();
|
||||
String content = getContent(result);
|
||||
jsonPathHelper.assertValueIsEmpty(content);
|
||||
}
|
||||
};
|
||||
|
|
@ -148,7 +168,7 @@ public class JsonPathResultMatchers {
|
|||
return new ResultMatcher() {
|
||||
@Override
|
||||
public void match(MvcResult result) throws Exception {
|
||||
String content = result.getResponse().getContentAsString();
|
||||
String content = getContent(result);
|
||||
jsonPathHelper.assertValueIsNotEmpty(content);
|
||||
}
|
||||
};
|
||||
|
|
@ -163,7 +183,7 @@ public class JsonPathResultMatchers {
|
|||
return new ResultMatcher() {
|
||||
@Override
|
||||
public void match(MvcResult result) throws Exception {
|
||||
String content = result.getResponse().getContentAsString();
|
||||
String content = getContent(result);
|
||||
jsonPathHelper.assertValueIsString(content);
|
||||
}
|
||||
};
|
||||
|
|
@ -178,7 +198,7 @@ public class JsonPathResultMatchers {
|
|||
return new ResultMatcher() {
|
||||
@Override
|
||||
public void match(MvcResult result) throws Exception {
|
||||
String content = result.getResponse().getContentAsString();
|
||||
String content = getContent(result);
|
||||
jsonPathHelper.assertValueIsBoolean(content);
|
||||
}
|
||||
};
|
||||
|
|
@ -193,7 +213,7 @@ public class JsonPathResultMatchers {
|
|||
return new ResultMatcher() {
|
||||
@Override
|
||||
public void match(MvcResult result) throws Exception {
|
||||
String content = result.getResponse().getContentAsString();
|
||||
String content = getContent(result);
|
||||
jsonPathHelper.assertValueIsNumber(content);
|
||||
}
|
||||
};
|
||||
|
|
@ -207,7 +227,7 @@ public class JsonPathResultMatchers {
|
|||
return new ResultMatcher() {
|
||||
@Override
|
||||
public void match(MvcResult result) throws Exception {
|
||||
String content = result.getResponse().getContentAsString();
|
||||
String content = getContent(result);
|
||||
jsonPathHelper.assertValueIsArray(content);
|
||||
}
|
||||
};
|
||||
|
|
@ -222,10 +242,29 @@ public class JsonPathResultMatchers {
|
|||
return new ResultMatcher() {
|
||||
@Override
|
||||
public void match(MvcResult result) throws Exception {
|
||||
String content = result.getResponse().getContentAsString();
|
||||
String content = getContent(result);
|
||||
jsonPathHelper.assertValueIsMap(content);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String getContent(MvcResult result) throws UnsupportedEncodingException {
|
||||
String content = result.getResponse().getContentAsString();
|
||||
if (StringUtils.hasLength(this.prefix)) {
|
||||
try {
|
||||
String reason = String.format("Expected a JSON payload prefixed with \"%s\" but found: %s", this.prefix,
|
||||
StringUtils.quote(content.substring(0, this.prefix.length())));
|
||||
MatcherAssert.assertThat(reason, content, StringStartsWith.startsWith(this.prefix));
|
||||
return content.substring(this.prefix.length());
|
||||
}
|
||||
catch (StringIndexOutOfBoundsException oobe) {
|
||||
String message = "JSON prefix \"" + this.prefix + "\" not found, exception: ";
|
||||
throw new AssertionError(message + oobe.getMessage());
|
||||
}
|
||||
}
|
||||
else {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -29,6 +29,7 @@ import org.springframework.test.web.servlet.StubMvcResult;
|
|||
* @author Rossen Stoyanchev
|
||||
* @author Craig Andrews
|
||||
* @author Sam Brannen
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class JsonPathResultMatchersTests {
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ public class JsonPathResultMatchersTests {
|
|||
"'emptyString': '', " + //
|
||||
"'emptyArray': [], " + //
|
||||
"'emptyMap': {} " + //
|
||||
"}";
|
||||
"}";
|
||||
|
||||
private static final StubMvcResult stubMvcResult;
|
||||
|
||||
|
|
@ -57,7 +58,6 @@ public class JsonPathResultMatchersTests {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void value() throws Exception {
|
||||
new JsonPathResultMatchers("$.str").value("foo").match(stubMvcResult);
|
||||
|
|
@ -233,4 +233,42 @@ public class JsonPathResultMatchersTests {
|
|||
new JsonPathResultMatchers("$.arr").isString().match(stubMvcResult);
|
||||
}
|
||||
|
||||
@Test(expected = AssertionError.class)
|
||||
public void valueWithJsonPrefixNotConfigured() throws Exception {
|
||||
String jsonPrefix = "prefix";
|
||||
StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix);
|
||||
new JsonPathResultMatchers("$.str").value("foo").match(result);
|
||||
}
|
||||
|
||||
@Test(expected = AssertionError.class)
|
||||
public void valueWithJsonWrongPrefix() throws Exception {
|
||||
String jsonPrefix = "prefix";
|
||||
StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix);
|
||||
new JsonPathResultMatchers("$.str").prefix("wrong").value("foo").match(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void valueWithJsonPrefix() throws Exception {
|
||||
String jsonPrefix = "prefix";
|
||||
StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix);
|
||||
new JsonPathResultMatchers("$.str").prefix(jsonPrefix).value("foo").match(result);
|
||||
}
|
||||
|
||||
@Test(expected = AssertionError.class)
|
||||
public void prefixWithPayloadNotLongEnough() throws Exception {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
response.addHeader("Content-Type", "application/json");
|
||||
response.getWriter().print(new String("test".getBytes("ISO-8859-1")));
|
||||
StubMvcResult result = new StubMvcResult(null, null, null, null, null, null, response);
|
||||
|
||||
new JsonPathResultMatchers("$.str").prefix("prefix").value("foo").match(result);
|
||||
}
|
||||
|
||||
private StubMvcResult createPrefixedStubMvcResult(String jsonPrefix) throws Exception {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
response.addHeader("Content-Type", "application/json");
|
||||
response.getWriter().print(jsonPrefix + new String(RESPONSE_CONTENT.getBytes("ISO-8859-1")));
|
||||
return new StubMvcResult(null, null, null, null, null, null, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue