diff --git a/SECURITY.md b/SECURITY.md index d05c8c3e202..1f313821623 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,7 +12,7 @@ Each GeoServer release is supported with bug fixes for a year, with releases mad This approach provides ample time for upgrading ensuring you are always working with a supported GeoServer release. -If your organisation is making use of a GeoServer version that is no longer in use by the community all is not lost. +If your organization is making use of a GeoServer version that is no longer in use by the community all is not lost. You can volunteer on the developer list to make additional releases, or engage with one of our [Commercial Support](http://geoserver.org/support/) providers. @@ -53,6 +53,6 @@ Disclosure policy: 4. A fix is included for the "stable" and "maintenance" downloads ([released as scheduled](https://github.com/geoserver/geoserver/wiki/Release-Schedule), or issued via emergency update) 6. The CVE vulnerability is published with mitigation and patch instructions -This represents a balance between transparency and particpation that does not overwhelm particpants. +This represents a balance between transparency and participation that does not overwhelm participants. Those seeking greater visibility are encouraged to volunteer with the geoserver-security list; or work with one of the [commercial support providers](https://geoserver.org/support/) who participate on behalf of their customers. diff --git a/doc/en/api/1.0.0/urlchecks.yaml b/doc/en/api/1.0.0/urlchecks.yaml new file mode 100644 index 00000000000..81c45ae5761 --- /dev/null +++ b/doc/en/api/1.0.0/urlchecks.yaml @@ -0,0 +1,281 @@ +--- +swagger: '2.0' +info: + version: 1.0.0 + title: GeoServer UrlCheck + description: An URL External Access Check is the check performed on user provided URLs that GeoServer will use to access remote resources. + contact: + name: GeoServer + email: 'geoserver-users@sourceforge.net' + url: 'http://geoserver.org/comm/' +host: localhost:8080 +basePath: /geoserver/rest + +paths: + /urlchecks: + get: + operationId: getUrlChecks + tags: + - "UrlChecks" + summary: Get a list of URL checks + description: Displays a list of all URL checks on the server. Use the "Accept:" header to specify format or append an extension to the endpoint (example "/urlchecks.xml" for XML) + produces: + - text/html + - application/json + - application/xml + responses: + 200: + description: OK + schema: + $ref: "#/definitions/urlChecks" + examples: + text/html: | + +
+"*"
: Allow all http(s) schema locations
- * "" or undefined: Restrict to schemas provided by w3c, ogc and inspire
- *
- * "location1,location2": Restrict to the provided locations, and those list by w4c, ogc and inspire
- *
+ * The built-in list appended by {@link AllowListEntityResolver} is equivalent to: EntityResolver returns {@code null} when the provided URI is *allowed*, Returns an
+ * Inputstream if the content is provided, or throws an Exception if the URI is not allowed.
+ */
+ @Test
+ public void testEntityResolverDefaultBehaviour() throws Exception {
+ EntityResolverProvider provider = new EntityResolverProvider(null);
+ provider.setEntityResolver(new AllowListEntityResolver(null));
+ EntityResolver resolver = provider.getEntityResolver();
+
+ // Confirm schema is available from public location
+ // (this is a default from AllowListEntiryResolver)
+ InputSource filter =
+ resolver.resolveEntity(null, "http://schemas.opengis.net/filter/1.1.0/filter.xsd");
+ assertNull("Public Filter 1.1.0 connection allowed", filter);
+
+ // Confirm schema is available from jars, as is the case for those included in GeoTools
+ InputSource filterJar =
+ resolver.resolveEntity(
+ null, "jar:file:/some/path/gs-main.jar!schemas/filter/1.1.0/filter.xsd");
+ assertNull("JAR Filter 1.1.0 connection allowed", filterJar);
+
+ // Confirm schema is available when war is unpacked into JBoss virtual filesystem
+ InputSource filterJBoss =
+ resolver.resolveEntity(
+ null,
+ "vfsfile:/home/userone/jboss-eap-5.1/jboss-as/server/default_WAR/deploy/geoserver.war/WEB-INF/lib/gs-main.jar!/filter/1.1.0/filter.xsd");
+ assertNull("JBoss Virtual File System Filter 1.1.0 connection allowed", filterJBoss);
+
+ // confirm schema CANNOT be accessed from a random website http address
+ // (such as an external geoserver location mentioned below)
+ try {
+ InputSource external =
+ resolver.resolveEntity(
+ null,
+ "https://how2map.geocat.live/geoserver/schemas/wfs/1.0.0/WFS-basic.xsd");
+ assertNotNull("Website Filter 1.1.0 not allowed", external);
+ fail("Filter 1.1.0 is should not be provided built-in");
+ } catch (SAXException e) {
+ // Confirm the exception is clear, and contains the URI for folks to troubleshoot their
+ // xml document
+ assertTrue(
+ "External XSD not allowed",
+ e.getMessage().startsWith("Entity resolution disallowed for"));
+ assertTrue(
+ "External XSD not allowed",
+ e.getMessage()
+ .contains(
+ "https://how2map.geocat.live/geoserver/schemas/wfs/1.0.0/WFS-basic.xsd"));
+ }
+
+ // not allowed to access local file system
+ try {
+ InputSource filesystem =
+ resolver.resolveEntity(
+ null, "file:/var/opt/geoserver/data/www/schemas/WFS-basic.xsd");
+ assertNotNull("Filesystem Filter 1.1.0 not allowed", filesystem);
+ fail("Filter 1.1.0 is should not avalable as a file reference");
+ } catch (SAXException e) {
+ // Confirm the exception is clear, and contains the URI for folks to troubleshoot their
+ // xml document
+ assertTrue(
+ "Filesystem XSD not allowed",
+ e.getMessage().startsWith("Entity resolution disallowed for"));
+ assertTrue(
+ "Filesystem XSD not allowed",
+ e.getMessage()
+ .contains("file:/var/opt/geoserver/data/www/schemas/WFS-basic.xsd"));
+ }
+ }
+
+ /**
+ * Test behaviour of EntityResolveProvider in response to configuration to prevent local
+ * filesystem access (as done with ENTITY_RESOLUTION_ALLOWLIST=*)
+ *
+ * EntityResolver returns {@code null} when the provided URI is *allowed*, Returns an
+ * Inputstream if the content is provided, or throws an Exception if the URI is not allowed.
+ */
+ @Test
+ public void testEntityResolverPreventLocal() throws Exception {
+ EntityResolverProvider provider = new EntityResolverProvider(null);
+ provider.setEntityResolver(PreventLocalEntityResolver.INSTANCE);
+ EntityResolver resolver = provider.getEntityResolver();
+
+ // Confirm schema is available from public location
+ // (this is a default from AllowListEntiryResolver)
+ InputSource filter =
+ resolver.resolveEntity(null, "http://schemas.opengis.net/filter/1.1.0/filter.xsd");
+ assertNull("Public Filter 1.1.0 connection allowed", filter);
+
+ // Confirm schema is available from jars, as is the case for those included in GeoTools
+ InputSource filterJar =
+ resolver.resolveEntity(
+ null, "jar:file:/some/path/gs-main.jar!schemas/filter/1.1.0/filter.xsd");
+ assertNull("JAR Filter 1.1.0 connection allowed", filterJar);
+
+ // Confirm schema is available when war is unpacked into JBoss virtual filesystem
+ InputSource filterJBoss =
+ resolver.resolveEntity(
+ null,
+ "vfsfile:/home/userone/jboss-eap-5.1/jboss-as/server/default_WAR/deploy/geoserver.war/WEB-INF/lib/gs-main.jar!/filter/1.1.0/filter.xsd");
+ assertNull("JBoss Virtual File System Filter 1.1.0 connection allowed", filterJBoss);
+
+ // confirm that by default can access any random website http address
+ InputSource external =
+ resolver.resolveEntity(
+ null,
+ "https://how2map.geocat.live/geoserver/schemas/wfs/1.0.0/WFS-basic.xsd");
+ assertNull("Website Filter 1.1.0 allowed", external);
+
+ // not allowed to access local file system
+ try {
+ InputSource filesystem =
+ resolver.resolveEntity(
+ null, "file:/var/opt/geoserver/data/www/schemas/WFS-basic.xsd");
+ assertNotNull("Filesystem Filter 1.1.0 not allowed", filesystem);
+ fail("Filter 1.1.0 is should not avalable as a file reference");
+ } catch (SAXException e) {
+ // Confirm the exception is clear, and contains the URI for folks to troubleshoot their
+ // xml document
+ assertTrue(
+ "Filesystem XSD not allowed",
+ e.getMessage().startsWith("Entity resolution disallowed for"));
+ assertTrue(
+ "Filesystem XSD not allowed",
+ e.getMessage()
+ .contains("file:/var/opt/geoserver/data/www/schemas/WFS-basic.xsd"));
+ }
}
}
diff --git a/src/ows/src/main/java/org/geoserver/ows/XmlRequestReader.java b/src/ows/src/main/java/org/geoserver/ows/XmlRequestReader.java
index 1d51ca5de09..be14fcc082f 100644
--- a/src/ows/src/main/java/org/geoserver/ows/XmlRequestReader.java
+++ b/src/ows/src/main/java/org/geoserver/ows/XmlRequestReader.java
@@ -5,12 +5,16 @@
*/
package org.geoserver.ows;
+import java.io.IOException;
import java.io.Reader;
import java.util.Map;
+import java.util.logging.Logger;
import javax.xml.namespace.QName;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.geotools.util.Version;
+import org.geotools.util.logging.Logging;
+import org.xml.sax.SAXException;
/**
* Creates a request bean from xml.
@@ -24,6 +28,11 @@
* @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
*/
public abstract class XmlRequestReader {
+ /**
+ * Logger for XmlRequestReader subclass established in constructor for logging of parse errors.
+ */
+ final Logger LOGGER;
+
/** the qualified name of the element this reader can read. */
final QName element;
@@ -64,6 +73,8 @@ public XmlRequestReader(QName element, Version version, String serviceId) {
this.version = version;
this.serviceId = serviceId;
+ LOGGER = Logging.getLogger(this.getClass());
+
if (element == null) {
throw new NullPointerException("element");
}
@@ -126,4 +137,76 @@ public int hashCode() {
public String getServiceId() {
return serviceId;
}
+
+ /**
+ * Wrap a request parsing failure in a SAXException to prevent generic FileNotFound and
+ * ConnectionRefused messages.
+ *
+ * This communicates that there is a problem with the XML Document while avoiding providing
+ * internal details that are not useful to web service users.
+ *
+ * The full stack trace is preserved to support development and debugging; so the line number
+ * can be used by developers.
+ *
+ * @param t Failure during parsing (such as SAXException or IOException)
+ * @return Clean SAXException for common parsing failures, or initial throwable
+ */
+ protected Exception cleanException(Exception t) {
+ if (t instanceof IOException) {
+ return createParseException(t);
+ }
+ if (t instanceof SAXException) {
+ // Double check SAXException does not echo caused by message
+ return cleanSaxException((SAXException) t);
+ }
+ return t;
+ }
+
+ /**
+ * Clean the localized message, in the case where it is reproduced by SAXException default
+ * constructor.
+ *
+ * @param saxException
+ * @return saxException with nested localized message removed.
+ */
+ protected SAXException cleanSaxException(SAXException saxException) {
+ Throwable cause = saxException.getCause();
+ // We only wish to check SAXException which echos internal caused by message
+ // Subclasses such as SAXParserException provide a useful message
+ if (saxException != null
+ && saxException.getCause() != null
+ && saxException.getClass() == SAXException.class) {
+ String saxMessage = saxException.getMessage();
+ String causeMessage = cause.getLocalizedMessage();
+ if (causeMessage != null && saxMessage.contains(causeMessage)) {
+ return createParseException(saxException);
+ }
+ }
+ return saxException;
+ }
+
+ /**
+ * Log the cause, and return a SAXException indicaitng a parse failure.
+ *
+ * This is a replacement for the provided cause, and includes the same stack trace to assist
+ * with troubleshooting and debugging.
+ *
+ * @param cause
+ * @return SAXException indicating parse failure
+ */
+ protected SAXException createParseException(Throwable cause) {
+ // Log actual failure for debugging and troubleshooting
+ String requestFailure = "XML " + getElement().getLocalPart() + " Parsing Failure: ";
+ LOGGER.info(requestFailure + cause.toString());
+
+ // Provide clean SAXException message, keep stacktrace history (for verbose service
+ // exception document)
+ String cleanMessage =
+ "Parsing failed, the xml request is most probably not compliant to "
+ + getElement().getLocalPart()
+ + " element";
+ SAXException saxException = new SAXException(cleanMessage);
+ saxException.setStackTrace(cause.getStackTrace());
+ return saxException;
+ }
}
diff --git a/src/ows/src/test/java/org/geoserver/ows/DispatcherTest.java b/src/ows/src/test/java/org/geoserver/ows/DispatcherTest.java
index 349946314fb..1441973a6cd 100644
--- a/src/ows/src/test/java/org/geoserver/ows/DispatcherTest.java
+++ b/src/ows/src/test/java/org/geoserver/ows/DispatcherTest.java
@@ -10,13 +10,17 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -44,6 +48,8 @@
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
public class DispatcherTest {
@Test
@@ -130,6 +136,56 @@ public void testReadOpPost() throws Exception {
}
}
+ @Test
+ public void testReadOpPostServiceExceptions() throws Exception {
+ // Test XmlRequestReader cleanException handling
+ MessageXmlParser parser = new MessageXmlParser();
+
+ IOException ioException = new FileNotFoundException("notFound.txt");
+ SAXException saxException = new SAXException(ioException);
+ SAXException saxParseException =
+ new SAXParseException("glitch", "test.xsd", "test.xsd", 30, 12);
+
+ Exception clean = parser.cleanException(ioException);
+ String message = clean.getLocalizedMessage();
+ assertFalse(message.contains("notFound.txt"));
+
+ Exception cleanSAX = parser.cleanSaxException(saxException);
+ String message2 = cleanSAX.getLocalizedMessage();
+ assertFalse(message2.contains("notFound.txt"));
+
+ Exception cleanSAXParse = parser.cleanSaxException(saxParseException);
+ assertSame(saxParseException, cleanSAXParse);
+ }
+
+ // // Test ServiceException via dispatcher
+ // MockHttpServletRequest request = new MockHttpServletRequest();
+ // request.setContextPath("/geoserver");
+ // request.setRequestURI("/geoserver/hello");
+ // request.setMethod("post");
+ //
+ // String dtdExternal = "\n" +
+ // " %external;\n" +
+ // " ]>";
+ // String body = dtdExternal+"\n
@@ -130,17 +129,16 @@ public static Set
+
+
+<#include "tail.ftl">
\ No newline at end of file
diff --git a/src/restconfig/src/main/java/org/geoserver/rest/security/ftl-templates/head.ftl b/src/restconfig/src/main/java/org/geoserver/rest/security/ftl-templates/head.ftl
new file mode 100644
index 00000000000..37eeabaa41a
--- /dev/null
+++ b/src/restconfig/src/main/java/org/geoserver/rest/security/ftl-templates/head.ftl
@@ -0,0 +1,10 @@
+
+
+
+
+<#list values as c>
+
+<#include "tail.ftl">
diff --git a/src/restconfig/src/test/java/org/geoserver/rest/security/UrlCheckControllerTest.java b/src/restconfig/src/test/java/org/geoserver/rest/security/UrlCheckControllerTest.java
new file mode 100644
index 00000000000..6d39b267365
--- /dev/null
+++ b/src/restconfig/src/test/java/org/geoserver/rest/security/UrlCheckControllerTest.java
@@ -0,0 +1,385 @@
+/* (c) 2024 Open Source Geospatial Foundation - all rights reserved
+ * This code is licensed under the GPL 2.0 license, available at the root
+ * application directory.
+ */
+package org.geoserver.rest.security;
+
+import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo;
+import static org.geoserver.rest.RestBaseController.ROOT_PATH;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
+
+import java.util.List;
+import net.sf.json.JSON;
+import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+import org.geoserver.platform.GeoServerExtensions;
+import org.geoserver.security.urlchecks.AbstractURLCheck;
+import org.geoserver.security.urlchecks.RegexURLCheck;
+import org.geoserver.security.urlchecks.URLCheckDAO;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.w3c.dom.Document;
+
+public class UrlCheckControllerTest extends SecurityRESTTestSupport {
+
+ private URLCheckDAO urlCheckDao;
+
+ @Before
+ public void setUp() throws Exception {
+
+ urlCheckDao = GeoServerExtensions.bean(URLCheckDAO.class, applicationContext);
+
+ RegexURLCheck check1 = new RegexURLCheck("check1", "regex check 1", "check1://.*");
+ RegexURLCheck check2 = new RegexURLCheck("check2", "regex check 2", "check2://.*");
+ RegexURLCheck check3 = new RegexURLCheck("check3", "regex check 3", "check3://.*");
+
+ check2.setEnabled(false);
+
+ urlCheckDao.saveChecks(List.of(check1, check2, check3));
+ }
+
+ @Test
+ public void testGetAllAsXml() throws Exception {
+
+ Document dom = getAsDOM(ROOT_PATH + "/urlchecks.xml");
+
+ assertXpathEvaluatesTo("3", "count(//urlCheck)", dom);
+
+ assertXpathEvaluatesTo("check1", "//urlCheck[1]/name", dom);
+ assertXpathEvaluatesTo(
+ "http://localhost:8080/geoserver/rest/urlchecks/check1.xml",
+ "//urlCheck[1]/atom:link/@href",
+ dom);
+
+ assertXpathEvaluatesTo("check2", "//urlCheck[2]/name", dom);
+ assertXpathEvaluatesTo(
+ "http://localhost:8080/geoserver/rest/urlchecks/check2.xml",
+ "//urlCheck[2]/atom:link/@href",
+ dom);
+
+ assertXpathEvaluatesTo("check3", "//urlCheck[3]/name", dom);
+ assertXpathEvaluatesTo(
+ "http://localhost:8080/geoserver/rest/urlchecks/check3.xml",
+ "//urlCheck[3]/atom:link/@href",
+ dom);
+ }
+
+ @Test
+ public void testGetAllAsJson() throws Exception {
+
+ JSON json = getAsJSON(ROOT_PATH + "/urlchecks.json");
+
+ JSONArray urlChecks =
+ ((JSONObject) json).getJSONObject("urlChecks").getJSONArray("urlCheck");
+
+ assertEquals(3, urlChecks.size());
+
+ JSONObject check1 = urlChecks.getJSONObject(0);
+ JSONObject check2 = urlChecks.getJSONObject(1);
+ JSONObject check3 = urlChecks.getJSONObject(2);
+
+ assertEquals("check1", check1.getString("name"));
+ assertEquals(
+ "http://localhost:8080/geoserver/rest/urlchecks/check1.json",
+ check1.getString("href"));
+ assertEquals("check2", check2.getString("name"));
+ assertEquals(
+ "http://localhost:8080/geoserver/rest/urlchecks/check2.json",
+ check2.getString("href"));
+ assertEquals("check3", check3.getString("name"));
+ assertEquals(
+ "http://localhost:8080/geoserver/rest/urlchecks/check3.json",
+ check3.getString("href"));
+ }
+
+ @Test
+ public void testGetAllAsHtml() throws Exception {
+
+ Document dom = getAsDOM(ROOT_PATH + "/urlchecks.html");
+
+ assertXpathEvaluatesTo("3", "count(//html:li)", dom);
+
+ assertXpathEvaluatesTo("check1", "//html:li[1]", dom);
+ assertXpathEvaluatesTo(
+ "http://localhost:8080/geoserver/rest/urlchecks/check1.html",
+ "//html:li[1]/html:a/@href",
+ dom);
+
+ assertXpathEvaluatesTo("check2", "//html:li[2]", dom);
+ assertXpathEvaluatesTo(
+ "http://localhost:8080/geoserver/rest/urlchecks/check2.html",
+ "//html:li[2]/html:a/@href",
+ dom);
+
+ assertXpathEvaluatesTo("check3", "//html:li[3]", dom);
+ assertXpathEvaluatesTo(
+ "http://localhost:8080/geoserver/rest/urlchecks/check3.html",
+ "//html:li[3]/html:a/@href",
+ dom);
+ }
+
+ @Test
+ public void testGetAsXml() throws Exception {
+
+ Document dom = getAsDOM(ROOT_PATH + "/urlchecks/check1.xml");
+
+ assertXpathEvaluatesTo("1", "count(/regexUrlCheck)", dom);
+
+ assertXpathEvaluatesTo("check1", "/regexUrlCheck/name", dom);
+ assertXpathEvaluatesTo("regex check 1", "/regexUrlCheck/description", dom);
+ assertXpathEvaluatesTo("true", "/regexUrlCheck/enabled", dom);
+ assertXpathEvaluatesTo("check1://.*", "/regexUrlCheck/regex", dom);
+ }
+
+ @Test
+ public void testGetAsJson() throws Exception {
+
+ JSON json = getAsJSON(ROOT_PATH + "/urlchecks/check2.json");
+
+ JSONObject urlCheck = ((JSONObject) json).getJSONObject("regexUrlCheck");
+
+ assertFalse(urlCheck.isNullObject());
+
+ assertEquals("check2", urlCheck.getString("name"));
+ assertEquals("regex check 2", urlCheck.getString("description"));
+ assertEquals("false", urlCheck.getString("enabled"));
+ assertEquals("check2://.*", urlCheck.getString("regex"));
+ }
+
+ @Test
+ public void testGetAsHtml() throws Exception {
+
+ Document dom = getAsDOM(ROOT_PATH + "/urlchecks/check3.html");
+
+ assertXpathEvaluatesTo("1", "count(//html:ul)", dom);
+
+ assertXpathEvaluatesTo("Name: check3", "//html:li[1]", dom);
+ assertXpathEvaluatesTo("Description: regex check 3", "//html:li[2]", dom);
+ assertXpathEvaluatesTo("Configuration: check3://.*", "//html:li[3]", dom);
+ assertXpathEvaluatesTo("Enabled: true", "//html:li[4]", dom);
+ }
+
+ @Test
+ public void testGetUnknown() throws Exception {
+
+ String checkName = "unknown";
+
+ String requestPath = ROOT_PATH + "/urlchecks/" + checkName + ".html";
+ MockHttpServletResponse response = getAsServletResponse(requestPath);
+
+ assertEquals(404, response.getStatus());
+ assertEquals("No such URL check found: '" + checkName + "'", response.getContentAsString());
+ }
+
+ @Test
+ public void testPost() throws Exception {
+
+ String checkJson =
+ "{"
+ + " \"regexUrlCheck\": {"
+ + " \"name\": \"check\","
+ + " \"description\": \"this is another check\","
+ + " \"enabled\": \"true\","
+ + " \"regex\": \"http://example.com/.*\""
+ + " }"
+ + "}";
+
+ String requestPath = ROOT_PATH + "/urlchecks";
+ MockHttpServletResponse response =
+ postAsServletResponse(requestPath, checkJson, APPLICATION_JSON_VALUE);
+
+ assertEquals(201, response.getStatus());
+ assertEquals("check", response.getContentAsString());
+ assertEquals(
+ "http://localhost:8080/geoserver/rest/urlchecks/check",
+ response.getHeader("location"));
+
+ AbstractURLCheck check = urlCheckDao.getCheckByName("check");
+ assertEquals("check", check.getName());
+ assertEquals("this is another check", check.getDescription());
+ assertTrue(check.isEnabled());
+ assertEquals("http://example.com/.*", check.getConfiguration());
+ }
+
+ @Test
+ public void testPostWhenAlreadyExists() throws Exception {
+
+ String checkJson =
+ "{"
+ + " \"regexUrlCheck\": {"
+ + " \"name\": \"check\","
+ + " \"description\": \"this is another check\","
+ + " \"enabled\": \"true\","
+ + " \"regex\": \"http://example.com/.*\""
+ + " }"
+ + "}";
+
+ String requestPath = ROOT_PATH + "/urlchecks";
+ postAsServletResponse(requestPath, checkJson, APPLICATION_JSON_VALUE);
+
+ MockHttpServletResponse response =
+ postAsServletResponse(requestPath, checkJson, APPLICATION_JSON_VALUE);
+
+ assertEquals(409, response.getStatus());
+ assertEquals("URL check 'check' already exists", response.getContentAsString());
+ }
+
+ @Test
+ public void testPostWithoutName() throws Exception {
+
+ String checkJson =
+ "{"
+ + " \"regexUrlCheck\": {"
+ + " \"description\": \"this is another check\","
+ + " \"enabled\": \"true\","
+ + " \"regex\": \"http://example.com/.*\""
+ + " }"
+ + "}";
+
+ String requestPath = ROOT_PATH + "/urlchecks";
+ MockHttpServletResponse response =
+ postAsServletResponse(requestPath, checkJson, APPLICATION_JSON_VALUE);
+
+ assertEquals(400, response.getStatus());
+ assertEquals("The URL check name is required", response.getContentAsString());
+ }
+
+ @Test
+ public void testPostWithoutConfiguration() throws Exception {
+
+ String checkJson =
+ "{"
+ + " \"regexUrlCheck\": {"
+ + " \"name\": \"check\","
+ + " \"description\": \"this is another check\","
+ + " \"enabled\": \"true\""
+ + " }"
+ + "}";
+
+ String requestPath = ROOT_PATH + "/urlchecks";
+ MockHttpServletResponse response =
+ postAsServletResponse(requestPath, checkJson, APPLICATION_JSON_VALUE);
+
+ assertEquals(400, response.getStatus());
+ assertEquals("The URL check configuration is required", response.getContentAsString());
+ }
+
+ @Test
+ public void testPostWithoutEnabled() throws Exception {
+
+ String checkJson =
+ "{"
+ + " \"regexUrlCheck\": {"
+ + " \"name\": \"check\","
+ + " \"description\": \"this is another check\","
+ + " \"regex\": \"http://example.com/.*\""
+ + " }"
+ + "}";
+
+ String requestPath = ROOT_PATH + "/urlchecks";
+ MockHttpServletResponse response =
+ postAsServletResponse(requestPath, checkJson, APPLICATION_JSON_VALUE);
+
+ assertEquals(201, response.getStatus());
+ assertEquals("check", response.getContentAsString());
+ assertEquals(
+ "http://localhost:8080/geoserver/rest/urlchecks/check",
+ response.getHeader("location"));
+
+ AbstractURLCheck check = urlCheckDao.getCheckByName("check");
+ assertFalse(check.isEnabled());
+ }
+
+ @Test
+ public void testPut() throws Exception {
+
+ String editCheckJson =
+ "{"
+ + " \"regexUrlCheck\": {"
+ + " \"name\": \"new-check\","
+ + " \"description\": \"new description\","
+ + " \"enabled\": \"false\","
+ + " \"regex\": \"new regex\""
+ + " }"
+ + "}";
+
+ String requestPath = ROOT_PATH + "/urlchecks/check1";
+ MockHttpServletResponse response =
+ putAsServletResponse(requestPath, editCheckJson, APPLICATION_JSON_VALUE);
+
+ assertEquals(200, response.getStatus());
+
+ AbstractURLCheck check = urlCheckDao.getCheckByName("new-check");
+ assertEquals("new-check", check.getName());
+ assertEquals("new description", check.getDescription());
+ assertFalse(check.isEnabled());
+ assertEquals("new regex", check.getConfiguration());
+ }
+
+ @Test
+ public void testPutUnknown() throws Exception {
+
+ String checkName = "unknown";
+ String editCheckJson = "{\"regexUrlCheck\": {}}";
+
+ String requestPath = ROOT_PATH + "/urlchecks/" + checkName;
+ MockHttpServletResponse response =
+ putAsServletResponse(requestPath, editCheckJson, APPLICATION_JSON_VALUE);
+
+ assertEquals(404, response.getStatus());
+ assertEquals(
+ "Can't change a non existent URL check (" + checkName + ")",
+ response.getContentAsString());
+ }
+
+ @Test
+ public void testPutWithEmptyConfiguration() throws Exception {
+
+ String editCheckJson = "{\"regexUrlCheck\": {\"regex\": \"\"}}";
+
+ String requestPath = ROOT_PATH + "/urlchecks/check1";
+ MockHttpServletResponse response =
+ putAsServletResponse(requestPath, editCheckJson, APPLICATION_JSON_VALUE);
+
+ assertEquals(400, response.getStatus());
+ assertEquals("The URL check configuration is required", response.getContentAsString());
+ }
+
+ @Test
+ public void testPutNoChanges() throws Exception {
+
+ String editCheckJson = "{\"regexUrlCheck\": {}}";
+
+ String requestPath = ROOT_PATH + "/urlchecks/check1";
+ MockHttpServletResponse response =
+ putAsServletResponse(requestPath, editCheckJson, APPLICATION_JSON_VALUE);
+
+ assertEquals(200, response.getStatus());
+
+ AbstractURLCheck check = urlCheckDao.getCheckByName("check1");
+ assertEquals("check1", check.getName());
+ assertEquals("regex check 1", check.getDescription());
+ assertFalse(check.isEnabled());
+ assertEquals("check1://.*", check.getConfiguration());
+ }
+
+ @Test
+ public void testDelete() throws Exception {
+
+ /* needed to avoid UnsupportedOperationException */
+ Thread.sleep(1000);
+
+ String requestPath = ROOT_PATH + "/urlchecks/check1";
+ MockHttpServletResponse response = deleteAsServletResponse(requestPath);
+
+ assertEquals(200, response.getStatus());
+
+ AbstractURLCheck check = urlCheckDao.getCheckByName("check1");
+ assertNull(check);
+ }
+}
diff --git a/src/wcs1_0/src/main/java/org/geoserver/wcs/xml/v1_0_0/WcsXmlReader.java b/src/wcs1_0/src/main/java/org/geoserver/wcs/xml/v1_0_0/WcsXmlReader.java
index 425e9aabf1c..ad9b8353d57 100644
--- a/src/wcs1_0/src/main/java/org/geoserver/wcs/xml/v1_0_0/WcsXmlReader.java
+++ b/src/wcs1_0/src/main/java/org/geoserver/wcs/xml/v1_0_0/WcsXmlReader.java
@@ -54,9 +54,8 @@ public Object read(Object request, Reader reader, Map kvp) throws Exception {
} catch (Exception e) {
throw new WcsException(
"Parsing failed, the xml request is most probably not compliant to the wcs schema",
- e);
+ cleanException(e));
}
-
return parsed;
}
}
diff --git a/src/wcs1_1/src/main/java/org/geoserver/wcs/xml/v1_1_1/WcsXmlReader.java b/src/wcs1_1/src/main/java/org/geoserver/wcs/xml/v1_1_1/WcsXmlReader.java
index d278e56968a..cb8572f09cc 100644
--- a/src/wcs1_1/src/main/java/org/geoserver/wcs/xml/v1_1_1/WcsXmlReader.java
+++ b/src/wcs1_1/src/main/java/org/geoserver/wcs/xml/v1_1_1/WcsXmlReader.java
@@ -55,7 +55,7 @@ public Object read(Object request, Reader reader, Map kvp) throws Exception {
} catch (Exception e) {
throw new WcsException(
"Parsing failed, the xml request is most probably not compliant to the wcs schema",
- e);
+ cleanException(e));
}
return parsed;
diff --git a/src/wcs2_0/src/main/java/org/geoserver/wcs2_0/xml/WcsXmlReader.java b/src/wcs2_0/src/main/java/org/geoserver/wcs2_0/xml/WcsXmlReader.java
index 9ca4be863e3..7c55b366301 100644
--- a/src/wcs2_0/src/main/java/org/geoserver/wcs2_0/xml/WcsXmlReader.java
+++ b/src/wcs2_0/src/main/java/org/geoserver/wcs2_0/xml/WcsXmlReader.java
@@ -55,7 +55,7 @@ public Object read(Object request, Reader reader, Map kvp) throws Exception {
} catch (Exception e) {
throw new WcsException(
"Parsing failed, the xml request is most probably not compliant to the wcs 2.0.1 schema",
- e);
+ cleanException(e));
}
return parsed;
diff --git a/src/wfs/src/main/java/org/geoserver/wfs/xml/v1_0_0/WfsXmlReader.java b/src/wfs/src/main/java/org/geoserver/wfs/xml/v1_0_0/WfsXmlReader.java
index c1f9c5d519f..849fb190d9c 100644
--- a/src/wfs/src/main/java/org/geoserver/wfs/xml/v1_0_0/WfsXmlReader.java
+++ b/src/wfs/src/main/java/org/geoserver/wfs/xml/v1_0_0/WfsXmlReader.java
@@ -72,8 +72,12 @@ public Object read(Object request, Reader reader, Map kvp) throws Exception {
parser.setEntityExpansionLimit(WFSXmlUtils.getEntityExpansionLimitConfiguration());
// parse
- Object parsed = parser.parse(reader);
-
+ Object parsed = null;
+ try {
+ parsed = parser.parse(reader);
+ } catch (Exception e) {
+ throw cleanException(e);
+ }
// if strict was set, check for validation errors and throw an exception
if (strict.booleanValue() && !parser.getValidationErrors().isEmpty()) {
WFSException exception = new WFSException("Invalid request", "InvalidParameterValue");
diff --git a/src/wfs/src/main/java/org/geoserver/wfs/xml/v1_1_0/WfsXmlReader.java b/src/wfs/src/main/java/org/geoserver/wfs/xml/v1_1_0/WfsXmlReader.java
index 0d06c67735e..0c985f15a02 100644
--- a/src/wfs/src/main/java/org/geoserver/wfs/xml/v1_1_0/WfsXmlReader.java
+++ b/src/wfs/src/main/java/org/geoserver/wfs/xml/v1_1_0/WfsXmlReader.java
@@ -5,6 +5,7 @@
*/
package org.geoserver.wfs.xml.v1_1_0;
+import java.io.IOException;
import java.io.Reader;
import java.util.Map;
import javax.xml.namespace.QName;
@@ -16,6 +17,7 @@
import org.geotools.util.Version;
import org.geotools.xsd.Configuration;
import org.geotools.xsd.Parser;
+import org.xml.sax.SAXException;
/**
* Xml reader for wfs 1.1.0 xml requests.
@@ -59,11 +61,17 @@ public Object read(Object request, Reader reader, Map kvp) throws Exception {
// set entity expansion limit
parser.setEntityExpansionLimit(WFSXmlUtils.getEntityExpansionLimitConfiguration());
- WFSXmlUtils.initRequestParser(parser, wfs, geoServer, kvp);
- Object parsed = WFSXmlUtils.parseRequest(parser, reader, wfs);
+ try {
+ WFSXmlUtils.initRequestParser(parser, wfs, geoServer, kvp);
+ Object parsed = WFSXmlUtils.parseRequest(parser, reader, wfs);
- WFSXmlUtils.checkValidationErrors(parser, this);
+ WFSXmlUtils.checkValidationErrors(parser, this);
- return parsed;
+ return parsed;
+ } catch (IOException e) {
+ throw cleanException(e);
+ } catch (SAXException e) {
+ throw cleanSaxException(e);
+ }
}
}
diff --git a/src/wfs/src/main/java/org/geoserver/wfs/xml/v2_0/WfsXmlReader.java b/src/wfs/src/main/java/org/geoserver/wfs/xml/v2_0/WfsXmlReader.java
index 2c3b6aee20d..b18bed0513c 100644
--- a/src/wfs/src/main/java/org/geoserver/wfs/xml/v2_0/WfsXmlReader.java
+++ b/src/wfs/src/main/java/org/geoserver/wfs/xml/v2_0/WfsXmlReader.java
@@ -48,13 +48,17 @@ public Object read(Object request, Reader reader, Map kvp) throws Exception {
WFSInfo wfs = wfs();
WFSXmlUtils.initRequestParser(parser, wfs, gs, kvp);
- Object parsed = null;
+ Object parsed;
try {
parsed = WFSXmlUtils.parseRequest(parser, reader, wfs);
} catch (Exception e) {
// check the exception, and set code to OperationParsingFailed if code not set
if (!(e instanceof ServiceException) || ((ServiceException) e).getCode() == null) {
- e = new WFSException("Request parsing failed", e, "OperationParsingFailed");
+ e =
+ new WFSException(
+ "Request parsing failed",
+ cleanException(e),
+ "OperationParsingFailed");
}
throw e;
}
diff --git a/src/wfs/src/test/java/org/geoserver/wfs/ExternalEntitiesTest.java b/src/wfs/src/test/java/org/geoserver/wfs/ExternalEntitiesTest.java
index 444a3c67e0d..1fba0f9153a 100644
--- a/src/wfs/src/test/java/org/geoserver/wfs/ExternalEntitiesTest.java
+++ b/src/wfs/src/test/java/org/geoserver/wfs/ExternalEntitiesTest.java
@@ -208,21 +208,25 @@ public void testWfs1_0() throws Exception {
String output = string(post("wfs", WFS_1_0_0_REQUEST));
// the server tried to read a file on local file system
- Assert.assertTrue(output.indexOf("java.io.FileNotFoundException") > -1);
+ Assert.assertTrue(
+ "FileNotFoundException",
+ output.indexOf(
+ "xml request is most probably not compliant to GetFeature element")
+ > -1);
// disable entity parsing
cfg.setXmlExternalEntitiesEnabled(false);
getGeoServer().save(cfg);
output = string(post("wfs", WFS_1_0_0_REQUEST));
- Assert.assertTrue(output.indexOf("Entity resolution disallowed") > -1);
+ Assert.assertTrue("disallowed", output.indexOf("Entity resolution disallowed") > -1);
// set default (entity parsing disabled);
cfg.setXmlExternalEntitiesEnabled(null);
getGeoServer().save(cfg);
output = string(post("wfs", WFS_1_0_0_REQUEST));
- Assert.assertTrue(output.indexOf("Entity resolution disallowed") > -1);
+ Assert.assertTrue("disallowed", output.indexOf("Entity resolution disallowed") > -1);
} finally {
cfg.setXmlExternalEntitiesEnabled(null);
getGeoServer().save(cfg);
@@ -239,21 +243,25 @@ public void testWfs1_1() throws Exception {
String output = string(post("wfs", WFS_1_1_0_REQUEST));
// the server tried to read a file on local file system
- Assert.assertTrue(output.indexOf("java.io.FileNotFoundException") > -1);
+ Assert.assertTrue(
+ "FileNotFoundException",
+ output.indexOf(
+ "xml request is most probably not compliant to GetFeature element")
+ > -1);
// disable entity parsing
cfg.setXmlExternalEntitiesEnabled(false);
getGeoServer().save(cfg);
output = string(post("wfs", WFS_1_1_0_REQUEST));
- Assert.assertTrue(output.indexOf("Entity resolution disallowed") > -1);
+ Assert.assertTrue("disallowed", output.indexOf("Entity resolution disallowed") > -1);
// set default (entity parsing disabled);
cfg.setXmlExternalEntitiesEnabled(null);
getGeoServer().save(cfg);
output = string(post("wfs", WFS_1_1_0_REQUEST));
- Assert.assertTrue(output.indexOf("Entity resolution disallowed") > -1);
+ Assert.assertTrue("disallowed", output.indexOf("Entity resolution disallowed") > -1);
} finally {
cfg.setXmlExternalEntitiesEnabled(null);
getGeoServer().save(cfg);
@@ -270,7 +278,10 @@ public void testWfs2_0() throws Exception {
String output = string(post("wfs", WFS_2_0_0_REQUEST));
// the server tried to read a file on local file system
- Assert.assertTrue(output.indexOf("thisfiledoesnotexist") > -1);
+ Assert.assertTrue(
+ output.indexOf(
+ "xml request is most probably not compliant to GetFeature element")
+ > -1);
// disable entity parsing
cfg.setXmlExternalEntitiesEnabled(false);
diff --git a/src/wms/src/main/java/org/geoserver/sld/SLDXmlRequestReader.java b/src/wms/src/main/java/org/geoserver/sld/SLDXmlRequestReader.java
index 6827d5a35af..b07346e643b 100644
--- a/src/wms/src/main/java/org/geoserver/sld/SLDXmlRequestReader.java
+++ b/src/wms/src/main/java/org/geoserver/sld/SLDXmlRequestReader.java
@@ -5,10 +5,12 @@
*/
package org.geoserver.sld;
+import java.io.IOException;
import java.io.Reader;
import java.util.Map;
import org.geoserver.catalog.Styles;
import org.geoserver.ows.XmlRequestReader;
+import org.geoserver.platform.ServiceException;
import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.WMS;
import org.geoserver.wms.map.ProcessStandaloneSLDVisitor;
@@ -33,16 +35,19 @@ public Object read(Object request, Reader reader, Map kvp) throws Exception {
if (request == null) {
throw new IllegalArgumentException("request must be not null");
}
-
- GetMapRequest getMap = (GetMapRequest) request;
- StyledLayerDescriptor sld =
- Styles.handler(getMap.getStyleFormat())
- .parse(reader, getMap.styleVersion(), null, null);
-
- // process the sld
- sld.accept(new ProcessStandaloneSLDVisitor(wms, getMap));
- // GetMapKvpRequestReader.processStandaloneSld(wms, getMap, sld);
-
- return getMap;
+ try {
+ GetMapRequest getMap = (GetMapRequest) request;
+ StyledLayerDescriptor sld =
+ Styles.handler(getMap.getStyleFormat())
+ .parse(reader, getMap.styleVersion(), null, null);
+
+ // process the sld
+ sld.accept(new ProcessStandaloneSLDVisitor(wms, getMap));
+ // GetMapKvpRequestReader.processStandaloneSld(wms, getMap, sld);
+
+ return getMap;
+ } catch (IOException e) {
+ throw new ServiceException(cleanException(e));
+ }
}
}
diff --git a/src/wms/src/main/java/org/geoserver/wms/capabilities/CapabilitiesXmlReader.java b/src/wms/src/main/java/org/geoserver/wms/capabilities/CapabilitiesXmlReader.java
index f80336d0f5d..79621777a27 100644
--- a/src/wms/src/main/java/org/geoserver/wms/capabilities/CapabilitiesXmlReader.java
+++ b/src/wms/src/main/java/org/geoserver/wms/capabilities/CapabilitiesXmlReader.java
@@ -61,13 +61,17 @@ public Object read(Object request, Reader reader, Map kvp) throws Exception {
adapter.parse(new InputSource(reader));
} catch (SAXException e) {
throw new ServiceException(
- e, "XML capabilities request parsing error", getClass().getName());
+ cleanSaxException((SAXException) e),
+ "XML capabilities request parsing error",
+ getClass().getName());
} catch (IOException e) {
throw new ServiceException(
- e, "XML capabilities request input error", getClass().getName());
+ cleanException(e),
+ "XML capabilities request input error",
+ getClass().getName());
} catch (ParserConfigurationException e) {
throw new ServiceException(
- e, "Some sort of issue creating parser", getClass().getName());
+ cleanException(e), "Some sort of issue creating parser", getClass().getName());
}
return req;
diff --git a/src/wms/src/main/java/org/geoserver/wms/map/GetMapXmlReader.java b/src/wms/src/main/java/org/geoserver/wms/map/GetMapXmlReader.java
index e4882bbb9f6..5ca8a3f18ab 100644
--- a/src/wms/src/main/java/org/geoserver/wms/map/GetMapXmlReader.java
+++ b/src/wms/src/main/java/org/geoserver/wms/map/GetMapXmlReader.java
@@ -137,8 +137,10 @@ public Object read(Object request, Reader reader, Map kvp) throws Exception {
+ se.getColumnNumber()
+ " -- "
+ se.getLocalizedMessage());
+ } catch (SAXException sax) {
+ throw new ServiceException(cleanSaxException(sax));
} catch (Exception e) {
- throw new ServiceException(e);
+ throw new ServiceException(cleanException(e));
}
return getMapRequest;
diff --git a/src/wms/src/test/java/org/geoserver/wms/map/GetMapXmlReaderTest.java b/src/wms/src/test/java/org/geoserver/wms/map/GetMapXmlReaderTest.java
index aff5f7149cc..ac22c59bf83 100644
--- a/src/wms/src/test/java/org/geoserver/wms/map/GetMapXmlReaderTest.java
+++ b/src/wms/src/test/java/org/geoserver/wms/map/GetMapXmlReaderTest.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.BufferedReader;
import java.io.IOException;
@@ -16,11 +17,14 @@
import org.geoserver.catalog.CatalogBuilder;
import org.geoserver.catalog.CatalogFactory;
import org.geoserver.catalog.LayerGroupInfo;
+import org.geoserver.config.GeoServerInfo;
import org.geoserver.config.GeoServerLoader;
import org.geoserver.data.test.MockData;
import org.geoserver.ows.Dispatcher;
import org.geoserver.platform.ServiceException;
+import org.geoserver.test.GeoServerSystemTestSupport;
import org.geoserver.test.ows.KvpRequestReaderTestSupport;
+import org.geoserver.util.EntityResolverProvider;
import org.geoserver.wms.GetMapRequest;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSInfo;
@@ -28,6 +32,7 @@
import org.geotools.api.filter.PropertyIsEqualTo;
import org.geotools.api.style.Style;
import org.junit.Test;
+import org.xml.sax.SAXException;
public class GetMapXmlReaderTest extends KvpRequestReaderTestSupport {
GetMapXmlReader reader;
@@ -132,6 +137,32 @@ public void testAllowDynamicStyles() throws Exception {
}
}
+ @Test
+ public void testCleanServiceException() throws Exception {
+ GeoServerInfo cfg = getGeoServer().getGlobal();
+ GetMapRequest request = reader.createRequest();
+ // this request forces an IOException
+ try (BufferedReader input = getResourceInputStream("WMSPostServiceException.xml")) {
+
+ cfg.setXmlExternalEntitiesEnabled(true);
+ getGeoServer().save(cfg);
+
+ request = (GetMapRequest) reader.read(request, input, new HashMap());
+ fail("ServiceException with IOException Expected");
+ } catch (ServiceException e) {
+ assertTrue(
+ e.getMessage()
+ .contains(
+ "xml request is most probably not compliant to GetMap element"));
+ assertTrue(e.getCause() instanceof SAXException);
+ } finally {
+ cfg.setXmlExternalEntitiesEnabled(null);
+ getGeoServer().save(cfg);
+ EntityResolverProvider.setEntityResolver(
+ GeoServerSystemTestSupport.RESOLVE_DISABLED_PROVIDER_DEVMODE);
+ }
+ }
+
@SuppressWarnings("PMD.CloseResource") // wrapped and returned
private BufferedReader getResourceInputStream(String classRelativePath) throws IOException {
InputStream resourceStream = getClass().getResource(classRelativePath).openStream();
diff --git a/src/wms/src/test/resources/org/geoserver/wms/map/WMSPostServiceException.xml b/src/wms/src/test/resources/org/geoserver/wms/map/WMSPostServiceException.xml
new file mode 100644
index 00000000000..0b36d5ef950
--- /dev/null
+++ b/src/wms/src/test/resources/org/geoserver/wms/map/WMSPostServiceException.xml
@@ -0,0 +1,23 @@
+
+
+ %external;
+ ]>
+