/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.gwc;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasProperty;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.logging.Logger;
import javax.xml.namespace.QName;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.data.test.CiteTestData;
import org.geoserver.data.test.MockData;
import org.geoserver.data.test.SystemTestData;
import org.geoserver.data.test.SystemTestData.LayerProperty;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.security.CatalogMode;
import org.geoserver.security.CoverageAccessLimits;
import org.geoserver.security.DataAccessLimits;
import org.geoserver.security.GeoServerRoleStore;
import org.geoserver.security.GeoServerUserGroupStore;
import org.geoserver.security.ResourceAccessManager;
import org.geoserver.security.TestResourceAccessManager;
import org.geoserver.security.impl.AbstractUserGroupService;
import org.geoserver.security.impl.GeoServerRole;
import org.geoserver.wms.WMSTestSupport;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.util.logging.Logging;
import org.geowebcache.service.ve.VEConverter;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.springframework.mock.web.MockHttpServletResponse;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.io.WKTReader;
/**
* Performs integration tests using a mock {@link ResourceAccessManager}
*
* @author Niels Charlier, Scitus Development
*/
public class GWCDataSecurityTest extends WMSTestSupport {
static final Logger LOGGER = Logging.getLogger(GWCDataSecurityTest.class);
/**
* Add the test resource access manager in the spring context
*/
@Override
protected void setUpSpring(List<String> springContextLocations) {
super.setUpSpring(springContextLocations);
springContextLocations.add("classpath:/org/geoserver/wms/ResourceAccessManagerContext.xml");
}
/**
* Enable the Spring Security auth filters
*/
@Override
protected List<javax.servlet.Filter> getFilters() {
return Collections.singletonList((javax.servlet.Filter) GeoServerExtensions
.bean("filterChainProxy"));
}
@Override
protected void onSetUp(SystemTestData testData) throws Exception {
super.onSetUp(testData);
GWC.get().getConfig().setSecurityEnabled(true);
testData.addStyle("raster","raster.sld",SystemTestData.class,getCatalog());
Map properties = new HashMap();
properties.put(LayerProperty.STYLE, "raster");
testData.addRasterLayer(new QName(MockData.SF_URI, "mosaic", MockData.SF_PREFIX),
"raster-filter-test.zip",null, properties, SystemTestData.class, getCatalog());
testData.addRasterLayer(new QName(MockData.SF_URI, "Mosaic2", MockData.SF_PREFIX),
"raster-filter-test.zip",null, properties, SystemTestData.class, getCatalog());
CoverageInfo sfMosaicCI = getCatalog().getCoverageByName("sf:mosaic");
sfMosaicCI.setNativeBoundingBox(CiteTestData.DEFAULT_LATLON_ENVELOPE);
getCatalog().save(sfMosaicCI);
GeoServerUserGroupStore ugStore= getSecurityManager().
loadUserGroupService(AbstractUserGroupService.DEFAULT_NAME).createStore();
ugStore.addUser(ugStore.createUserObject("cite", "cite", true));
ugStore.addUser(ugStore.createUserObject("cite_mosaic2", "cite", true));
ugStore.addUser(ugStore.createUserObject("cite_nomosaic", "cite", true));
ugStore.addUser(ugStore.createUserObject("cite_cropmosaic", "cite", true));
ugStore.addUser(ugStore.createUserObject("cite_filtermosaic", "cite", true));
ugStore.addUser(ugStore.createUserObject("cite_nogroup", "cite", true));
ugStore.store();
GeoServerRoleStore roleStore= getSecurityManager().getActiveRoleService().createStore();
GeoServerRole role = roleStore.createRoleObject("ROLE_DUMMY");
roleStore.addRole(role);
roleStore.associateRoleToUser(role, "cite");
roleStore.associateRoleToUser(role, "cite_mosaic2");
roleStore.associateRoleToUser(role, "cite_nogroup");
roleStore.associateRoleToUser(role, "cite_nomosaic");
roleStore.associateRoleToUser(role, "cite_cropmosaic");
roleStore.associateRoleToUser(role, "cite_filtermosaic");
roleStore.store();
// populate the access manager
Catalog catalog = getCatalog();
TestResourceAccessManager tam = (TestResourceAccessManager) applicationContext
.getBean("testResourceAccessManager");
CoverageInfo coverage = catalog.getCoverageByName("sf:mosaic");
CoverageInfo coverage2 = catalog.getCoverageByName("sf:Mosaic2");
// set permissions on layer coverage
tam.putLimits("cite_mosaic2", coverage, new DataAccessLimits(CatalogMode.HIDE, Filter.EXCLUDE));
tam.putLimits("cite", coverage, new DataAccessLimits(CatalogMode.HIDE, Filter.INCLUDE));
// set permissions on layer coverage2
tam.putLimits("cite", coverage2, new DataAccessLimits(CatalogMode.CHALLENGE, Filter.EXCLUDE));
tam.putLimits("cite_mosaic2", coverage2, new DataAccessLimits(CatalogMode.CHALLENGE, Filter.INCLUDE));
//layer disable
tam.putLimits("cite_nomosaic", coverage, new CoverageAccessLimits(CatalogMode.HIDE, Filter.EXCLUDE, null, null));
// image cropping setup
WKTReader wkt = new WKTReader();
MultiPolygon cropper = (MultiPolygon) wkt.read("MULTIPOLYGON(((140 -50, 150 -50, 150 -30, 140 -30, 140 -50)))");
tam.putLimits("cite_cropmosaic", coverage, new CoverageAccessLimits(CatalogMode.HIDE, Filter.INCLUDE, cropper, null));
// filter setup
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);
Filter filter = ff.contains(ff.property("geometry"), ff.literal(cropper));
tam.putLimits("cite_filtermosaic", coverage, new CoverageAccessLimits(CatalogMode.HIDE, filter, null, null));
}
@Test
public void testNoMosaic() throws Exception {
GWC.get().getConfig().setSecurityEnabled(true);
//first to cache
setRequestAuth("cite", "cite");
String path = "gwc/service/wms?bgcolor=0x000000&LAYERS=sf:mosaic&STYLES=&FORMAT=image/png&SERVICE=WMS&VERSION=1.1.1" +
"&REQUEST=GetMap&SRS=EPSG:4326&BBOX=0,-90,180,90&WIDTH=256&HEIGHT=256&transparent=false";
MockHttpServletResponse response = getAsServletResponse(path);
assertEquals("image/png", response.getContentType() );
// try again, now should be cached
response = getAsServletResponse(path);
assertEquals("image/png", response.getContentType());
//try now as different user
setRequestAuth("cite_nomosaic", "cite");
response = getAsServletResponse(path);
assertEquals("application/xml", response.getContentType());
String str = string(getBinaryInputStream(response));
assertTrue(str.contains("org.geotools.ows.ServiceException: Could not find layer sf:mosaic"));
}
@Test
public void testPermissionMosaicTileWmts() throws Exception {
doPermissionMosaicTileTest(
(layer)->String.format("gwc/service/wmts?LAYER=%s&FORMAT=image/png&SERVICE=WMTS&VERSION=1.0.0" +
"&REQUEST=GetTile&TILEMATRIXSET=EPSG:900913&TILEMATRIX=EPSG:900913:0&TILECOL=0&TILEROW=0", layer),
"application/xml");
}
Matcher<MockHttpServletResponse> hasBody(Matcher<String> matcher) {
return new org.hamcrest.BaseMatcher<MockHttpServletResponse>() {
@Override
public boolean matches(Object item) {
try {
return matcher.matches(string(getBinaryInputStream((MockHttpServletResponse) item)));
} catch (Exception e) {
return false;
}
}
@Override
public void describeTo(Description description) {
description
.appendText("HTTP Response with body ")
.appendDescriptionOf(matcher);
}
@Override
public void describeMismatch(Object item, Description description) {
description.appendText("body was: \n");
try {
description.appendValue(string(getBinaryInputStream((MockHttpServletResponse) item)));
} catch (Exception e) {
description.appendText("\tcould not read body ").appendValue(e.getMessage());
}
}
};
}
Matcher<MockHttpServletResponse> addBodyOnFail(Matcher<? extends MockHttpServletResponse> matcher) {
return new org.hamcrest.BaseMatcher<MockHttpServletResponse>() {
@Override
public boolean matches(Object item) {
return matcher.matches(item);
}
@Override
public void describeTo(Description description) {
matcher.describeTo(description);
}
@Override
public void describeMismatch(Object item, Description description) {
matcher.describeMismatch(item, description);
String body;
if(((MockHttpServletResponse)item).getContentType().startsWith("image")) {
description.appendText("\n was an image");
}
try {
body = string(getBinaryInputStream((MockHttpServletResponse) item));
description.appendText("\n body:").appendValue(body);
} catch (Exception e) {
description.appendText("\n could not get body:").appendValue(e.getMessage());
}
}
};
}
enum TestGridset {
GlobalCRS84Geometric("EPSG:4326", new long[]{7489,1245,12}, new long[]{4096,2048,12}),
GoogleMapsCompatible("EPSG:900913", new long[]{7489,3237,13}, new long[]{4096,4096,13});
public final String name;
public final long[] tileInBounds;
public final long[] tileOutOfBounds;
private TestGridset(String name, long[] tileInBounds, long[] tileOutOfBounds) {
this.name = name;
this.tileInBounds = tileInBounds;
this.tileOutOfBounds = tileOutOfBounds;
}
}
protected void doPermissionCropTileTest(BiFunction<String, long[], String> pathForLayer, String failFormat, TestGridset grid) throws Exception {
final String tileFormat = "image/png";
// final String pathInBounds = pathForLayer.apply("sf:mosaic", new long[]{4073,4118,13});
// final String pathOutOfBounds = pathForLayer.apply("sf:mosaic", new long[]{4073,4117,13});
final String pathInBounds = pathForLayer.apply("sf:mosaic", grid.tileInBounds);
final String pathOutOfBounds = pathForLayer.apply("sf:mosaic", grid.tileOutOfBounds);
GWC.get().getConfig().setSecurityEnabled(true);
setRequestAuth("cite", "cite");
// Add out of bounds tile to the cache
MockHttpServletResponse response = getAsServletResponse(pathOutOfBounds);
assertThat(response, addBodyOnFail(hasProperty("contentType", equalTo(tileFormat)))); // If this was unsuccessful the test is invalid
setRequestAuth("cite_cropmosaic", "cite");
// Request out of bounds should fail
response = getAsServletResponse(pathOutOfBounds);
assertThat(response, allOf(
hasProperty("contentType", equalTo(failFormat)),
hasBody(Matchers.containsString("Access denied to bounding box on layer sf:mosaic"))
));
// Request within bounds should work
response = getAsServletResponse(pathInBounds);
assertThat(response, addBodyOnFail(hasProperty("contentType", equalTo(tileFormat))));
}
protected void doPermissionMosaicTileTest(Function<String, String> pathForLayer, String failFormat) throws Exception {
final String tileFormat = "image/png";
final String path = pathForLayer.apply("sf:mosaic");
final String path2 = pathForLayer.apply("sf:Mosaic2");
GWC.get().getConfig().setSecurityEnabled(true);
// First make sure the tiles are cached
setRequestAuth("cite", "cite");
MockHttpServletResponse response = getAsServletResponse(path);
assertEquals(tileFormat, response.getContentType() );
// try again, now should be cached
response = getAsServletResponse(path);
assertEquals(tileFormat, response.getContentType());
// The second layer
setRequestAuth("cite_mosaic2", "cite");
response = getAsServletResponse(path2);
assertEquals(tileFormat, response.getContentType() );
// try again, now should be cached
response = getAsServletResponse(path2);
assertEquals(tileFormat, response.getContentType());
// permission must be denied to cite user
setRequestAuth("cite", "cite");
response = getAsServletResponse(path2);
assertEquals(failFormat, response.getContentType() );
String str = string(getBinaryInputStream(response));
// mode challenge
assertTrue(str.contains("Access denied to bounding box on layer sf:Mosaic2"));
//try now as cite_mosaic2 user permission on sf:mosaic must be denied
setRequestAuth("cite_mosaic2", "cite");
response = getAsServletResponse(path);
assertEquals(failFormat, response.getContentType());
str = string(getBinaryInputStream(response));
// mode hide
assertTrue(str.contains("Could not find layer sf:mosaic"));
}
@Test
public void testPermissionMosaicTileGmaps() throws Exception {
doPermissionMosaicTileTest(
(layer)->String.format("gwc/service/gmaps?LAYERS=%s&FORMAT=image/png&ZOOM=0&X=0&Y=0", layer),
"application/xml");
}
@Test
public void testPermissionMosaicTileMGmaps() throws Exception {
doPermissionMosaicTileTest(
(layer)->String.format("gwc/service/mgmaps?LAYERS=%s&FORMAT=image/png&ZOOM=17&X=0&Y=0", layer),
"application/xml");
}
@Test
public void testPermissionMosaicTileTms() throws Exception {
doPermissionMosaicTileTest(
(layer)->String.format("gwc/service/tms/1.0.0/%s@EPSG:900913@png/0/0/0.png", layer),
"application/xml");
}
@Test
public void testPermissionMosaicTileKml() throws Exception {
doPermissionMosaicTileTest(
(layer)->String.format("gwc/service/kml/%s/x0y0z0.png", layer),
"application/xml");
}
@Test
public void testPermissionCropTileTms() throws Exception {
doPermissionCropTileTest(
(layer, index)->String.format("gwc/service/tms/1.0.0/%s@EPSG:900913@png/%d/%d/%d.png", layer, index[2], index[0], index[1]),
"application/xml",
TestGridset.GoogleMapsCompatible);
}
@Test
public void testPermissionCropTileWmts() throws Exception {
doPermissionCropTileTest(
(layer, index)->String.format("gwc/service/wmts?LAYER=%s&FORMAT=image/png&SERVICE=WMTS&VERSION=1.0.0" +
"&REQUEST=GetTile&TILEMATRIXSET=EPSG:900913&TILEMATRIX=EPSG:900913:%d&TILECOL=%d&TILEROW=%d", layer, index[2], index[0], (1<<index[2])-index[1]-1),
"application/xml",
TestGridset.GoogleMapsCompatible);
}
@Test
public void testPermissionCropTileGmaps() throws Exception {
doPermissionCropTileTest(
(layer, index)->String.format("gwc/service/gmaps?LAYERS=%s&FORMAT=image/png&ZOOM=%d&X=%d&Y=%d", layer, index[2], index[0], (1<<index[2])-index[1]-1),
"application/xml",
TestGridset.GoogleMapsCompatible);
}
@Test
public void testPermissionCropTileMGmaps() throws Exception {
doPermissionCropTileTest(
(layer, index)->String.format("gwc/service/mgmaps?LAYERS=%s&FORMAT=image/png&ZOOM=%d&X=%d&Y=%d", layer, 17-index[2], index[0], (1<<index[2])-index[1]-1),
"application/xml",
TestGridset.GoogleMapsCompatible);
}
@Test
public void testPermissionCropTileKml() throws Exception {
doPermissionCropTileTest(
(layer, index)->String.format("gwc/service/kml/%s/x%dy%dz%d.png", layer, index[0], index[1], index[2]),
"application/xml",
TestGridset.GlobalCRS84Geometric);
}
@Test
public void testPermissionCropTileBing() throws Exception {
doPermissionCropTileTest(
(layer, index)->{
long col = index[0];
long row = (1<<index[2])-index[1]-1;
long zoom = index[2];
long key = 0;
for (int i = 0; i<zoom; i++) {
key |= (col&(1<<i))!=0?(1<<(i*2)):0;
key |= (row&(1<<i))!=0?(1<<(i*2+1)):0;
}
String quadKey = Long.toString(key, 4);
assertThat(VEConverter.convert(quadKey), equalTo(index)); // Check that the test key is correct. Failure means the test is broken
return String.format("gwc/service/ve?layers=%s&format=image/png&quadKey=%s", layer, quadKey);
},
"application/xml",
TestGridset.GoogleMapsCompatible);
}
protected void doPermissionKmlOverlay(Function<String, String> pathForLayer, String overlayFormat) throws Exception {
final String failFormat = "application/xml";
final String path = pathForLayer.apply("sf:mosaic");
final String path2 = pathForLayer.apply("sf:Mosaic2");
GWC.get().getConfig().setSecurityEnabled(true);
// Test that we have access when we should
setRequestAuth("cite", "cite");
MockHttpServletResponse response = getAsServletResponse(path);
assertThat(response,
addBodyOnFail(hasProperty("contentType", equalTo(overlayFormat))));
setRequestAuth("cite_mosaic2", "cite");
response = getAsServletResponse(path2);
assertThat(response,
addBodyOnFail(hasProperty("contentType", equalTo(overlayFormat))));
// permission must be denied to cite user
setRequestAuth("cite", "cite");
response = getAsServletResponse(path2);
assertThat(response, allOf(
hasProperty("contentType", equalTo(failFormat)),
hasBody(Matchers.containsString("Access denied to bounding box on layer sf:Mosaic2"))
));
//try now as cite_mosaic2 user permission on sf:mosaic must be denied
setRequestAuth("cite_mosaic2", "cite");
response = getAsServletResponse(path);
assertThat(response, allOf(
hasProperty("contentType", equalTo(failFormat)),
hasBody(Matchers.containsString("Could not find layer sf:mosaic"))
));
// Check that geofencing blocks access to the overlay
setRequestAuth("cite_cropmosaic", "cite");
response = getAsServletResponse(path);
assertThat(response, allOf(
hasProperty("contentType", equalTo(failFormat)),
hasBody(Matchers.containsString("Access denied to bounding box on layer sf:mosaic"))
));
}
@Test
public void testPermissionMosaicKmlRasterSuperOverlay() throws Exception {
doPermissionKmlOverlay(
(layer)->String.format("gwc/service/kml/%s.png.kmz", layer),
"application/xml");
}
@Test
public void testPermissionMosaicKmlVectorSuperOverlay() throws Exception {
doPermissionKmlOverlay(
(layer)->String.format("gwc/service/kml/%s.kml.kmz", layer),
"application/xml");
}
@Test
public void testCroppedMosaic() throws Exception {
//first to cache
setRequestAuth("cite", "cite");
String path = "gwc/service/wms?bgcolor=0x000000&LAYERS=sf:mosaic&STYLES=&FORMAT=image/png&SERVICE=WMS&VERSION=1.1.1" +
"&REQUEST=GetMap&SRS=EPSG:4326&BBOX=0,-90,180,90&WIDTH=256&HEIGHT=256&transparent=false";
MockHttpServletResponse response = getAsServletResponse(path);
assertEquals("image/png", response.getContentType());
// this should fail
setRequestAuth("cite_cropmosaic", "cite");
path = "gwc/service/wms?bgcolor=0x000000&LAYERS=sf:mosaic&STYLES=&FORMAT=image/png&SERVICE=WMS&VERSION=1.1.1" +
"&REQUEST=GetMap&SRS=EPSG:4326&BBOX=0,-90,180,90&WIDTH=256&HEIGHT=256&transparent=false";
response = getAsServletResponse(path);
assertEquals("application/xml", response.getContentType());
String str = string(getBinaryInputStream(response));
assertTrue(str.contains("org.geotools.ows.ServiceException: Access denied to bounding box on layer sf:mosaic"));
//but this should be fine
path = "gwc/service/wms?bgcolor=0x000000&LAYERS=sf:mosaic&STYLES=&FORMAT=image/png&SERVICE=WMS&VERSION=1.1.1" +
"&REQUEST=GetMap&SRS=EPSG:4326&BBOX=143.4375,-42.1875,146.25,-39.375&WIDTH=256&HEIGHT=256&transparent=false";
response = getAsServletResponse(path);
assertEquals("image/png", response.getContentType());
}
@Test
public void testFilterMosaic() throws Exception {
//first to cache
setRequestAuth("cite", "cite");
String path = "gwc/service/wms?bgcolor=0x000000&LAYERS=sf:mosaic&STYLES=&FORMAT=image/png&SERVICE=WMS&VERSION=1.1.1" +
"&REQUEST=GetMap&SRS=EPSG:4326&BBOX=0,-90,180,90&WIDTH=256&HEIGHT=256&transparent=false";
MockHttpServletResponse response = getAsServletResponse(path);
assertEquals("image/png", response.getContentType());
// this should fail
setRequestAuth("cite_filtermosaic", "cite");
path = "gwc/service/wms?bgcolor=0x000000&LAYERS=sf:mosaic&STYLES=&FORMAT=image/png&SERVICE=WMS&VERSION=1.1.1" +
"&REQUEST=GetMap&SRS=EPSG:4326&BBOX=0,-90,180,90&WIDTH=256&HEIGHT=256&transparent=false";
response = getAsServletResponse(path);
assertEquals("application/xml", response.getContentType());
String str = string(getBinaryInputStream(response));
assertTrue(str.contains("org.geotools.ows.ServiceException: Access denied to bounding box on layer sf:mosaic"));
//but this should be fine
path = "gwc/service/wms?bgcolor=0x000000&LAYERS=sf:mosaic&STYLES=&FORMAT=image/png&SERVICE=WMS&VERSION=1.1.1" +
"&REQUEST=GetMap&SRS=EPSG:4326&BBOX=143.4375,-42.1875,146.25,-39.375&WIDTH=256&HEIGHT=256&transparent=false";
response = getAsServletResponse(path);
assertEquals("image/png", response.getContentType());
}
@Test
public void testLayerGroup() throws Exception {
// no auth, it should work
setRequestAuth(null, null);
String path = "gwc/service/wms?bgcolor=0x000000&LAYERS=" + NATURE_GROUP + "&STYLES=&FORMAT=image/png&SERVICE=WMS&VERSION=1.1.1" +
"&REQUEST=GetMap&SRS=EPSG:4326&BBOX=0,-90,180,90&WIDTH=256&HEIGHT=256&transparent=false";
MockHttpServletResponse response = getAsServletResponse(path);
assertEquals("image/png", response.getContentType());
// now setup auth for the group
TestResourceAccessManager tam = (TestResourceAccessManager) applicationContext.getBean("testResourceAccessManager");
LayerInfo lakes = getCatalog().getLayerByName(getLayerId(MockData.LAKES));
// LayerInfo forests = getCatalog().getLayerByName(getLayerId(MockData.FORESTS));
tam.putLimits("cite_nogroup", lakes, new DataAccessLimits(CatalogMode.HIDE, Filter.EXCLUDE));
tam.putLimits("cite", lakes, new DataAccessLimits(CatalogMode.HIDE, Filter.INCLUDE));
// tam.putLimits("cite_nogroup", forests, new DataAccessLimits(CatalogMode.HIDE, Filter.EXCLUDE));
// tam.putLimits("cite", forests, new DataAccessLimits(CatalogMode.HIDE, Filter.INCLUDE));
// this one cannot get the image, one layer in the group is not accessible
setRequestAuth("cite_nogroup", "cite");
response = getAsServletResponse(path);
assertEquals("application/xml", response.getContentType());
String str = string(getBinaryInputStream(response));
assertTrue(str.contains("org.geotools.ows.ServiceException: Could not find layer " + NATURE_GROUP));
// but this can access it all
setRequestAuth("cite", "cite");
response = getAsServletResponse(path);
assertEquals("image/png", response.getContentType());
}
@Test
public void testPermissionNewService() throws Exception {
final String failFormat = "application/xml";
// Applying security service by service means we have no idea how to secure an unknown
// service properly so we just have to lock it down entirely if security is enabled.
// This test can be eliminated if we switch to doing security underneath the service.
setRequestAuth("cite", "cite");
MockHttpServletResponse response = getAsServletResponse("gwc/service/myAwesomeNewService/dosomething");
assertThat(response, allOf(
hasProperty("contentType", equalTo(failFormat)),
hasBody(Matchers.containsString("could not apply layer security so denying"))
));
}
}