/* (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.wfs;
import static junit.framework.Assert.assertTrue;
import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
import org.custommonkey.xmlunit.XMLUnit;
import org.custommonkey.xmlunit.XpathEngine;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.data.test.CiteTestData;
import org.geoserver.data.test.SystemTestData;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.security.CatalogMode;
import org.geoserver.security.DataAccessLimits;
import org.geoserver.security.ResourceAccessManager;
import org.geoserver.security.TestResourceAccessManager;
import org.geoserver.security.VectorAccessLimits;
import org.geotools.factory.CommonFactoryFinder;
import org.junit.Before;
import org.junit.Test;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.PropertyName;
import org.w3c.dom.Document;
import org.springframework.mock.web.MockHttpServletResponse;
/**
* Performs integration tests using a mock {@link ResourceAccessManager}
*
* @author Andrea Aime - GeoSolutions
*
*/
public class ResourceAccessManagerWFSTest extends WFSTestSupport {
static final String INSERT_RESTRICTED_STREET = "<wfs:Transaction service=\"WFS\" version=\"1.0.0\"\n"
+ " xmlns:wfs=\"http://www.opengis.net/wfs\" xmlns:cite=\"http://www.opengis.net/cite\"\n"
+ " xmlns:gml=\"http://www.opengis.net/gml\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ " xsi:schemaLocation=\"http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd http://www.openplans.org/topp http://localhost:8080/geoserver/wfs/DescribeFeatureType?typename=topp:tasmania_roads\">\n"
+ " <wfs:Insert>\n"
+ " <cite:Buildings fid=\"Buildings.123\">\n"
+ " <cite:the_geom>\n"
+ " <gml:MultiPolygon srsName=\"http://www.opengis.net/gml/srs/epsg.xml#4326\">\n"
+ " <gml:polygonMember>\n"
+ " <gml:Polygon>\n"
+ " <gml:outerBoundaryIs>\n"
+ " <gml:LinearRing>\n"
+ " <gml:coordinates cs=\",\" decimal=\".\"\n"
+ " ts=\" \" xmlns:gml=\"http://www.opengis.net/gml\">0.0020,0.0008 0.0020,0.0010\n"
+ " 0.0024,0.0010 0.0024,0.0008 0.0020,0.0008</gml:coordinates>\n"
+ " </gml:LinearRing>\n"
+ " </gml:outerBoundaryIs>\n"
+ " </gml:Polygon>\n"
+ " </gml:polygonMember>\n"
+ " </gml:MultiPolygon>\n"
+ " </cite:the_geom>\n"
+ " <cite:FID>151</cite:FID>\n"
+ " <cite:ADDRESS>123 Restricted Street</cite:ADDRESS>\n"
+ " </cite:Buildings>\n" + " </wfs:Insert>\n" + "</wfs:Transaction>";
static final String UPDATE_ADDRESS = "<wfs:Transaction service=\"WFS\" version=\"1.1.0\"\n" +
" xmlns:cite=\"http://www.opengis.net/cite\"\n" +
" xmlns:ogc=\"http://www.opengis.net/ogc\"\n" +
" xmlns:wfs=\"http://www.opengis.net/wfs\">\n" +
" <wfs:Update typeName=\"cite:Buildings\">\n" +
" <wfs:Property>\n" +
" <wfs:Name>ADDRESS</wfs:Name>\n" +
" <wfs:Value>123 ABC Street</wfs:Value>\n" +
" </wfs:Property>\n" +
" </wfs:Update>\n" +
"</wfs:Transaction>";
static final String DELETE_ADDRESS = "<wfs:Transaction service=\"WFS\" version=\"1.1.0\"\n" +
" xmlns:cite=\"http://www.opengis.net/cite\"\n" +
" xmlns:ogc=\"http://www.opengis.net/ogc\"\n" +
" xmlns:wfs=\"http://www.opengis.net/wfs\"" +
" xmlns:gml=\"http://www.opengis.net/gml\">\n" +
" <wfs:Delete typeName=\"cite:Buildings\">" +
" <ogc:Filter>\n" +
" <ogc:BBOX>\n" +
" <ogc:PropertyName>the_geom</ogc:PropertyName>\n" +
" <gml:Envelope srsName=\"http://www.opengis.net/gml/srs/epsg.xml#4326\">\n" +
" <gml:lowerCorner>-180 -90</gml:lowerCorner>\n" +
" <gml:upperCorner>180 90</gml:upperCorner>\n" +
" </gml:Envelope>\n" +
" </ogc:BBOX>\n" +
" </ogc:Filter>\n" +
" </wfs:Delete>\n" +
"</wfs:Transaction>";
@Before
public void revert() throws Exception {
revertLayer(CiteTestData.BUILDINGS);
}
/**
* 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/wfs/ResourceAccessManagerContext.xml");
}
/**
* Enable the Spring Security auth filters
*/
@Override
protected List<javax.servlet.Filter> getFilters() {
return Collections.singletonList((javax.servlet.Filter) GeoServerExtensions
.bean("filterChainProxy"));
}
/**
* Add the users
*/
@Override
protected void setUpInternal(SystemTestData dataDirectory) throws Exception {
addUser("cite", "cite", null, Collections.singletonList("ROLE_DUMMY"));
addUser("cite_readfilter", "cite", null, Collections.singletonList("ROLE_DUMMY"));
addUser("cite,ROLE_DUMMY", "cite", null, Collections.singletonList("ROLE_DUMMY"));
addUser("cite_readatts", "cite", null, Collections.singletonList("ROLE_DUMMY"));
addUser("cite_readattsnf", "cite", null, Collections.singletonList("ROLE_DUMMY"));
addUser("cite_insertfilter", "cite", null, Collections.singletonList("ROLE_DUMMY"));
addUser("cite_writefilter", "cite", null, Collections.singletonList("ROLE_DUMMY"));
addUser("cite_writeatts", "cite", null, Collections.singletonList("ROLE_DUMMY"));
addUser("cite_mixed", "cite", null, Collections.singletonList("ROLE_DUMMY"));
//------
FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
// populate the access manager
TestResourceAccessManager tam = (TestResourceAccessManager) applicationContext
.getBean("testResourceAccessManager");
Catalog catalog = getCatalog();
FeatureTypeInfo buildings = catalog.getFeatureTypeByName(getLayerId(SystemTestData.BUILDINGS));
// limits for mr readfilter
Filter fid113 = ff.equal(ff.property("FID"), ff.literal("113"), false);
tam.putLimits("cite_readfilter", buildings, new VectorAccessLimits(CatalogMode.HIDE, null,
fid113, null, null));
// limits for mr readatts (both limited read attributes and features)
List<PropertyName> readAtts = Arrays.asList(ff.property("the_geom"), ff.property("FID"));
tam.putLimits("cite_readatts", buildings, new VectorAccessLimits(CatalogMode.HIDE,
readAtts, fid113, null, null));
// limits the attributes, but specifies no filtering
tam.putLimits("cite_readattsnf", buildings, new VectorAccessLimits(CatalogMode.HIDE,
readAtts, Filter.INCLUDE, null, Filter.INCLUDE));
// disallow writing on Restricted Street
Filter restrictedStreet = ff.not(ff.like(ff.property("ADDRESS"), "*Restricted Street*", "*", "?",
"\\"));
tam.putLimits("cite_insertfilter", buildings, new VectorAccessLimits(CatalogMode.HIDE, null,
null, null, restrictedStreet));
// allows writing only on 113
tam.putLimits("cite_writefilter", buildings, new VectorAccessLimits(CatalogMode.HIDE, null,
null, null, fid113));
// disallow writing on the ADDRESS attribute
List<PropertyName> writeAtts = Arrays.asList(ff.property("the_geom"), ff.property("FID"));
tam.putLimits("cite_writeatts", buildings, new VectorAccessLimits(CatalogMode.HIDE, null,
null, writeAtts, null));
// user with mixed mode access
tam.putLimits("cite_mixed", buildings, new VectorAccessLimits(CatalogMode.MIXED, null, Filter.EXCLUDE, null, Filter.EXCLUDE));
}
@Test
public void testNoLimits() throws Exception {
// no limits, should see all of the attributes and rows
setRequestAuth("cite", "cite");
Document doc = getAsDOM("wfs?request=GetFeature&version=1.0.0&service=wfs&typeName="
+ getLayerId(SystemTestData.BUILDINGS));
print(doc);
assertXpathEvaluatesTo("2", "count(//cite:Buildings)", doc);
assertXpathEvaluatesTo("2", "count(//cite:ADDRESS)", doc);
}
@Test
public void testReadFilter() throws Exception {
// should only see one feature and all attributes
setRequestAuth("cite_readfilter", "cite");
Document doc = getAsDOM("wfs?request=GetFeature&version=1.0.0&service=wfs&typeName="
+ getLayerId(SystemTestData.BUILDINGS));
print(doc);
assertXpathEvaluatesTo("1", "count(//cite:Buildings)", doc);
assertXpathEvaluatesTo("113", "//cite:FID", doc);
assertXpathEvaluatesTo("1", "count(//cite:ADDRESS)", doc);
}
@Test
public void testReadFilterReproject() throws Exception {
// should only see one feature and all attributes
setRequestAuth("cite_readfilter", "cite");
Document doc = getAsDOM("wfs?request=GetFeature&version=1.0.0&service=wfs&typeName="
+ getLayerId(SystemTestData.BUILDINGS) + "&srsName=EPSG:4269");
// print(doc);
assertXpathEvaluatesTo("1", "count(//cite:Buildings)", doc);
assertXpathEvaluatesTo("113", "//cite:FID", doc);
assertXpathEvaluatesTo("1", "count(//cite:ADDRESS)", doc);
assertXpathEvaluatesTo("http://www.opengis.net/gml/srs/epsg.xml#4269", "//gml:MultiPolygon/@srsName", doc);
}
@Test
public void testFilterAttribute() throws Exception {
// should only see one feature
setRequestAuth("cite_readatts", "cite");
Document doc = getAsDOM("wfs?request=GetFeature&version=1.0.0&service=wfs&typeName="
+ getLayerId(SystemTestData.BUILDINGS));
// print(doc);
assertXpathEvaluatesTo("1", "count(//cite:Buildings)", doc);
assertXpathEvaluatesTo("113", "//cite:FID", doc);
assertXpathEvaluatesTo("0", "count(//cite:ADDRESS)", doc);
}
@Test
public void testDescribeLimitedAttributes() throws Exception {
// this one should see all attributes
setRequestAuth("admin", "geoserver");
Document doc = getAsDOM("wfs?request=DescribeFeatureType&version=1.0.0&service=wfs&typeName="
+ getLayerId(SystemTestData.BUILDINGS));
// print(doc);
assertXpathEvaluatesTo("1", "count(//xsd:element[@name='the_geom'])", doc);
assertXpathEvaluatesTo("1", "count(//xsd:element[@name='FID'])", doc);
assertXpathEvaluatesTo("1", "count(//xsd:element[@name='ADDRESS'])", doc);
// this one should see only 2
setRequestAuth("cite_readatts", "cite");
doc = getAsDOM("wfs?request=DescribeFeatureType&version=1.0.0&service=wfs&typeName="
+ getLayerId(SystemTestData.BUILDINGS));
// print(doc);
assertXpathEvaluatesTo("1", "count(//xsd:element[@name='the_geom'])", doc);
assertXpathEvaluatesTo("1", "count(//xsd:element[@name='FID'])", doc);
assertXpathEvaluatesTo("0", "count(//xsd:element[@name='ADDRESS'])", doc);
// paranoid check to make sure there is no caching
setRequestAuth("admin", "geoserver");
doc = getAsDOM("wfs?request=DescribeFeatureType&version=1.0.0&service=wfs&typeName="
+ getLayerId(SystemTestData.BUILDINGS));
// print(doc);
assertXpathEvaluatesTo("1", "count(//xsd:element[@name='the_geom'])", doc);
assertXpathEvaluatesTo("1", "count(//xsd:element[@name='FID'])", doc);
assertXpathEvaluatesTo("1", "count(//xsd:element[@name='ADDRESS'])", doc);
}
@Test
public void testCapabilitiesMixed() throws Exception {
setRequestAuth("admin", "geoserver");
Document doc = getAsDOM("cite/wfs?request=GetCapabilities&version=1.1.0&service=wfs");
print(doc);
assertXpathEvaluatesTo("1", "count(//wfs:FeatureType[wfs:Name='cite:Buildings'])", doc);
// this one not
setRequestAuth("cite_mixed", "cite");
doc = getAsDOM("cite/wfs?request=GetCapabilities&version=1.1.0&service=wfs");
print(doc);
assertXpathEvaluatesTo("0", "count(//wfs:FeatureType[wfs:Name='cite:Buildings'])", doc);
}
@Test
public void testDescribeMixed() throws Exception {
// this one should see all types
setRequestAuth("admin", "geoserver");
Document doc = getAsDOM("cite/wfs?request=DescribeFeatureType&version=1.1.0&service=wfs");
// print(doc);
assertXpathEvaluatesTo("1", "count(//xsd:complexType[@name='BuildingsType'])", doc);
// this one not
setRequestAuth("cite_mixed", "cite");
doc = getAsDOM("cite/wfs?request=DescribeFeatureType&version=1.1.0&service=wfs");
// print(doc);
assertXpathEvaluatesTo("0", "count(//xsd:complexType[@name='BuildingsType'])", doc);
// and a direct access will fail with an auth challenge
setRequestAuth("cite_mixed", "cite");
MockHttpServletResponse response = getAsServletResponse("cite/wfs?request=DescribeFeatureType&version=1.1.0&service=wfs&typeName=" + getLayerId(SystemTestData.BUILDINGS));
assertEquals(403, response.getStatus());
}
@Test
public void testFilterRequestedAttribute() throws Exception {
// should only see one feature
setRequestAuth("cite_readatts", "cite");
Document doc = getAsDOM("wfs?request=GetFeature&version=1.1.0&service=wfs&typeName="
+ getLayerId(SystemTestData.BUILDINGS) + "&propertyName=FID,ADDRESS");
// print(doc);
assertXpathEvaluatesTo("1", "count(//ows:ExceptionReport)", doc);
XpathEngine xpath = XMLUnit.newXpathEngine();
String message = xpath.evaluate("//ows:ExceptionText", doc);
Pattern pattern = Pattern.compile(".*ADDRESS.*not available.*", Pattern.MULTILINE | Pattern.DOTALL);
assertTrue(pattern.matcher(message).matches());
}
@Test
public void testExtraAttributesNoFilter() throws Exception {
// should only see one feature
setRequestAuth("cite_readattsnf", "cite");
Document doc = getAsDOM("wfs?request=GetFeature&version=1.1.0&service=wfs&typeName="
+ getLayerId(SystemTestData.BUILDINGS) + "&propertyName=FID,ADDRESS");
// print(doc);
assertXpathEvaluatesTo("1", "count(//ows:ExceptionReport)", doc);
XpathEngine xpath = XMLUnit.newXpathEngine();
String message = xpath.evaluate("//ows:ExceptionText", doc);
Pattern pattern = Pattern.compile(".*ADDRESS.*not available.*", Pattern.MULTILINE | Pattern.DOTALL);
assertTrue(pattern.matcher(message).matches());
}
@Test
public void testLimitAttributesNoFilter() throws Exception {
// should only see one feature
setRequestAuth("cite_readattsnf", "cite");
Document doc = getAsDOM("wfs?request=GetFeature&version=1.1.0&service=wfs&typeName="
+ getLayerId(SystemTestData.BUILDINGS));
// print(doc);
assertXpathEvaluatesTo("0", "count(//cite:ADDRESS)", doc);
}
@Test
public void testInsertNoLimits() throws Exception {
setRequestAuth("cite", "cite");
Document dom = postAsDOM("wfs", INSERT_RESTRICTED_STREET);
// print(dom);
assertXpathEvaluatesTo("1", "count(//wfs:WFS_TransactionResponse)", dom);
assertXpathEvaluatesTo("1", "count(//ogc:FeatureId)", dom);
assertXpathEvaluatesTo("new0", "//ogc:FeatureId/@fid", dom);
assertXpathEvaluatesTo("1", "count(//wfs:Status/wfs:SUCCESS)", dom);
}
@Test
public void testInsertRestricted() throws Exception {
setRequestAuth("cite_insertfilter", "cite");
Document dom = postAsDOM("wfs", INSERT_RESTRICTED_STREET);
// print(dom);
assertXpathEvaluatesTo("1", "count(//wfs:WFS_TransactionResponse)", dom);
assertXpathEvaluatesTo("1", "count(//wfs:Status/wfs:FAILED)", dom);
XpathEngine xpath = XMLUnit.newXpathEngine();
String message = xpath.evaluate("//wfs:Message", dom);
assertTrue(message.matches(".*write restrictions.*"));
}
@Test
public void testInsertAttributeRestricted() throws Exception {
setRequestAuth("cite_writeatts", "cite");
Document dom = postAsDOM("wfs", INSERT_RESTRICTED_STREET);
print(dom);
assertXpathEvaluatesTo("1", "count(//wfs:WFS_TransactionResponse)", dom);
assertXpathEvaluatesTo("1", "count(//wfs:Status/wfs:FAILED)", dom);
XpathEngine xpath = XMLUnit.newXpathEngine();
String message = xpath.evaluate("//wfs:Message", dom);
assertTrue(message.matches(".*write protected.*ADDRESS.*"));
}
@Test
public void testUpdateNoLimits() throws Exception {
setRequestAuth("cite", "cite");
Document dom = postAsDOM("wfs", UPDATE_ADDRESS);
//print(dom);
assertXpathEvaluatesTo("2", "//wfs:totalUpdated", dom);
}
@Test
public void testUpdateLimitWrite() throws Exception {
setRequestAuth("cite_writefilter", "cite");
Document dom = postAsDOM("wfs", UPDATE_ADDRESS);
//print(dom)
assertXpathEvaluatesTo("1", "//wfs:totalUpdated", dom);
// double check
setRequestAuth("cite", "cite");
Document doc = getAsDOM("wfs?request=GetFeature&version=1.0.0&service=wfs&typeName="
+ getLayerId(SystemTestData.BUILDINGS));
//print(doc);
// check this one has been updated
assertXpathEvaluatesTo("123 ABC Street", "//cite:Buildings[cite:FID = '113']/cite:ADDRESS", doc);
// but the other did not
assertXpathEvaluatesTo("215 Main Street", "//cite:Buildings[cite:FID = '114']/cite:ADDRESS", doc);
}
@Test
public void testUpdateAttributeRestricted() throws Exception {
setRequestAuth("cite_writeatts", "cite");
Document dom = postAsDOM("wfs", UPDATE_ADDRESS);
// print(dom);
// not sure why CITE tests make us throw an exception for this one instead of using
// in transaction reporting...
assertXpathEvaluatesTo("1", "count(//ows:ExceptionReport)", dom);
XpathEngine xpath = XMLUnit.newXpathEngine();
String message = xpath.evaluate("//ows:ExceptionText", dom);
assertTrue(message.matches(".*write protected.*ADDRESS.*"));
}
@Test
public void testDeleteLimitWrite() throws Exception {
setRequestAuth("cite_writefilter", "cite");
Document dom = postAsDOM("wfs", DELETE_ADDRESS);
// print(dom);
assertXpathEvaluatesTo("1", "//wfs:totalDeleted", dom);
// double check
setRequestAuth("cite", "cite");
Document doc = getAsDOM("wfs?request=GetFeature&version=1.0.0&service=wfs&typeName="
+ getLayerId(SystemTestData.BUILDINGS));
// print(doc);
// check this one has been deleted
assertXpathEvaluatesTo("0", "count(//cite:Buildings[cite:FID = '113'])", doc);
// but the other did not
assertXpathEvaluatesTo("1", "count(//cite:Buildings[cite:FID = '114'])", doc);
}
}