package com.camptocamp.owsproxy; import static org.apache.commons.httpclient.HttpStatus.SC_OK; import static org.apache.commons.httpclient.HttpStatus.SC_UNAUTHORIZED; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.apache.commons.httpclient.Credentials; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.methods.GetMethod; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Class for testing WMS requests on the owsproxyserver * */ public class TestWMS extends TestCase { final static String BASE_WMS_URL = "http://localhost:8080/owsproxyserver/"; static final Credentials TOMCAT_CRED = new UsernamePasswordCredentials("tomcat", "tomcat"); static final Credentials ALICE_CRED = new UsernamePasswordCredentials("alice", "alice"); static final Credentials INVALID_CRED = new UsernamePasswordCredentials("invalid_user", "nopass"); // NOTE: This has to be kept in sync with the value from accessDenied.html static final String DENIED_MESSAGE = "Access denied"; // Layers for layer restriction tests final static Set<String> AUTHORIZED_LAYERS_SET1 = new HashSet<String>(Arrays.asList(new String[] {"tiger:giant_polygon", "topp:tasmania_cities"})); // TODO: tests several versions private static final String VERSION = "1.1.1"; static String AUTHORIZED_LAYERS_SET1_AS_STRING = ""; static { AUTHORIZED_LAYERS_SET1_AS_STRING = ""; for (String layer : AUTHORIZED_LAYERS_SET1) { AUTHORIZED_LAYERS_SET1_AS_STRING += "," + layer; } AUTHORIZED_LAYERS_SET1_AS_STRING = AUTHORIZED_LAYERS_SET1_AS_STRING.substring(1); } // Number of layers in map final int NUM_LAYERS = 14; // Utility variable to quickly enable/disable tests //private boolean TESTS_ENABLED = false; private boolean TESTS_ENABLED = true; private Map<String, String> overrideMap(Map<String, String> original, Map<String, String> override) { Map<String, String> overridden = (Map<String, String>)((HashMap<String, String>)original).clone(); if (override != null) overridden.putAll(override); return overridden; } private String buildQueryString(Map<String, String> params, Map<String, String> override) { Map<String, String> updated = overrideMap(params, override); String queryString = ""; for (String p : updated.keySet()) { queryString += p + "=" + updated.get(p) + "&"; } return queryString; } public void setUp() { } private Map<String, String> getGetMapDefaultParams() { Map<String, String> m = new HashMap<String, String>(); m.put("REQUEST", "GetMap"); m.put("SERVICE", "WMS"); m.put("VERSION", VERSION); m.put("WIDTH", "684"); m.put("HEIGHT", "497"); m.put("LAYERS", "nurc%3AImg_Sample"); m.put("TRANSPARENT", "TRUE"); m.put("FORMAT", "image/png"); m.put("BBOX", "-124.03,11.36,-68.14,55.86"); m.put("SRS", "EPSG:4326"); m.put("STYLES", ""); return m; } private Map<String, String> getGetCapabilitiesDefaultParams() { Map<String, String> m = new HashMap<String, String>(); m.put("REQUEST", "GetCapabilities"); m.put("SERVICE", "WMS"); m.put("VERSION", VERSION); return m; } private Map<String, String> getGetFeatureInfoDefaultParams() { Map<String, String> m = new HashMap<String, String>(); m.put("REQUEST", "GetFeatureInfo"); m.put("SERVICE", "WMS"); m.put("VERSION", VERSION); m.put("EXCEPTIONS", "application/vnd.ogc.se_xml"); m.put("X", "25"); m.put("Y", "311"); m.put("INFO_FORMAT", "text/html"); m.put("QUERY_LAYERS", "tiger%3Agiant_polygon"); // GetFeatureInfo is based on GetMap return overrideMap(getGetMapDefaultParams(), m); } public void testNoRestrictions() { if(!TESTS_ENABLED)return; final String TEST = "test_no_restrictions"; Map<String, String> override = new HashMap<String, String>(); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, false); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), INVALID_CRED, SC_UNAUTHORIZED, false); } public void testSizeRestrictions() { if(!TESTS_ENABLED)return; final String TEST = "test_size_restrictions"; Map<String, String> override = new HashMap<String, String>(); override.clear(); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, false); override.clear(); override.put("WIDTH", "2000"); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, true); } private Set<String> getLayers(InputStream is) { List<String> layers = new Vector<String>(); try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse( is ); System.out.println("document " + document); System.out.println("document " + document.getDocumentElement()); NodeList nl = document.getElementsByTagName("Layer"); for (int i = 0; i < nl.getLength(); i++) { Node n = nl.item(i); // XXX name could be elsewhere String layerName = n.getFirstChild().getTextContent(); // XXX ignore root layer if (layerName.equals("My GeoServer WMS")) continue; layers.add(layerName); } } catch (Throwable t) { t.printStackTrace(); throw new AssertionFailedError("failed parsing capabilities XML"); } System.out.println("Layers: " + layers); return new HashSet<String>(layers); } public void testLayerRestrictions() { if(!TESTS_ENABLED)return; final String TEST = "test_layer_restrictions"; Map<String, String> override = new HashMap<String, String>(); // getCapabilities override.clear(); InputStream is = doTestRequest(TEST, buildQueryString(getGetCapabilitiesDefaultParams(), override), TOMCAT_CRED, SC_OK, false); assertEquals(AUTHORIZED_LAYERS_SET1, getLayers(is)); // getMap, with authorized layers -> allowed override.clear(); override.put("LAYERS", AUTHORIZED_LAYERS_SET1_AS_STRING); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, false); // getMap, with unauthorized layers -> denied override.clear(); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, true); } public void testLayerRestrictions2() { if(!TESTS_ENABLED)return; final String TEST = "test_layer_restrictions2"; Map<String, String> override = new HashMap<String, String>(); InputStream is; // getCapabilities // Alice can see all layers override.clear(); is = doTestRequest(TEST, buildQueryString(getGetCapabilitiesDefaultParams(), override), ALICE_CRED, SC_OK, false); assertEquals(NUM_LAYERS, getLayers(is).size()); // tomcat can see only two is = doTestRequest(TEST, buildQueryString(getGetCapabilitiesDefaultParams(), override), TOMCAT_CRED, SC_OK, false); assertEquals(AUTHORIZED_LAYERS_SET1, getLayers(is)); // Alice can getMap all layers override.clear(); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), ALICE_CRED, SC_OK, false); // ... not tomcat override.clear(); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, true); // ... but he can see only two override.clear(); override.put("LAYERS", AUTHORIZED_LAYERS_SET1_AS_STRING); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, false); // ... so does alice override.clear(); override.put("LAYERS", AUTHORIZED_LAYERS_SET1_AS_STRING); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), ALICE_CRED, SC_OK, false); } public void testBboxRestrictions() { if(!TESTS_ENABLED)return; // Keep this in sync with the testBboxOutsideRestrictions final String TEST = "test_bbox_restrictions"; Map<String, String> override = new HashMap<String, String>(); // default bbox is surrounding the allowed one -> deny override.clear(); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, true); // totally inside allowed bbox -> allow override.clear(); override.put("BBOX", "-92.91,26.21,-67.42,52.18"); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, false); // intersects allowed bbox -> deny override.clear(); override.put("BBOX", "-92.91,26.21,-10,52.18"); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, true); // totally outside of allowed bbox -> deny override.clear(); override.put("BBOX", "-92.91,80.21,-67.42,85.18"); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, true); } public void testBboxOutsideRestrictions() { if(!TESTS_ENABLED)return; // Keep this in sync with the testBboxRestrictions final String TEST = "test_bbox_outside_restrictions"; Map<String, String> override = new HashMap<String, String>(); // default bbox is surrounding the allowed one -> allow override.clear(); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, false); // totally inside allowed bbox -> allow override.clear(); override.put("BBOX", "-92.91,26.21,-67.42,52.18"); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, false); // intersects allowed bbox -> allow override.clear(); override.put("BBOX", "-92.91,26.21,-10,52.18"); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, false); // totally outside of allowed bbox -> deny override.clear(); override.put("BBOX", "-92.91,80.21,-67.42,85.18"); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, true); } public void testBboxRestrictions2() { if(!TESTS_ENABLED)return; final String TEST = "test_bbox_restrictions2"; Map<String, String> override = new HashMap<String, String>(); // default bbox is outside of allowed -> deny for tomcat ... override.clear(); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, true); // ... and allow for alice override.clear(); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), ALICE_CRED, SC_OK, false); // inside allowed bbox -> allow for tomcat override.clear(); override.put("BBOX", "-92.91,26.21,-67.42,52.18"); doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), TOMCAT_CRED, SC_OK, false); // ... and allow for alice doTestRequest(TEST, buildQueryString(getGetMapDefaultParams(), override), ALICE_CRED, SC_OK, false); } public void testGetFeatureInfoNoRestriction() { if(!TESTS_ENABLED)return; final String TEST = "test_no_restrictions"; Map<String, String> override = new HashMap<String, String>(); override.clear(); doTestRequest(TEST, buildQueryString(getGetFeatureInfoDefaultParams(), override), TOMCAT_CRED, SC_OK, false); } /** * Some WMS Client, like ArcMAP, only send the QUERY_LAYERS parameter without a LAYERS parameter. * * In such situation, the service should not throw an exception. */ public void testGetFeatureInfoNoLayers() { if(!TESTS_ENABLED)return; final String TEST = "test_no_restrictions"; Map<String, String> override = new HashMap<String, String>(); override.clear(); Map<String, String> params = getGetFeatureInfoDefaultParams(); params.remove("LAYERS"); doTestRequest(TEST, buildQueryString(params, override), TOMCAT_CRED, SC_OK, false); } public void testGetFeatureInfoFeaturecount() { if(!TESTS_ENABLED)return; final String TEST = "test_featureinfo_featurecount"; Map<String, String> override = new HashMap<String, String>(); // no feature count is equivalent to 1 feature count -> allowed override.clear(); doTestRequest(TEST, buildQueryString(getGetFeatureInfoDefaultParams(), override), TOMCAT_CRED, SC_OK, false); // 200 > 100 -> deny override.clear(); override.put("FEATURE_COUNT", "200"); doTestRequest(TEST, buildQueryString(getGetFeatureInfoDefaultParams(), override), TOMCAT_CRED, SC_OK, true); } public void testGetFeatureInfoFormat() { if(!TESTS_ENABLED)return; final String TEST = "test_featureinfo_format"; Map<String, String> override = new HashMap<String, String>(); // format text/html is allowed override.clear(); override.put("INFO_FORMAT", "text/html"); doTestRequest(TEST, buildQueryString(getGetFeatureInfoDefaultParams(), override), TOMCAT_CRED, SC_OK, false); // unauthorized format -> denied override.clear(); override.put("INFO_FORMAT", "x-foo/x-bar"); doTestRequest(TEST, buildQueryString(getGetFeatureInfoDefaultParams(), override), TOMCAT_CRED, SC_OK, true); } public void testGetFeatureInfoLayers() { if(!TESTS_ENABLED)return; final String TEST = "test_featureinfo_layers"; Map<String, String> override = new HashMap<String, String>(); // default layer is allowed override.clear(); doTestRequest(TEST, buildQueryString(getGetFeatureInfoDefaultParams(), override), TOMCAT_CRED, SC_OK, false); // unauthorized layer -> denied override.clear(); override.put("QUERY_LAYERS", "invalid_layer"); doTestRequest(TEST, buildQueryString(getGetFeatureInfoDefaultParams(), override), TOMCAT_CRED, SC_OK, true); } // TODO: Add expected content type: image / xml / ... private InputStream doTestRequest(String servletName, String queryString, Credentials creds, int expectStatus, boolean expectDenied) { System.out.println("----------------------------------"); System.out.println("Testing servlet " + servletName); String serviceURL = BASE_WMS_URL + servletName; // XXX not working HostnameVerifier dummyVerifier = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } }; HttpsURLConnection.setDefaultHostnameVerifier(dummyVerifier); HttpClient client = new HttpClient(); if (queryString != null) { serviceURL += "?" + queryString; } System.out.println("End point: " + serviceURL); HttpMethod method = new GetMethod(serviceURL); if (creds != null) { client.getParams().setAuthenticationPreemptive(true); client.getState().setCredentials(AuthScope.ANY, creds); } // Execute the method. int statusCode = -1; try { statusCode = client.executeMethod(method); } catch (Exception e) { fail(e.toString()); } System.out.println("Http status: " + statusCode); assertEquals(expectStatus, statusCode); Header contentTypeHeader = method.getResponseHeader("Content-Type"); assertNotNull(contentTypeHeader); System.out.println("Content-Type: " + contentTypeHeader.getValue()); // WARNING: Going to buffer response body of large or unknown // size. Using getResponseBodyAsStream instead is recommended. byte[] responseBody = null; try { responseBody = method.getResponseBody(); } catch (IOException e) { fail(e.toString()); } assertTrue(responseBody.length > 0); String body = new String(responseBody); /* Uncomment to save body to file try { BufferedWriter bw = new BufferedWriter(new FileWriter("/tmp/out.xml")); bw.write(body); bw.close(); } catch (IOException e) { e.printStackTrace(); } */ assertEquals("Wrong access restriction", expectDenied, body.contains(DENIED_MESSAGE)); try { return method.getResponseBodyAsStream(); } catch (IOException e) { e.printStackTrace(); throw new AssertionFailedError("No input stream"); } } }