From f0a71860ebb8ec2d4c4f27db1fe4c677b8194a1a Mon Sep 17 00:00:00 2001 From: Johannes Weskamm Date: Thu, 9 Jul 2020 19:59:56 +0200 Subject: [PATCH 1/4] [WIP] interceptor work --- .../layer/source/WfsLayerDataSource.java | 135 ++++++++++ .../shoguncore/util/enumeration/OgcEnum.java | 23 +- .../util/interceptor/OgcMessage.java | 35 +++ .../interceptor/OgcMessageDistributor.java | 68 +++++ .../WmtsRequestInterceptorInterface.java | 14 + .../WmtsResponseInterceptorInterface.java | 15 ++ .../secure/WfsRequestInterceptor.java | 233 +++++++++++++++++ .../secure/WfsResponseInterceptor.java | 136 ++++++++++ .../secure/WmsRequestInterceptor.java | 185 ++++++++++++++ .../secure/WmsResponseInterceptor.java | 239 ++++++++++++++++++ .../secure/WmtsRequestInterceptor.java | 156 ++++++++++++ .../secure/WmtsResponseInterceptor.java | 216 ++++++++++++++++ ..._artifactId__-context-initialize-beans.xml | 40 ++- .../spring/__artifactId__-context.xml | 8 +- 14 files changed, 1494 insertions(+), 9 deletions(-) create mode 100644 src/shogun-core-main/src/main/java/de/terrestris/shoguncore/model/layer/source/WfsLayerDataSource.java create mode 100644 src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/WmtsRequestInterceptorInterface.java create mode 100644 src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/WmtsResponseInterceptorInterface.java create mode 100644 src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsRequestInterceptor.java create mode 100644 src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsResponseInterceptor.java create mode 100644 src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmsRequestInterceptor.java create mode 100644 src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmsResponseInterceptor.java create mode 100644 src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsRequestInterceptor.java create mode 100644 src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsResponseInterceptor.java diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/model/layer/source/WfsLayerDataSource.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/model/layer/source/WfsLayerDataSource.java new file mode 100644 index 000000000..f4b3f794e --- /dev/null +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/model/layer/source/WfsLayerDataSource.java @@ -0,0 +1,135 @@ +package de.terrestris.shoguncore.model.layer.source; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +import javax.persistence.Cacheable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * Class representing a layer data source for WFS layers + * + * @author Johannes Weskamm + */ +@Entity +@Table +@Cacheable +public class WfsLayerDataSource extends LayerDataSource { + + /** + * + */ + private static final long serialVersionUID = 1L; + + /** + * + */ + @Column + private String version; + + /** + * used for versions later than 1.1.0 + */ + @Column(length = 2048) + private String typeName; + + /** + * used for versions 1.1.0 and earlier + */ + @Column(length = 2048) + private String typeNames; + + /** + * + */ + public WfsLayerDataSource() { + super(); + } + + /** + * @param name Name of datasource + * @param type Type of datasource + * @param url URL of datasource + * @param format The format + * @param version WFS version + * @param typeName Thr type name + * @param typeNames Thr type names + */ + public WfsLayerDataSource(String name, String type, String url, String format, String version, String typeName, String typeNames) { + super(name, type, url, format); + this.version = version; + this.typeName = typeName; + this.typeNames = typeNames; + } + + /** + * @return the version + */ + public String getVersion() { + return version; + } + + /** + * @param version the version to set + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * @return the typeName + */ + public String getTypeName() { + return typeName; + } + + /** + * @param typeName the typeName to set + */ + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + /** + * @return the typeNames + */ + public String getTypeNames() { + return typeNames; + } + + /** + * @param typeNames the typeNames to set + */ + public void setTypeNames(String typeNames) { + this.typeNames = typeNames; + } + + @Override + public int hashCode() { + // two randomly chosen prime numbers + return new HashCodeBuilder(67, 59). + appendSuper(super.hashCode()). + append(getVersion()). + append(getTypeName()). + append(getTypeNames()). + toHashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof WfsLayerDataSource)) { + return false; + } + WfsLayerDataSource other = (WfsLayerDataSource) obj; + + return new EqualsBuilder(). + appendSuper(super.equals(other)). + append(getVersion(), other.getVersion()). + append(getTypeName(), other.getTypeName()). + append(getTypeNames(), other.getTypeNames()). + isEquals(); + } + +} diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/enumeration/OgcEnum.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/enumeration/OgcEnum.java index 0f9a67790..c2ab39efb 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/enumeration/OgcEnum.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/enumeration/OgcEnum.java @@ -35,6 +35,11 @@ public class OgcEnum { wmsOps.add(OperationType.GET_LEGEND_GRAPHIC); wmsOps.add(OperationType.GET_STYLES); + Set wmtsOps = new HashSet(); + wmtsOps.add(OperationType.GET_CAPABILITIES); + wmtsOps.add(OperationType.GET_TILE); + wmtsOps.add(OperationType.GET_FEATURE_INFO); + Set wfsOps = new HashSet(); wfsOps.add(OperationType.GET_CAPABILITIES); wfsOps.add(OperationType.DESCRIBE_FEATURE_TYPE); @@ -61,6 +66,7 @@ public class OgcEnum { map.put(ServiceType.WMS, Collections.unmodifiableSet(wmsOps)); + map.put(ServiceType.WMTS, Collections.unmodifiableSet(wmtsOps)); map.put(ServiceType.WFS, Collections.unmodifiableSet(wfsOps)); map.put(ServiceType.WCS, Collections.unmodifiableSet(wcsOps)); map.put(ServiceType.WPS, Collections.unmodifiableSet(wpsOps)); @@ -78,6 +84,11 @@ public class OgcEnum { wmsSet.add(ServiceType.WMS); wmsSet = Collections.unmodifiableSet(wmsSet); + // A set containing only the WMTS ServiceType + Set wmtsSet = new HashSet(); + wmtsSet.add(ServiceType.WMTS); + wmtsSet = Collections.unmodifiableSet(wmtsSet); + // A set containing only the WFS ServiceType Set wfsSet = new HashSet(); wfsSet.add(ServiceType.WFS); @@ -93,9 +104,10 @@ public class OgcEnum { wpsSet.add(ServiceType.WPS); wpsSet = Collections.unmodifiableSet(wpsSet); - // A set containing the WMS, WFS, WCS and WPS ServiceTypes + // A set containing the WMS, WMTS, WFS, WCS and WPS ServiceTypes Set getCapSet = new HashSet(); getCapSet.add(ServiceType.WMS); + getCapSet.add(ServiceType.WMTS); getCapSet.add(ServiceType.WFS); getCapSet.add(ServiceType.WCS); getCapSet.add(ServiceType.WPS); @@ -103,6 +115,8 @@ public class OgcEnum { // look up all WMS operations from the previously created map Set wmsOperations = OPERATIONS_BY_SERVICETYPE.get(ServiceType.WMS); + // look up all WMTS operations from the previously created map + Set wmtsOperations = OPERATIONS_BY_SERVICETYPE.get(ServiceType.WMTS); // look up all WFS operations from the previously created map Set wfsOperations = OPERATIONS_BY_SERVICETYPE.get(ServiceType.WFS); // look up all WCS operations from the previously created map @@ -118,6 +132,12 @@ public class OgcEnum { map.put(wmsOperation, wmsSet); } } + // for WMTS operations, put the WMTS set, unless it's the GetCapability op + for (OperationType wmtsOperation : wmtsOperations) { + if (!OperationType.GET_CAPABILITIES.equals(wmtsOperation)) { + map.put(wmtsOperation, wmtsSet); + } + } // for WFS operations, put the WFS set, unless it's the GetCapability op for (OperationType wfsOperation : wfsOperations) { if (!OperationType.GET_CAPABILITIES.equals(wfsOperation)) { @@ -319,6 +339,7 @@ public String toString() { */ public enum ServiceType { WMS("WMS"), + WMTS("WMTS"), WFS("WFS"), WCS("WCS"), WPS("WPS"), diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/OgcMessage.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/OgcMessage.java index 1e6e18110..c75fddaa3 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/OgcMessage.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/OgcMessage.java @@ -155,6 +155,14 @@ public boolean isWms() { this.getService().equals(OgcEnum.ServiceType.WMS); } + /** + * @return + */ + public boolean isWmts() { + return this.getService() != null && + this.getService().equals(OgcEnum.ServiceType.WMTS); + } + /** * @return */ @@ -241,6 +249,33 @@ public boolean isWmsGetStyles() { this.getOperation().equals(OgcEnum.OperationType.GET_STYLES); } + /** + * @return + */ + public boolean isWmtsGetCapabilities() { + return this.isWmts() && + this.getOperation() != null && + this.getOperation().equals(OgcEnum.OperationType.GET_CAPABILITIES); + } + + /** + * @return + */ + public boolean isWmtsGetTile() { + return this.isWmts() && + this.getOperation() != null && + this.getOperation().equals(OgcEnum.OperationType.GET_TILE); + } + + /** + * @return + */ + public boolean isWmtsGetFeatureInfo() { + return this.isWmts() && + this.getOperation() != null && + this.getOperation().equals(OgcEnum.OperationType.GET_FEATURE_INFO); + } + /** * @return */ diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/OgcMessageDistributor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/OgcMessageDistributor.java index bfdd026a0..7c7202cd3 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/OgcMessageDistributor.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/OgcMessageDistributor.java @@ -67,6 +67,13 @@ public class OgcMessageDistributor { @Qualifier("wmsRequestInterceptor") private WmsRequestInterceptorInterface wmsRequestInterceptor; + /** + * + */ + @Autowired(required = false) + @Qualifier("wmtsRequestInterceptor") + private WmtsRequestInterceptorInterface wmtsRequestInterceptor; + /** * */ @@ -95,6 +102,13 @@ public class OgcMessageDistributor { @Qualifier("wmsResponseInterceptor") private WmsResponseInterceptorInterface wmsResponseInterceptor; + /** + * + */ + @Autowired(required = false) + @Qualifier("wmtsResponseInterceptor") + private WmtsResponseInterceptorInterface wmtsResponseInterceptor; + /** * */ @@ -170,6 +184,24 @@ public MutableHttpServletRequest distributeToRequestInterceptor( throw new InterceptorException(operationErrMsg); } + } else if (message.isWmts()) { + // check if the wmtsRequestInterceptor is available + if (this.wmtsRequestInterceptor == null) { + LOG.debug(implErrMsg); + return request; + } + + LOG.debug(infoMsg); + + if (message.isWmtsGetCapabilities()) { + request = this.wmtsRequestInterceptor.interceptGetCapabilities(request); + } else if (message.isWmtsGetTile()) { + request = this.wmtsRequestInterceptor.interceptGetTile(request); + } else if (message.isWmtsGetFeatureInfo()) { + request = this.wmtsRequestInterceptor.interceptGetFeatureInfo(request); + } else { + throw new InterceptorException(operationErrMsg); + } } else if (message.isWfs()) { // check if the wfsRequestInterceptor is available @@ -302,6 +334,26 @@ public Response distributeToResponseInterceptor(MutableHttpServletRequest mutabl throw new InterceptorException(operationErrMsg); } + } else if (message.isWmts()) { + + // check if the wmtsResponseInterceptor is available + if (this.wmtsResponseInterceptor == null) { + LOG.debug(implErrMsg); + return response; + } + + LOG.debug(infoMsg); + + if (message.isWmtsGetCapabilities()) { + response = this.wmtsResponseInterceptor.interceptGetCapabilities(mutableRequest, response); + } else if (message.isWmtsGetTile()) { + response = this.wmtsResponseInterceptor.interceptGetTile(mutableRequest, response); + } else if (message.isWmtsGetFeatureInfo()) { + response = this.wmtsResponseInterceptor.interceptGetFeatureInfo(mutableRequest, response); + } else { + throw new InterceptorException(operationErrMsg); + } + } else if (message.isWfs()) { // check if the wfsResponseInterceptor is available @@ -387,6 +439,14 @@ public void setWmsRequestInterceptor( this.wmsRequestInterceptor = wmsRequestInterceptor; } + /** + * @param wmtsRequestInterceptor the wmtsRequestInterceptor to set + */ + public void setWmtsRequestInterceptor( + WmtsRequestInterceptorInterface wmtsRequestInterceptor) { + this.wmtsRequestInterceptor = wmtsRequestInterceptor; + } + /** * @param wfsRequestInterceptor the wfsRequestInterceptor to set */ @@ -425,6 +485,14 @@ public void setWmsResponseInterceptor( this.wmsResponseInterceptor = wmsResponseInterceptor; } + /** + * @param wmtsResponseInterceptor the wmtsResponseInterceptor to set + */ + public void setWmtsResponseInterceptor( + WmtsResponseInterceptorInterface wmtsResponseInterceptor) { + this.wmtsResponseInterceptor = wmtsResponseInterceptor; + } + /** * @param wfsResponseInterceptor the wfsResponseInterceptor to set */ diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/WmtsRequestInterceptorInterface.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/WmtsRequestInterceptorInterface.java new file mode 100644 index 000000000..708d20ee3 --- /dev/null +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/WmtsRequestInterceptorInterface.java @@ -0,0 +1,14 @@ +package de.terrestris.shoguncore.util.interceptor; + +import org.springframework.stereotype.Component; + +@Component +public interface WmtsRequestInterceptorInterface { + + MutableHttpServletRequest interceptGetTile(MutableHttpServletRequest request); + + MutableHttpServletRequest interceptGetCapabilities(MutableHttpServletRequest request); + + MutableHttpServletRequest interceptGetFeatureInfo(MutableHttpServletRequest request); + +} diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/WmtsResponseInterceptorInterface.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/WmtsResponseInterceptorInterface.java new file mode 100644 index 000000000..67bcb20d7 --- /dev/null +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/WmtsResponseInterceptorInterface.java @@ -0,0 +1,15 @@ +package de.terrestris.shoguncore.util.interceptor; + +import de.terrestris.shoguncore.util.model.Response; +import org.springframework.stereotype.Component; + +@Component +public interface WmtsResponseInterceptorInterface { + + Response interceptGetTile(MutableHttpServletRequest request, Response response); + + Response interceptGetCapabilities(MutableHttpServletRequest request, Response response); + + Response interceptGetFeatureInfo(MutableHttpServletRequest request, Response response); + +} diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsRequestInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsRequestInterceptor.java new file mode 100644 index 000000000..402a24720 --- /dev/null +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsRequestInterceptor.java @@ -0,0 +1,233 @@ +package de.terrestris.shoguncore.util.interceptor.secure; + +import de.terrestris.shoguncore.dao.LayerDao; +import de.terrestris.shoguncore.dao.UserDao; +import de.terrestris.shoguncore.model.User; +import de.terrestris.shoguncore.model.UserGroup; +import de.terrestris.shoguncore.model.layer.Layer; +import de.terrestris.shoguncore.model.layer.source.WfsLayerDataSource; +import de.terrestris.shoguncore.model.security.Permission; +import de.terrestris.shoguncore.model.security.PermissionCollection; +import de.terrestris.shoguncore.service.LayerService; +import de.terrestris.shoguncore.service.UserService; +import de.terrestris.shoguncore.util.application.ShogunCoreContextUtil; +import de.terrestris.shoguncore.util.interceptor.GeoserverAuthHeaderRequest; +import de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest; +import de.terrestris.shoguncore.util.interceptor.WfsRequestInterceptorInterface; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; + +import static org.apache.logging.log4j.LogManager.getLogger; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Interceptor class for WFS requests. Adds basic auth headers based on the GS + * properties by default and checks for permission on layer + * + * @author Nils Bühner + * @author Johannes Weskamm + * + */ +public class WfsRequestInterceptor implements WfsRequestInterceptorInterface { + + /** + * + */ + private URI appUri = null; + + /** + * + */ + private static final Logger LOG = getLogger(WfsRequestInterceptor.class); + + /** + * + */ + @Autowired + @Qualifier("userService") + protected UserService> userService; + + /** + * + */ + @Autowired + @Qualifier("layerService") + protected LayerService> layerService; + + /** + * + */ + @Value("${geoserver.username:}") + private String gsUser; + + /** + * + */ + @Value("${geoserver.password:}") + private String gsPass; + + /** + * + */ + @Override + public MutableHttpServletRequest interceptGetCapabilities(MutableHttpServletRequest request) { + LOG.debug("Intercepting WFS GetCapabilities"); + return new GeoserverAuthHeaderRequest(request, gsUser, gsPass); + } + + /** + * + */ + @Override + public MutableHttpServletRequest interceptDescribeFeatureType(MutableHttpServletRequest request) { + LOG.debug("Intercepting WFS DescribeFeatureType"); + return new GeoserverAuthHeaderRequest(request, gsUser, gsPass); + } + + /** + * + */ + @Override + public MutableHttpServletRequest interceptGetFeature(MutableHttpServletRequest request) { + LOG.debug("Intercepting WFS GetFeature"); + return isAllowed(request, "READ") ? new GeoserverAuthHeaderRequest( + request, gsUser, gsPass) : forbidRequest(request); + } + + /** + * + */ + @Override + public MutableHttpServletRequest interceptLockFeature(MutableHttpServletRequest request) { + LOG.debug("Intercepting WFS LockFeature"); + return isAllowed(request, "UPDATE") ? new GeoserverAuthHeaderRequest( + request, gsUser, gsPass) : forbidRequest(request); + } + + /** + * + */ + @Override + public MutableHttpServletRequest interceptTransaction(MutableHttpServletRequest request) { + LOG.debug("Intercepting WFS Transaction"); + return isAllowed(request, "UPDATE") ? new GeoserverAuthHeaderRequest( + request, gsUser, gsPass) : forbidRequest(request); + } + + /** + * Check for permission on layer by getting it through service. + * For update permissions, we check through custom method. + * + * @param request The request + * @param paramName The optional parameter name for the layer parameter + * @param method The access method like read or write + * @return if the layer is allowed to read for current user + */ + private boolean isAllowed( + MutableHttpServletRequest request, String paramName, String method) { + String typeNameParam = request.getParameterIgnoreCase(paramName); + List all = layerService.findAll(); + boolean match = false; + for (Layer layer : all) { + if (layer.getSource() instanceof WfsLayerDataSource) { + WfsLayerDataSource source = (WfsLayerDataSource) + layer.getSource(); + if ((source.getTypeName().equalsIgnoreCase(typeNameParam) || + source.getTypeNames().equalsIgnoreCase(typeNameParam)) && + source.getUrl().equalsIgnoreCase(request.getContextPath() + + "/geoserver.action")) { + if (method.equals("UPDATE")) { + match = checkForPermission(layer, Permission.UPDATE); + } else if (method.equals("READ")) { + // implicitly checked by findAll method + match = true; + } + } + } + } + return match; + } + + /** + * Calls main method with default "typename" parameter + * + * @param request The request + * @param method The access method like read or write + * @return If the layer is allowed to read for current user + */ + private boolean isAllowed(MutableHttpServletRequest request, String method) { + String typeNameParam = "typeNames"; + if (StringUtils.isEmpty(request.getParameterIgnoreCase(typeNameParam))) { + typeNameParam = "typeName"; + } + return isAllowed(request, typeNameParam, method); + } + + /** + * check if user has permission on given layer by user or group permissions + * + * @param layer + * @param permission + * @return + */ + private boolean checkForPermission(Layer layer, Permission permission) { + Map up = layer.getUserPermissions(); + Map gp = layer.getGroupPermissions(); + User user = userService.getUserBySession(); + boolean hasUser = up.containsKey(user); + boolean allowedOnUser = false; + if (hasUser) { + PermissionCollection pc = up.get(user); + allowedOnUser = pc.getPermissions().contains(permission); + } + + boolean allowedOnGroup = false; + Set groups = user.getUserGroups(); + if (groups != null) { + for (UserGroup group : groups) { + if (gp.containsKey(group) && !allowedOnGroup) { + allowedOnGroup = gp.get(group).getPermissions().contains( + permission); + } + } + } + return allowedOnUser || allowedOnGroup; + } + + /** + *

forbidRequest.

+ * + * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. + * @return a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. + */ + protected MutableHttpServletRequest forbidRequest(MutableHttpServletRequest request) { + this.setAppUriFromRequest(request); + String redirectUri = appUri == null ? "" : appUri.toString(); + request.setRequestURI(redirectUri + "/response/forbidden.action"); + return request; + } + + /** + *

setAppUriFromRequest.

+ * + * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. + */ + protected void setAppUriFromRequest(MutableHttpServletRequest request) { + if (appUri == null) { + try { + appUri = ShogunCoreContextUtil.getApplicationURIFromRequest(request); + } catch (URISyntaxException e) { + // pass + } + } + } +} diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsResponseInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsResponseInterceptor.java new file mode 100644 index 000000000..93aa79148 --- /dev/null +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsResponseInterceptor.java @@ -0,0 +1,136 @@ +package de.terrestris.shoguncore.util.interceptor.secure; + +import de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest; +import de.terrestris.shoguncore.util.interceptor.WfsResponseInterceptorInterface; +import de.terrestris.shoguncore.util.model.Response; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.deegree.commons.xml.CommonNamespaces; +import org.deegree.commons.xml.NamespaceBindings; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import static javax.xml.xpath.XPathConstants.NODESET; +import static org.apache.logging.log4j.LogManager.getLogger; + +public class WfsResponseInterceptor implements WfsResponseInterceptorInterface { + + private static final Logger LOG = getLogger(WfsResponseInterceptor.class); + + private void interceptGetCapabilities100(Document doc, String baseUrl) throws XPathExpressionException { + XPath xpath = XPathFactory.newInstance().newXPath(); + // xpath seems not to work with the default bindings + NodeList list = (NodeList) xpath.compile("//*[local-name()=\"Get\"]").evaluate(doc.getDocumentElement(), NODESET); + for (int i = 0; i < list.getLength(); ++i) { + Element node = (Element) list.item(i); + node.setAttribute("onlineResource", baseUrl); + } + list = (NodeList) xpath.compile("//*[local-name()=\"Post\"]").evaluate(doc.getDocumentElement(), NODESET); + for (int i = 0; i < list.getLength(); ++i) { + Element node = (Element) list.item(i); + node.setAttribute("onlineResource", baseUrl); + } + } + + private void interceptGetCapabilities110And200(Document doc, String baseUrl, String owsNamespace) throws XPathExpressionException { + NamespaceBindings nscontext = new NamespaceBindings(); + nscontext.addNamespace("ows", owsNamespace); + nscontext.addNamespace("xlink", "http://www.w3.org/1999/xlink"); + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(nscontext); + NodeList list = (NodeList) xpath.compile("//ows:Get").evaluate(doc.getDocumentElement(), NODESET); + for (int i = 0; i < list.getLength(); ++i) { + Element node = (Element) list.item(i); + node.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", baseUrl); + } + list = (NodeList) xpath.compile("//ows:Post").evaluate(doc.getDocumentElement(), NODESET); + for (int i = 0; i < list.getLength(); ++i) { + Element node = (Element) list.item(i); + node.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", baseUrl); + } + } + + @Override + public Response interceptGetCapabilities(MutableHttpServletRequest request, Response response) { + String proto = request.getHeader("x-forwarded-proto"); + String requestHost = request.getHeader("x-forwarded-host"); + // fallbacks in case SHOGun is not behind a reverse proxy + if (StringUtils.isEmpty(proto)) { + proto = request.getScheme(); + } + if (StringUtils.isEmpty(requestHost)) { + requestHost = request.getServerName(); + } + String baseUrl = proto + "://" + requestHost + request.getContextPath() + "/geoserver.action"; + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try { + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + builderFactory.setNamespaceAware(true); + DocumentBuilder builder = builderFactory.newDocumentBuilder(); + Document doc = builder.parse(new ByteArrayInputStream(response.getBody())); + Element root = doc.getDocumentElement(); + String version = root.getAttribute("version"); + switch (version) { + case "1.0.0": + interceptGetCapabilities100(doc, baseUrl); + break; + case "1.1.0": + interceptGetCapabilities110And200(doc, baseUrl, CommonNamespaces.OWS_NS); + break; + case "2.0.0": + interceptGetCapabilities110And200(doc, baseUrl, CommonNamespaces.OWS_11_NS); + break; + default: + throw new IOException("WFS version is not supported"); + } + TransformerFactory factory = TransformerFactory.newInstance(); + Transformer transformer = factory.newTransformer(); + transformer.transform(new DOMSource(doc), new StreamResult(bout)); + response.setBody(bout.toByteArray()); + } catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException | TransformerException e) { + LOG.warn("Problem when intercepting WFS GetCapabilities: {}", e.getMessage()); + LOG.trace("Stack trace:", e); + } + return response; + } + + @Override + public Response interceptDescribeFeatureType(MutableHttpServletRequest request, Response response) { + // TODO: implement document filtering and rewriting as seen in the methods above + return response; + } + + @Override + public Response interceptGetFeature(MutableHttpServletRequest request, Response response) { + return response; + } + + @Override + public Response interceptLockFeature(MutableHttpServletRequest request, Response response) { + return response; + } + + @Override + public Response interceptTransaction(MutableHttpServletRequest request, Response response) { + return response; + } + +} diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmsRequestInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmsRequestInterceptor.java new file mode 100644 index 000000000..a37be576e --- /dev/null +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmsRequestInterceptor.java @@ -0,0 +1,185 @@ +package de.terrestris.shoguncore.util.interceptor.secure; + +import de.terrestris.shoguncore.dao.LayerDao; +import de.terrestris.shoguncore.model.layer.Layer; +import de.terrestris.shoguncore.model.layer.source.ImageWmsLayerDataSource; +import de.terrestris.shoguncore.service.LayerService; +import de.terrestris.shoguncore.util.application.ShogunCoreContextUtil; +import de.terrestris.shoguncore.util.interceptor.GeoserverAuthHeaderRequest; +import de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest; +import de.terrestris.shoguncore.util.interceptor.WmsRequestInterceptorInterface; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; + +import static org.apache.logging.log4j.LogManager.getLogger; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +/** + * Interceptor class for WMS requests. Adds basic auth headers based on the GS + * properties by default and checks for permission on requested layer + * + * @author Nils Bühner + * @author Johannes Weskamm + * + */ +public class WmsRequestInterceptor implements WmsRequestInterceptorInterface { + + /** + * + */ + private URI appUri = null; + + /** + * + */ + private static final Logger LOG = getLogger(WmsRequestInterceptor.class); + + /** + * + */ + @Autowired + @Qualifier("layerService") + protected LayerService> layerService; + + /** + * + */ + @Value("${geoserver.username:}") + private String gsUser; + + /** + * + */ + @Value("${geoserver.password:}") + private String gsPass; + + /** + * + */ + @Override + public MutableHttpServletRequest interceptGetMap(MutableHttpServletRequest request) { + LOG.debug("Intercepting WMS GetMap request"); + return isAllowed(request) ? new GeoserverAuthHeaderRequest( + request, gsUser, gsPass) : forbidRequest(request); + } + + /** + * + */ + @Override + public MutableHttpServletRequest interceptGetFeatureInfo(MutableHttpServletRequest request) { + LOG.debug("Intercepting WMS GetFeatureInfo request"); + return isAllowed(request) ? new GeoserverAuthHeaderRequest( + request, gsUser, gsPass) : forbidRequest(request); + } + + /** + * + */ + @Override + public MutableHttpServletRequest interceptDescribeLayer(MutableHttpServletRequest request) { + LOG.debug("Intercepting WMS DescribeLayer request"); + return isAllowed(request) ? new GeoserverAuthHeaderRequest( + request, gsUser, gsPass) : forbidRequest(request); + } + + /** + * + */ + @Override + public MutableHttpServletRequest interceptGetLegendGraphic(MutableHttpServletRequest request) { + LOG.debug("Intercepting WMS GetLegendGraphic request"); + return isAllowed(request, "layer") ? new GeoserverAuthHeaderRequest( + request, gsUser, gsPass) : forbidRequest(request); + } + + /** + * + */ + @Override + public MutableHttpServletRequest interceptGetStyles(MutableHttpServletRequest request) { + LOG.debug("Intercepting WMS GetStyles request"); + return isAllowed(request) ? new GeoserverAuthHeaderRequest( + request, gsUser, gsPass) : forbidRequest(request); + } + + /** + * + */ + @Override + public MutableHttpServletRequest interceptGetCapabilities(MutableHttpServletRequest request) { + LOG.debug("Intercepting WMS GetCapabilities request"); + // response will be intercepted + return new GeoserverAuthHeaderRequest(request, gsUser, gsPass); + } + + /** + * Check for read permission on layer by getting it through service + * + * @param request The request + * @param paramName The optional parameter name for the layer parameter + * @return if the layer is allowed to read for current user + */ + private boolean isAllowed(MutableHttpServletRequest request, + String paramName) { + String layersParam = request.getParameterIgnoreCase(paramName); + List all = layerService.findAll(); + boolean match = false; + for (Layer layer : all) { + if (layer.getSource() instanceof ImageWmsLayerDataSource) { + ImageWmsLayerDataSource source = (ImageWmsLayerDataSource) + layer.getSource(); + if (source.getLayerNames().equalsIgnoreCase(layersParam) && + source.getUrl().equalsIgnoreCase(request.getContextPath() + + "/geoserver.action")) { + match = true; + } + } + } + return match; + } + + /** + * Calls main method with default "layers" parameter + * + * @param request The request + * @return If the layer is allowed to read for current user + */ + private boolean isAllowed(MutableHttpServletRequest request) { + return isAllowed(request, "layers"); + } + + /** + *

forbidRequest.

+ * + * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. + * @return a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. + */ + protected MutableHttpServletRequest forbidRequest(MutableHttpServletRequest request) { + this.setAppUriFromRequest(request); + String redirectUri = appUri == null ? "" : appUri.toString(); + request.setRequestURI(redirectUri + "/response/forbidden.action"); + return request; + } + + /** + *

setAppUriFromRequest.

+ * + * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. + */ + protected void setAppUriFromRequest(MutableHttpServletRequest request) { + if (appUri == null) { + try { + appUri = ShogunCoreContextUtil.getApplicationURIFromRequest(request); + } catch (URISyntaxException e) { + // pass + } + } + } + +} diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmsResponseInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmsResponseInterceptor.java new file mode 100644 index 000000000..de8080a92 --- /dev/null +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmsResponseInterceptor.java @@ -0,0 +1,239 @@ +package de.terrestris.shoguncore.util.interceptor.secure; + +import de.terrestris.shoguncore.dao.LayerDao; +import de.terrestris.shoguncore.model.layer.Layer; +import de.terrestris.shoguncore.model.layer.source.ImageWmsLayerDataSource; +import de.terrestris.shoguncore.service.LayerService; +import de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest; +import de.terrestris.shoguncore.util.interceptor.WmsResponseInterceptorInterface; +import de.terrestris.shoguncore.util.model.Response; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.deegree.commons.xml.CommonNamespaces; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import javax.transaction.Transactional; +import javax.xml.namespace.NamespaceContext; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; + +import static org.apache.logging.log4j.LogManager.getLogger; + +/** + * @author Daniel Koch + * @author Andreas Schmitz + * @author Johannes Weskamm + * @author terrestris GmbH & Co. KG + */ +public class WmsResponseInterceptor implements WmsResponseInterceptorInterface { + + private static final Logger LOG = getLogger(WmsResponseInterceptor.class); + + /** + * + */ + @Autowired + @Qualifier("layerService") + protected LayerService> layerService; + + /** + * Intercepts the response in order to filter the capabilities document. + * URLs will be replaced to match the geoserver.action interface and + * layers without permission will be removed from the document + * + * @param request The request + * @param response The response to intercept + */ + @Override + @Transactional(value = Transactional.TxType.REQUIRED) + public Response interceptGetCapabilities(MutableHttpServletRequest request, Response response) { + String endpoint = request.getParameterIgnoreCase("CUSTOM_ENDPOINT"); + if (endpoint == null) { + return null; + } + + // get all layers allowed for this user in order to filter out not allowed ones + List layers = layerService.findAll(); + List layerNames = new ArrayList(); + for (Layer layer : layers) { + if (layer.getSource() instanceof ImageWmsLayerDataSource) { + ImageWmsLayerDataSource source = (ImageWmsLayerDataSource) layer.getSource(); + layerNames.add(source.getLayerNames()); + } + } + + byte[] body = response.getBody(); + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.parse(new ByteArrayInputStream(body)); + Element root = doc.getDocumentElement(); + String version = root.getAttribute("version"); + String proto = request.getHeader("x-forwarded-proto"); + String host = request.getHeader("x-forwarded-host"); + if (StringUtils.isEmpty(proto)) { + proto = request.getScheme(); + } + if (StringUtils.isEmpty(host)) { + host = request.getServerName(); + } + String baseUrl = proto + "://" + host + request.getParameter("CONTEXT_PATH") + "/geoserver.action/" + endpoint; + if (version.equals("1.3.0")) { + removeLayers(doc, "http://www.opengis.net/wms", layerNames); + updateUrls(doc, "http://www.opengis.net/wms", baseUrl); + } else { + removeLayers(doc, "", layerNames); + updateUrls(doc, "", baseUrl); + } + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + DOMSource source = new DOMSource(doc); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + StreamResult result = new StreamResult(bos); + transformer.transform(source, result); + response.setBody(bos.toByteArray()); + } catch (Throwable e) { + LOG.warn("Something went wrong when intercepting a get capabilities response: " + e.getMessage()); + LOG.trace("Stack trace", e); + return null; + } + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public Response interceptGetMap(MutableHttpServletRequest mutableRequest, Response response) { + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public Response interceptGetFeatureInfo(MutableHttpServletRequest mutableRequest, Response response) { + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public Response interceptDescribeLayer(MutableHttpServletRequest mutableRequest, Response response) { + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public Response interceptGetLegendGraphic(MutableHttpServletRequest mutableRequest, Response response) { + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public Response interceptGetStyles(MutableHttpServletRequest mutableRequest, Response response) { + return response; + } + + /** + * Removes layers from the capabilities document + * + * @param doc The document + * @param namespace The namespace + * @param layerNames The layerNames to keep + * @throws XPathExpressionException + */ + @SuppressFBWarnings("UC_USELESS_OBJECT") + private void removeLayers(Document doc, String namespace, List layerNames) throws XPathExpressionException { + List unqualifiedLayerNames = new ArrayList(); + for (String name : layerNames) { + if (name.contains(":")) { + unqualifiedLayerNames.add(name.split(":")[1]); + } else { + unqualifiedLayerNames.add(name); + } + } + XPathFactory factory = XPathFactory.newInstance(); + XPath xpath = factory.newXPath(); + NamespaceContext nscontext = CommonNamespaces.getNamespaceContext(); + xpath.setNamespaceContext(nscontext); + String prefix = namespace.equals("") ? "" : "wms:"; + XPathExpression expr = xpath.compile("//" + prefix + "Layer/" + prefix + "Name"); + NodeList nodeList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + List toRemove = new ArrayList<>(); + for (int i = 0; i < nodeList.getLength(); ++i) { + Element name = (Element) nodeList.item(i); + String str = name.getTextContent(); + if (!unqualifiedLayerNames.contains(str)) { + toRemove.add((Element) name.getParentNode()); + } else { + String nodeLayerName = name.getTextContent(); + for (int j = 0; j < layerNames.size(); j++) { + String qualifiedLayerName = layerNames.get(j); + if (qualifiedLayerName.contains(":")) { + String layerNamespace = qualifiedLayerName.split(":")[0]; + String unqualifiedLayerName = qualifiedLayerName.split(":")[1]; + if (nodeLayerName.equals(unqualifiedLayerName)) { + name.setTextContent(layerNamespace + ":" + name.getTextContent()); + } + } else { + if (nodeLayerName.equals(qualifiedLayerName)) { + name.setTextContent(name.getTextContent()); + } + } + } + } + } + toRemove.forEach(element -> element.getParentNode().removeChild(element)); + } + + /** + * Rewrites URLs in the capabilites document + * + * @param doc The document + * @param namespace The namespace + * @param baseUrl The baseUrl + * @throws XPathExpressionException + */ + private void updateUrls(Document doc, String namespace, String baseUrl) throws XPathExpressionException { + XPathFactory factory = XPathFactory.newInstance(); + XPath xpath = factory.newXPath(); + NamespaceContext nscontext = CommonNamespaces.getNamespaceContext(); + xpath.setNamespaceContext(nscontext); + String prefix = namespace.equals("") ? "" : "wms:"; + XPathExpression expr = xpath.compile("//" + prefix + "OnlineResource"); + NodeList nodeList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + for (int i = 0; i < nodeList.getLength(); ++i) { + Element link = (Element) nodeList.item(i); + String url = link.getAttributeNS("http://www.w3.org/1999/xlink", "xlink:href"); + int index = url.indexOf("?"); + if (index > -1) { + url = url.substring(index); + url = baseUrl + url; + link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", url); + } else { + link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", baseUrl); + } + } + } +} diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsRequestInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsRequestInterceptor.java new file mode 100644 index 000000000..862b58d89 --- /dev/null +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsRequestInterceptor.java @@ -0,0 +1,156 @@ +package de.terrestris.shoguncore.util.interceptor.secure; + +import de.terrestris.shoguncore.dao.LayerDao; +import de.terrestris.shoguncore.model.layer.Layer; +import de.terrestris.shoguncore.model.layer.source.WmtsLayerDataSource; +import de.terrestris.shoguncore.service.LayerService; +import de.terrestris.shoguncore.util.application.ShogunCoreContextUtil; +import de.terrestris.shoguncore.util.interceptor.GeoserverAuthHeaderRequest; +import de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest; +import de.terrestris.shoguncore.util.interceptor.WmtsRequestInterceptorInterface; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; + +import static org.apache.logging.log4j.LogManager.getLogger; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +/** + * Interceptor class for WMS requests. Adds basic auth headers based on the GS + * properties by default and checks for permission on requested layer + * + * @author Nils Bühner + * @author Johannes Weskamm + * + */ +public class WmtsRequestInterceptor implements WmtsRequestInterceptorInterface { + + /** + * + */ + private URI appUri = null; + + /** + * + */ + private static final Logger LOG = getLogger(WmsRequestInterceptor.class); + + /** + * + */ + @Autowired + @Qualifier("layerService") + protected LayerService> layerService; + + /** + * + */ + @Value("${geoserver.username:}") + private String gsUser; + + /** + * + */ + @Value("${geoserver.password:}") + private String gsPass; + + /** + * + */ + @Override + public MutableHttpServletRequest interceptGetTile(MutableHttpServletRequest request) { + LOG.debug("Intercepting WMTS GetTile request"); + return isAllowed(request) ? new GeoserverAuthHeaderRequest( + request, gsUser, gsPass) : forbidRequest(request); + } + + /** + * + */ + @Override + public MutableHttpServletRequest interceptGetFeatureInfo(MutableHttpServletRequest request) { + LOG.debug("Intercepting WMTS GetFeatureInfo request"); + return isAllowed(request) ? new GeoserverAuthHeaderRequest( + request, gsUser, gsPass) : forbidRequest(request); + } + + /** + * + */ + @Override + public MutableHttpServletRequest interceptGetCapabilities(MutableHttpServletRequest request) { + LOG.debug("Intercepting WMTS GetCapabilities request"); + // response will be intercepted + String url = request.getRequestURI().split("/ows") [0] + + "/gwc/service/wmts" + request.getQueryString(); + request.setRequestURI(url); + return new GeoserverAuthHeaderRequest(request, gsUser, gsPass); + } + + /** + * Check for read permission on layer by getting it through service + * + * @param request The request + * @param paramName The optional parameter name for the layer parameter + * @return if the layer is allowed to read for current user + */ + private boolean isAllowed(MutableHttpServletRequest request, + String paramName) { + String layersParam = request.getParameterIgnoreCase(paramName); + List all = layerService.findAll(); + boolean match = false; + for (Layer layer : all) { + if (layer.getSource() instanceof WmtsLayerDataSource) { + WmtsLayerDataSource source = (WmtsLayerDataSource) + layer.getSource(); + if (source.getWmtsLayer().equalsIgnoreCase(layersParam)) { + match = true; + } + } + } + return match; + } + + /** + * Calls main method with default "layers" parameter + * + * @param request The request + * @return If the layer is allowed to read for current user + */ + private boolean isAllowed(MutableHttpServletRequest request) { + return isAllowed(request, "layer"); + } + + /** + *

forbidRequest.

+ * + * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. + * @return a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. + */ + protected MutableHttpServletRequest forbidRequest(MutableHttpServletRequest request) { + this.setAppUriFromRequest(request); + String redirectUri = appUri == null ? "" : appUri.toString(); + request.setRequestURI(redirectUri + "/response/forbidden.action"); + return request; + } + + /** + *

setAppUriFromRequest.

+ * + * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. + */ + protected void setAppUriFromRequest(MutableHttpServletRequest request) { + if (appUri == null) { + try { + appUri = ShogunCoreContextUtil.getApplicationURIFromRequest(request); + } catch (URISyntaxException e) { + // pass + } + } + } + +} diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsResponseInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsResponseInterceptor.java new file mode 100644 index 000000000..75e9d358a --- /dev/null +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsResponseInterceptor.java @@ -0,0 +1,216 @@ +package de.terrestris.shoguncore.util.interceptor.secure; + +import de.terrestris.shoguncore.dao.LayerDao; +import de.terrestris.shoguncore.model.layer.Layer; +import de.terrestris.shoguncore.model.layer.source.WmtsLayerDataSource; +import de.terrestris.shoguncore.service.LayerService; +import de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest; +import de.terrestris.shoguncore.util.interceptor.WmtsResponseInterceptorInterface; +import de.terrestris.shoguncore.util.model.Response; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.Logger; +import org.deegree.commons.xml.CommonNamespaces; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +import javax.transaction.Transactional; +import javax.xml.namespace.NamespaceContext; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.xpath.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; + +import static org.apache.logging.log4j.LogManager.getLogger; + +/** + * @author Daniel Koch + * @author Andreas Schmitz + * @author Johannes Weskamm + * @author terrestris GmbH & Co. KG + */ +public class WmtsResponseInterceptor implements WmtsResponseInterceptorInterface { + + private static final Logger LOG = getLogger(WmsResponseInterceptor.class); + + /** + * + */ + @Autowired + @Qualifier("layerService") + protected LayerService> layerService; + + /** + * Intercepts the response in order to filter the capabilities document. + * URLs will be replaced to match the geoserver.action interface and + * layers without permission will be removed from the document + * + * @param request The request + * @param response The response to intercept + */ + @Override + @Transactional(value = Transactional.TxType.REQUIRED) + public Response interceptGetCapabilities(MutableHttpServletRequest request, Response response) { + String endpoint = request.getParameterIgnoreCase("CUSTOM_ENDPOINT"); + System.out.println("endpoint " + endpoint); + if (endpoint == null) { + return null; + } + + // get all layers allowed for this user in order to filter out not allowed ones + List layers = layerService.findAll(); + List layerNames = new ArrayList(); + for (Layer layer : layers) { + if (layer.getSource() instanceof WmtsLayerDataSource) { + WmtsLayerDataSource source = (WmtsLayerDataSource) layer.getSource(); + layerNames.add(source.getWmtsLayer()); + } + } + + byte[] body = response.getBody(); + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document doc = builder.parse(new ByteArrayInputStream(body)); + Element root = doc.getDocumentElement(); + String version = root.getAttribute("version"); + String proto = request.getHeader("x-forwarded-proto"); + String host = request.getHeader("x-forwarded-host"); + if (StringUtils.isEmpty(proto)) { + proto = request.getScheme(); + } + if (StringUtils.isEmpty(host)) { + host = request.getServerName(); + } + String baseUrl = proto + "://" + host + request.getParameter("CONTEXT_PATH") + "/geoserver.action/" + endpoint; + if (version.equals("1.3.0")) { + removeLayers(doc, "http://schemas.opengis.net/wmts", layerNames); + updateUrls(doc, "http://schemas.opengis.net/wmts", baseUrl); + } else { + removeLayers(doc, "", layerNames); + updateUrls(doc, "", baseUrl); + } + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + DOMSource source = new DOMSource(doc); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + StreamResult result = new StreamResult(bos); + transformer.transform(source, result); + response.setBody(bos.toByteArray()); + } catch (Throwable e) { + LOG.warn("Something went wrong when intercepting a get capabilities response: " + e.getMessage()); + LOG.trace("Stack trace", e); + return null; + } + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public Response interceptGetTile(MutableHttpServletRequest mutableRequest, Response response) { + return response; + } + + /** + * {@inheritDoc} + */ + @Override + public Response interceptGetFeatureInfo(MutableHttpServletRequest mutableRequest, Response response) { + return response; + } + + /** + * Removes layers from the capabilities document + * + * @param doc The document + * @param namespace The namespace + * @param layerNames The layerNames to keep + * @throws XPathExpressionException + */ + @SuppressFBWarnings("UC_USELESS_OBJECT") + private void removeLayers(Document doc, String namespace, List layerNames) throws XPathExpressionException { + List unqualifiedLayerNames = new ArrayList(); + for (String name : layerNames) { + if (name.contains(":")) { + unqualifiedLayerNames.add(name.split(":")[1]); + } else { + unqualifiedLayerNames.add(name); + } + } + XPathFactory factory = XPathFactory.newInstance(); + XPath xpath = factory.newXPath(); + NamespaceContext nscontext = CommonNamespaces.getNamespaceContext(); + xpath.setNamespaceContext(nscontext); + String prefix = namespace.equals("") ? "" : "wmts:"; + XPathExpression expr = xpath.compile("//" + prefix + "Layer/" + prefix + "Name"); + NodeList nodeList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + List toRemove = new ArrayList<>(); + for (int i = 0; i < nodeList.getLength(); ++i) { + Element name = (Element) nodeList.item(i); + String str = name.getTextContent(); + if (!unqualifiedLayerNames.contains(str)) { + toRemove.add((Element) name.getParentNode()); + } else { + String nodeLayerName = name.getTextContent(); + for (int j = 0; j < layerNames.size(); j++) { + String qualifiedLayerName = layerNames.get(j); + if (qualifiedLayerName.contains(":")) { + String layerNamespace = qualifiedLayerName.split(":")[0]; + String unqualifiedLayerName = qualifiedLayerName.split(":")[1]; + if (nodeLayerName.equals(unqualifiedLayerName)) { + name.setTextContent(layerNamespace + ":" + name.getTextContent()); + } + } else { + if (nodeLayerName.equals(qualifiedLayerName)) { + name.setTextContent(name.getTextContent()); + } + } + } + } + } + toRemove.forEach(element -> element.getParentNode().removeChild(element)); + } + + /** + * Rewrites URLs in the capabilites document + * + * @param doc The document + * @param namespace The namespace + * @param baseUrl The baseUrl + * @throws XPathExpressionException + */ + private void updateUrls(Document doc, String namespace, String baseUrl) throws XPathExpressionException { + XPathFactory factory = XPathFactory.newInstance(); + XPath xpath = factory.newXPath(); + NamespaceContext nscontext = CommonNamespaces.getNamespaceContext(); + xpath.setNamespaceContext(nscontext); + String prefix = namespace.equals("") ? "" : "wmts:"; + XPathExpression expr = xpath.compile("//" + prefix + "OnlineResource"); + NodeList nodeList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + for (int i = 0; i < nodeList.getLength(); ++i) { + Element link = (Element) nodeList.item(i); + String url = link.getAttributeNS("http://www.w3.org/1999/xlink", "xlink:href"); + int index = url.indexOf("?"); + if (index > -1) { + url = url.substring(index); + url = baseUrl + url; + link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", url); + } else { + link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", baseUrl); + } + } + } +} diff --git a/src/shogun-core-webapp-archetype/src/main/resources/archetype-resources/src/main/resources/META-INF/spring/__artifactId__-context-initialize-beans.xml b/src/shogun-core-webapp-archetype/src/main/resources/archetype-resources/src/main/resources/META-INF/spring/__artifactId__-context-initialize-beans.xml index 771e667cf..5b65f8e33 100644 --- a/src/shogun-core-webapp-archetype/src/main/resources/archetype-resources/src/main/resources/META-INF/spring/__artifactId__-context-initialize-beans.xml +++ b/src/shogun-core-webapp-archetype/src/main/resources/archetype-resources/src/main/resources/META-INF/spring/__artifactId__-context-initialize-beans.xml @@ -75,10 +75,14 @@ + + + + @@ -557,38 +561,62 @@ - + - + + + + + + + + + + + + + - + - + - + - + + + + + + + + + + + + + diff --git a/src/shogun-core-webapp-archetype/src/main/resources/archetype-resources/src/main/resources/META-INF/spring/__artifactId__-context.xml b/src/shogun-core-webapp-archetype/src/main/resources/archetype-resources/src/main/resources/META-INF/spring/__artifactId__-context.xml index 45cdcff5e..c1f89385f 100644 --- a/src/shogun-core-webapp-archetype/src/main/resources/archetype-resources/src/main/resources/META-INF/spring/__artifactId__-context.xml +++ b/src/shogun-core-webapp-archetype/src/main/resources/archetype-resources/src/main/resources/META-INF/spring/__artifactId__-context.xml @@ -79,9 +79,13 @@ - + + + + + + - From 50801f2238e32d95d1ba7e44e06a644b06444b5e Mon Sep 17 00:00:00 2001 From: Johannes Weskamm Date: Wed, 15 Jul 2020 17:04:39 +0200 Subject: [PATCH 2/4] Finalising interceptor work --- .../service/GeoServerInterceptorService.java | 85 +++++++++++-- .../util/interceptor/OgcMessage.java | 1 + .../interceptor/OgcMessageDistributor.java | 32 ++++- .../WmtsRequestInterceptorInterface.java | 7 +- .../secure/WfsRequestInterceptor.java | 14 ++ .../secure/WfsResponseInterceptor.java | 120 +++++++++++++++++- .../secure/WmtsRequestInterceptor.java | 116 +++++++++++++---- .../secure/WmtsResponseInterceptor.java | 50 +++++--- .../web/GeoServerInterceptorController.java | 38 +++++- .../GeoServerInterceptorServiceTest.java | 13 +- .../GeoServerInterceptorControllerTest.java | 6 +- 11 files changed, 405 insertions(+), 77 deletions(-) diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java index 0ab3d21d1..7e050f149 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java @@ -1,7 +1,8 @@ package de.terrestris.shoguncore.service; -import de.terrestris.shoguncore.dao.LayerDataSourceDao; +import de.terrestris.shoguncore.dao.LayerDao; import de.terrestris.shoguncore.model.interceptor.InterceptorRule; +import de.terrestris.shoguncore.model.layer.Layer; import de.terrestris.shoguncore.model.layer.source.WmtsLayerDataSource; import de.terrestris.shoguncore.util.enumeration.HttpEnum; import de.terrestris.shoguncore.util.enumeration.OgcEnum; @@ -23,6 +24,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; @@ -79,9 +81,14 @@ public class GeoServerInterceptorService { */ @Autowired InterceptorRuleService interceptorRuleService; + + /** + * + */ @Autowired - @Qualifier("layerDataSourceDao") - private LayerDataSourceDao wmtsLayerDataSourceDao; + @Qualifier("layerService") + protected LayerService> layerService; + /** * The autowired properties file containing the (application driven) * GeoServer namespace - GeoServer BaseURI mapping, e.g.: @@ -334,7 +341,24 @@ public Response interceptWmtsRequest(HttpServletRequest request, String serviceI throw new InterceptorException("No WMTS request path found!"); } String path = matcher.group(1); - WmtsLayerDataSource dataSource = wmtsLayerDataSourceDao.findById(id); + + // get all layers allowed for this user in order to filter out not allowed ones + List layers = layerService.findAll(); + WmtsLayerDataSource dataSource = null; + for (Layer layer : layers) { + if (layer.getSource() instanceof WmtsLayerDataSource) { + WmtsLayerDataSource source = (WmtsLayerDataSource) layer.getSource(); + if (source.getId().equals(id)) { + dataSource = source; + } + } + } + + if (dataSource == null) { + Response response = new Response(HttpStatus.FORBIDDEN, null, new byte[0]); + return response; + } + String baseUrl = dataSource.getUrl(); Response response = HttpUtil.get(baseUrl + "/" + path); @@ -345,6 +369,7 @@ public Response interceptWmtsRequest(HttpServletRequest request, String serviceI } /** + * Calls the main method with empty optionals hashmap * @param request * @return * @throws InterceptorException @@ -353,34 +378,51 @@ public Response interceptWmtsRequest(HttpServletRequest request, String serviceI * @throws IOException */ public Response interceptGeoServerRequest(HttpServletRequest request) - throws InterceptorException, URISyntaxException, - HttpException, IOException { - return interceptGeoServerRequest(request, Optional.empty()); + throws InterceptorException, URISyntaxException, HttpException, IOException { + return interceptGeoServerRequest(request, new HashMap>()); } /** + * * @param request - * @param endpoint + * @param optionals * @return * @throws InterceptorException * @throws URISyntaxException * @throws HttpException * @throws IOException */ - public Response interceptGeoServerRequest(HttpServletRequest request, Optional endpoint) + public Response interceptGeoServerRequest( + HttpServletRequest request, + HashMap> optionals) throws InterceptorException, URISyntaxException, HttpException, IOException { // wrap the request, we want to manipulate it MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(request); - if (endpoint.isPresent()) { - mutableRequest.addParameter("CUSTOM_ENDPOINT", endpoint.get()); + if (optionals.containsKey("endpoint") && optionals.get("endpoint").isPresent()) { + mutableRequest.addParameter("CUSTOM_ENDPOINT", optionals.get("endpoint").get()); mutableRequest.addParameter("CONTEXT_PATH", request.getContextPath()); } + // check if we have a RESTful WMTS request + boolean isRestfulWmts = false; + boolean isRestfulWmtsGetFeatureinfo = false; + if (optionals.containsKey("layername") && optionals.get("layername").isPresent() && + optionals.containsKey("style") && optionals.get("style").isPresent() && + optionals.containsKey("tilematrixset") && optionals.get("tilematrixset").isPresent() && + optionals.containsKey("tilematrix") && optionals.get("tilematrix").isPresent() && + optionals.containsKey("tilerow") && optionals.get("tilerow").isPresent() && + optionals.containsKey("tilecol") && optionals.get("tilecol").isPresent()) { + isRestfulWmts = true; + if (optionals.containsKey("j") && optionals.get("j").isPresent() && + optionals.containsKey("i") && optionals.get("i").isPresent()) { + isRestfulWmtsGetFeatureinfo = true; + } + } // get the OGC message information (service, request, endPoint) - OgcMessage message = getOgcMessage(mutableRequest); + OgcMessage message = getOgcMessage(mutableRequest, isRestfulWmts, isRestfulWmtsGetFeatureinfo); // check whether WMS reflector endpoint should be called final boolean useWmsReflector = shouldReflectEndpointBeCalled(mutableRequest, message); @@ -393,7 +435,7 @@ public Response interceptGeoServerRequest(HttpServletRequest request, Optional>() + ); + } + /** * @param request * @param message + * @param optionals * @return * @throws InterceptorException */ public MutableHttpServletRequest distributeToRequestInterceptor( - MutableHttpServletRequest request, OgcMessage message) + MutableHttpServletRequest request, + OgcMessage message, + HashMap> optionals) throws InterceptorException { if (message.isRequestAllowed()) { @@ -196,9 +218,13 @@ public MutableHttpServletRequest distributeToRequestInterceptor( if (message.isWmtsGetCapabilities()) { request = this.wmtsRequestInterceptor.interceptGetCapabilities(request); } else if (message.isWmtsGetTile()) { - request = this.wmtsRequestInterceptor.interceptGetTile(request); + request = this.wmtsRequestInterceptor.interceptGetTile( + request, + optionals); } else if (message.isWmtsGetFeatureInfo()) { - request = this.wmtsRequestInterceptor.interceptGetFeatureInfo(request); + request = this.wmtsRequestInterceptor.interceptGetFeatureInfo( + request, + optionals); } else { throw new InterceptorException(operationErrMsg); } diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/WmtsRequestInterceptorInterface.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/WmtsRequestInterceptorInterface.java index 708d20ee3..9d3498b46 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/WmtsRequestInterceptorInterface.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/WmtsRequestInterceptorInterface.java @@ -1,14 +1,17 @@ package de.terrestris.shoguncore.util.interceptor; +import java.util.HashMap; +import java.util.Optional; + import org.springframework.stereotype.Component; @Component public interface WmtsRequestInterceptorInterface { - MutableHttpServletRequest interceptGetTile(MutableHttpServletRequest request); + MutableHttpServletRequest interceptGetTile(MutableHttpServletRequest request, HashMap> optionals); MutableHttpServletRequest interceptGetCapabilities(MutableHttpServletRequest request); - MutableHttpServletRequest interceptGetFeatureInfo(MutableHttpServletRequest request); + MutableHttpServletRequest interceptGetFeatureInfo(MutableHttpServletRequest request, HashMap> optionals); } diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsRequestInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsRequestInterceptor.java index 402a24720..9c77a48e4 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsRequestInterceptor.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsRequestInterceptor.java @@ -5,6 +5,7 @@ import de.terrestris.shoguncore.model.User; import de.terrestris.shoguncore.model.UserGroup; import de.terrestris.shoguncore.model.layer.Layer; +import de.terrestris.shoguncore.model.layer.source.ImageWmsLayerDataSource; import de.terrestris.shoguncore.model.layer.source.WfsLayerDataSource; import de.terrestris.shoguncore.model.security.Permission; import de.terrestris.shoguncore.model.security.PermissionCollection; @@ -152,6 +153,19 @@ private boolean isAllowed( match = true; } } + } else if (layer.getSource() instanceof ImageWmsLayerDataSource) { + ImageWmsLayerDataSource source = (ImageWmsLayerDataSource) + layer.getSource(); + if (source.getLayerNames().equalsIgnoreCase(typeNameParam) && + source.getUrl().equalsIgnoreCase(request.getContextPath() + + "/geoserver.action")) { + if (method.equals("UPDATE")) { + match = checkForPermission(layer, Permission.UPDATE); + } else if (method.equals("READ")) { + // implicitly checked by findAll method + match = true; + } + } } } return match; diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsResponseInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsResponseInterceptor.java index 93aa79148..4be7afe7e 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsResponseInterceptor.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsResponseInterceptor.java @@ -1,5 +1,10 @@ package de.terrestris.shoguncore.util.interceptor.secure; +import de.terrestris.shoguncore.dao.LayerDao; +import de.terrestris.shoguncore.model.layer.Layer; +import de.terrestris.shoguncore.model.layer.source.ImageWmsLayerDataSource; +import de.terrestris.shoguncore.model.layer.source.WfsLayerDataSource; +import de.terrestris.shoguncore.service.LayerService; import de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest; import de.terrestris.shoguncore.util.interceptor.WfsResponseInterceptorInterface; import de.terrestris.shoguncore.util.model.Response; @@ -8,6 +13,8 @@ import org.apache.logging.log4j.Logger; import org.deegree.commons.xml.CommonNamespaces; import org.deegree.commons.xml.NamespaceBindings; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -27,6 +34,8 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import static javax.xml.xpath.XPathConstants.NODESET; import static org.apache.logging.log4j.LogManager.getLogger; @@ -35,6 +44,13 @@ public class WfsResponseInterceptor implements WfsResponseInterceptorInterface { private static final Logger LOG = getLogger(WfsResponseInterceptor.class); + /** + * + */ + @Autowired + @Qualifier("layerService") + protected LayerService> layerService; + private void interceptGetCapabilities100(Document doc, String baseUrl) throws XPathExpressionException { XPath xpath = XPathFactory.newInstance().newXPath(); // xpath seems not to work with the default bindings @@ -70,6 +86,7 @@ private void interceptGetCapabilities110And200(Document doc, String baseUrl, Str @Override public Response interceptGetCapabilities(MutableHttpServletRequest request, Response response) { + LOG.debug("Intercepting WFS GetCapabilities response"); String proto = request.getHeader("x-forwarded-proto"); String requestHost = request.getHeader("x-forwarded-host"); // fallbacks in case SHOGun is not behind a reverse proxy @@ -101,6 +118,7 @@ public Response interceptGetCapabilities(MutableHttpServletRequest request, Resp default: throw new IOException("WFS version is not supported"); } + removeLayers(doc); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.transform(new DOMSource(doc), new StreamResult(bout)); @@ -108,28 +126,128 @@ public Response interceptGetCapabilities(MutableHttpServletRequest request, Resp } catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException | TransformerException e) { LOG.warn("Problem when intercepting WFS GetCapabilities: {}", e.getMessage()); LOG.trace("Stack trace:", e); + return null; } return response; } + private void removeLayers(Document doc) throws XPathExpressionException { + // get all layers allowed for this user in order to filter out not allowed ones + List layers = layerService.findAll(); + List layerNames = new ArrayList(); + for (Layer layer : layers) { + if (layer.getSource() instanceof ImageWmsLayerDataSource) { + ImageWmsLayerDataSource source = (ImageWmsLayerDataSource) layer.getSource(); + layerNames.add(source.getLayerNames()); + } else if (layer.getSource() instanceof WfsLayerDataSource) { + WfsLayerDataSource source = (WfsLayerDataSource) layer.getSource(); + layerNames.add(source.getTypeName()); + layerNames.add(source.getTypeNames()); + } + } + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(CommonNamespaces.getNamespaceContext(). + addNamespace("wfs", "http://www.opengis.net/wfs")); + NodeList list = (NodeList) xpath.compile("//wfs:FeatureType/wfs:Name").evaluate(doc.getDocumentElement(), NODESET); + List toRemove = new ArrayList<>(); + for (int i = 0; i < list.getLength(); ++i) { + Element name = (Element) list.item(i); + String str = name.getTextContent(); + if (!layerNames.contains(str)) { + toRemove.add((Element) name.getParentNode()); + } + } + toRemove.forEach(element -> element.getParentNode().removeChild(element)); + } + @Override public Response interceptDescribeFeatureType(MutableHttpServletRequest request, Response response) { - // TODO: implement document filtering and rewriting as seen in the methods above + LOG.debug("Intercepting WFS DescribeFeatureType response"); + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try { + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + builderFactory.setNamespaceAware(true); + DocumentBuilder builder = builderFactory.newDocumentBuilder(); + Document doc = builder.parse(new ByteArrayInputStream(response.getBody())); + removeFeatureTypes(doc); + TransformerFactory factory = TransformerFactory.newInstance(); + Transformer transformer = factory.newTransformer(); + transformer.transform(new DOMSource(doc), new StreamResult(bout)); + response.setBody(bout.toByteArray()); + } catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException | TransformerException e) { + LOG.warn("Problem when intercepting WFS GetCapabilities: {}", e.getMessage()); + LOG.trace("Stack trace:", e); + return null; + } return response; } + private void removeFeatureTypes(Document doc) throws XPathExpressionException { + // get all layers allowed for this user in order to filter out not allowed ones + List layers = layerService.findAll(); + List layerNames = new ArrayList(); + for (Layer layer : layers) { + if (layer.getSource() instanceof ImageWmsLayerDataSource) { + ImageWmsLayerDataSource source = (ImageWmsLayerDataSource) layer.getSource(); + if (source.getLayerNames().contains(":")) { + layerNames.add(source.getLayerNames().split(":")[1]); + } else { + layerNames.add(source.getLayerNames()); + } + } else if (layer.getSource() instanceof WfsLayerDataSource) { + WfsLayerDataSource source = (WfsLayerDataSource) layer.getSource(); + if (source.getTypeName().contains(":")) { + layerNames.add(source.getTypeName().split(":")[1]); + } else { + layerNames.add(source.getTypeName()); + } + if (source.getTypeNames().contains(":")) { + layerNames.add(source.getTypeNames().split(":")[1]); + } else { + layerNames.add(source.getTypeNames()); + } + } + } + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(CommonNamespaces.getNamespaceContext(). + addNamespace("wfs", "http://www.opengis.net/wfs"). + addNamespace("xsd", "http://www.w3.org/2001/XMLSchema")); + NodeList list = (NodeList) xpath.compile("//xsd:complexType").evaluate(doc.getDocumentElement(), NODESET); + List toRemove = new ArrayList<>(); + for (int i = 0; i < list.getLength(); ++i) { + Element name = (Element) list.item(i); + String str = name.getAttribute("name"); + str = str.substring(0, str.lastIndexOf("Type")); + if (!layerNames.contains(str)) { + toRemove.add((Element) name); + } + } + list = (NodeList) xpath.compile("/xsd:schema/xsd:element").evaluate(doc.getDocumentElement(), NODESET); + for (int i = 0; i < list.getLength(); ++i) { + Element name = (Element) list.item(i); + String str = name.getAttribute("name"); + if (!layerNames.contains(str)) { + toRemove.add((Element) name); + } + } + toRemove.forEach(element -> element.getParentNode().removeChild(element)); + } + @Override public Response interceptGetFeature(MutableHttpServletRequest request, Response response) { + LOG.debug("Intercepting WFS GetFeature response"); return response; } @Override public Response interceptLockFeature(MutableHttpServletRequest request, Response response) { + LOG.debug("Intercepting WFS LockFeature response"); return response; } @Override public Response interceptTransaction(MutableHttpServletRequest request, Response response) { + LOG.debug("Intercepting WFS Transaction response"); return response; } diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsRequestInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsRequestInterceptor.java index 862b58d89..eb25c54a7 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsRequestInterceptor.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsRequestInterceptor.java @@ -8,6 +8,8 @@ import de.terrestris.shoguncore.util.interceptor.GeoserverAuthHeaderRequest; import de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest; import de.terrestris.shoguncore.util.interceptor.WmtsRequestInterceptorInterface; + +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -17,7 +19,9 @@ import java.net.URI; import java.net.URISyntaxException; +import java.util.HashMap; import java.util.List; +import java.util.Optional; /** * Interceptor class for WMS requests. Adds basic auth headers based on the GS @@ -29,7 +33,7 @@ */ public class WmtsRequestInterceptor implements WmtsRequestInterceptorInterface { - /** +/** * */ private URI appUri = null; @@ -37,7 +41,7 @@ public class WmtsRequestInterceptor implements WmtsRequestInterceptorInterface { /** * */ - private static final Logger LOG = getLogger(WmsRequestInterceptor.class); + private static final Logger LOG = getLogger(WmtsRequestInterceptor.class); /** * @@ -62,20 +66,79 @@ public class WmtsRequestInterceptor implements WmtsRequestInterceptorInterface { * */ @Override - public MutableHttpServletRequest interceptGetTile(MutableHttpServletRequest request) { + public MutableHttpServletRequest interceptGetTile(MutableHttpServletRequest request, HashMap> optionals) { LOG.debug("Intercepting WMTS GetTile request"); - return isAllowed(request) ? new GeoserverAuthHeaderRequest( - request, gsUser, gsPass) : forbidRequest(request); + + boolean restfulRequest = optionals.containsKey("layername") && + optionals.get("layername").isPresent(); + + if (isAllowed( + request, + restfulRequest ? "" : "layer", + restfulRequest ? optionals.get("layername").get() : "" + )) { + String url; + if (restfulRequest) { + // RESTful request + url = request.getRequestURI().split("/ows")[0] + + "/gwc/service/wmts/rest/" + optionals.get("layername").get() + + "/" + optionals.get("style").get() + + "/" + optionals.get("tilematrixset").get() + + "/" + optionals.get("tilematrix").get() + + "/" + optionals.get("tilerow").get() + + "/" + optionals.get("tilecol").get() + + "?" + request.getQueryString(); + } else { + // KVP request + url = request.getRequestURI().split("/ows")[0] + + "/gwc/service/wmts?" + request.getQueryString(); + } + request.setRequestURI(url); + return new GeoserverAuthHeaderRequest(request, gsUser, gsPass); + } else { + return forbidRequest(request); + } + } /** * */ @Override - public MutableHttpServletRequest interceptGetFeatureInfo(MutableHttpServletRequest request) { + public MutableHttpServletRequest interceptGetFeatureInfo(MutableHttpServletRequest request, HashMap> optionals) { LOG.debug("Intercepting WMTS GetFeatureInfo request"); - return isAllowed(request) ? new GeoserverAuthHeaderRequest( - request, gsUser, gsPass) : forbidRequest(request); + + boolean restfulRequest = optionals.containsKey("layername") && + optionals.get("layername").isPresent(); + + if (isAllowed( + request, + restfulRequest ? "" : "layer", + restfulRequest ? optionals.get("layername").get() : "" + )) { + String url; + if (restfulRequest) { + // RESTful request + url = request.getRequestURI().split("/ows")[0] + + "/gwc/service/wmts/rest/" + optionals.get("layername").get() + + "/" + optionals.get("style").get() + + "/" + optionals.get("tilematrixset").get() + + "/" + optionals.get("tilematrix").get() + + "/" + optionals.get("tilerow").get() + + "/" + optionals.get("tilecol").get() + + "/" + optionals.get("j").get() + + "/" + optionals.get("i").get() + + "?" + request.getQueryString(); + } else { + // KVP request + url = request.getRequestURI().split("/ows")[0] + + "/gwc/service/wmts?" + request.getQueryString(); + } + request.setRequestURI(url); + return new GeoserverAuthHeaderRequest(request, gsUser, gsPass); + } else { + return forbidRequest(request); + } } /** @@ -85,8 +148,8 @@ public MutableHttpServletRequest interceptGetFeatureInfo(MutableHttpServletReque public MutableHttpServletRequest interceptGetCapabilities(MutableHttpServletRequest request) { LOG.debug("Intercepting WMTS GetCapabilities request"); // response will be intercepted - String url = request.getRequestURI().split("/ows") [0] + - "/gwc/service/wmts" + request.getQueryString(); + String url = request.getRequestURI().split("/ows")[0] + + "/gwc/service/wmts?REQUEST=GetCapabilities"; request.setRequestURI(url); return new GeoserverAuthHeaderRequest(request, gsUser, gsPass); } @@ -99,15 +162,26 @@ public MutableHttpServletRequest interceptGetCapabilities(MutableHttpServletRequ * @return if the layer is allowed to read for current user */ private boolean isAllowed(MutableHttpServletRequest request, - String paramName) { - String layersParam = request.getParameterIgnoreCase(paramName); + String paramName, String layerName) { + String layer; + if (!StringUtils.isEmpty(paramName)) { + layer = request.getParameterIgnoreCase(paramName); + } else { + layer = layerName; + } List all = layerService.findAll(); boolean match = false; - for (Layer layer : all) { - if (layer.getSource() instanceof WmtsLayerDataSource) { + for (Layer l : all) { + if (l.getSource() instanceof WmtsLayerDataSource) { WmtsLayerDataSource source = (WmtsLayerDataSource) - layer.getSource(); - if (source.getWmtsLayer().equalsIgnoreCase(layersParam)) { + l.getSource(); + String sourceLayer = source.getWmtsLayer(); + if (sourceLayer.contains(":") && !layer.contains(":")) { + sourceLayer = sourceLayer.split(":")[1]; + } else if (layer.contains(":") && !sourceLayer.contains(":")) { + layer = layer.split(":")[1]; + } + if (sourceLayer.equalsIgnoreCase(layer)) { match = true; } } @@ -115,16 +189,6 @@ private boolean isAllowed(MutableHttpServletRequest request, return match; } - /** - * Calls main method with default "layers" parameter - * - * @param request The request - * @return If the layer is allowed to read for current user - */ - private boolean isAllowed(MutableHttpServletRequest request) { - return isAllowed(request, "layer"); - } - /** *

forbidRequest.

* diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsResponseInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsResponseInterceptor.java index 75e9d358a..28d72b0fc 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsResponseInterceptor.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsResponseInterceptor.java @@ -63,7 +63,6 @@ public class WmtsResponseInterceptor implements WmtsResponseInterceptorInterface @Transactional(value = Transactional.TxType.REQUIRED) public Response interceptGetCapabilities(MutableHttpServletRequest request, Response response) { String endpoint = request.getParameterIgnoreCase("CUSTOM_ENDPOINT"); - System.out.println("endpoint " + endpoint); if (endpoint == null) { return null; } @@ -84,8 +83,6 @@ public Response interceptGetCapabilities(MutableHttpServletRequest request, Resp factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(new ByteArrayInputStream(body)); - Element root = doc.getDocumentElement(); - String version = root.getAttribute("version"); String proto = request.getHeader("x-forwarded-proto"); String host = request.getHeader("x-forwarded-host"); if (StringUtils.isEmpty(proto)) { @@ -95,13 +92,9 @@ public Response interceptGetCapabilities(MutableHttpServletRequest request, Resp host = request.getServerName(); } String baseUrl = proto + "://" + host + request.getParameter("CONTEXT_PATH") + "/geoserver.action/" + endpoint; - if (version.equals("1.3.0")) { - removeLayers(doc, "http://schemas.opengis.net/wmts", layerNames); - updateUrls(doc, "http://schemas.opengis.net/wmts", baseUrl); - } else { - removeLayers(doc, "", layerNames); - updateUrls(doc, "", baseUrl); - } + removeLayers(doc, "wmts", layerNames); + updateUrls(doc, "wmts", baseUrl); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); DOMSource source = new DOMSource(doc); ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -152,10 +145,14 @@ private void removeLayers(Document doc, String namespace, List layerName } XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); - NamespaceContext nscontext = CommonNamespaces.getNamespaceContext(); + String owsVersion = doc.getDocumentElement().getAttribute("xmlns:ows"); + String wmtsVersion = doc.getDocumentElement().getAttribute("xmlns"); + NamespaceContext nscontext = CommonNamespaces.getNamespaceContext(). + addNamespace("ows", owsVersion). + addNamespace("wmts", wmtsVersion); xpath.setNamespaceContext(nscontext); String prefix = namespace.equals("") ? "" : "wmts:"; - XPathExpression expr = xpath.compile("//" + prefix + "Layer/" + prefix + "Name"); + XPathExpression expr = xpath.compile("//" + prefix + "Layer/ows:Identifier"); NodeList nodeList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); List toRemove = new ArrayList<>(); for (int i = 0; i < nodeList.getLength(); ++i) { @@ -195,21 +192,36 @@ private void removeLayers(Document doc, String namespace, List layerName private void updateUrls(Document doc, String namespace, String baseUrl) throws XPathExpressionException { XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); - NamespaceContext nscontext = CommonNamespaces.getNamespaceContext(); + String owsVersion = doc.getDocumentElement().getAttribute("xmlns:ows"); + String wmtsVersion = doc.getDocumentElement().getAttribute("xmlns"); + NamespaceContext nscontext = CommonNamespaces.getNamespaceContext(). + addNamespace("ows", owsVersion). + addNamespace("wmts", wmtsVersion); xpath.setNamespaceContext(nscontext); - String prefix = namespace.equals("") ? "" : "wmts:"; - XPathExpression expr = xpath.compile("//" + prefix + "OnlineResource"); + XPathExpression expr = xpath.compile("//@xlink:href"); NodeList nodeList = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); for (int i = 0; i < nodeList.getLength(); ++i) { - Element link = (Element) nodeList.item(i); - String url = link.getAttributeNS("http://www.w3.org/1999/xlink", "xlink:href"); + String url = nodeList.item(i).getNodeValue(); int index = url.indexOf("?"); if (index > -1) { url = url.substring(index); url = baseUrl + url; - link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", url); + nodeList.item(i).setNodeValue(url); + } else { + nodeList.item(i).setNodeValue(baseUrl); + } + } + XPathExpression expr2 = xpath.compile("//wmts:Layer//@template"); + NodeList nodeList2 = (NodeList) expr2.evaluate(doc, XPathConstants.NODESET); + for (int i = 0; i < nodeList2.getLength(); ++i) { + String url = nodeList2.item(i).getNodeValue(); + int index = url.indexOf("gwc/service/wmts/rest"); + if (index > -1) { + String path = url.split("gwc/service/wmts/rest")[1]; + url = baseUrl + path; + nodeList2.item(i).setNodeValue(url); } else { - link.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", baseUrl); + nodeList2.item(i).setNodeValue(baseUrl); } } } diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/web/GeoServerInterceptorController.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/web/GeoServerInterceptorController.java index df2f48ba8..1803cb283 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/web/GeoServerInterceptorController.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/web/GeoServerInterceptorController.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; +import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -77,9 +78,26 @@ public ResponseEntity interceptWmtsRequest(HttpServletRequest request, @PathV /** * @param request */ - @RequestMapping(value = {"/geoserver.action", "/geoserver.action/{endpoint}"}, method = { - RequestMethod.GET, RequestMethod.POST}) - public ResponseEntity interceptGeoServerRequest(HttpServletRequest request, @PathVariable(value = "endpoint", required = false) Optional endpoint) { + @RequestMapping(value = { + "/geoserver.action", + "/geoserver.action/{endpoint}", + "/geoserver.action/{endpoint}/{layername}/{style}/{tilematrixset}/{tilematrix}/{tilerow}/{tilecol}", + "/geoserver.action/{endpoint}/{layername}/{style}/{tilematrixset}/{tilematrix}/{tilerow}/{tilecol}/{j}/{i}" + }, method = { + RequestMethod.GET, RequestMethod.POST + }) + public ResponseEntity interceptGeoServerRequest( + HttpServletRequest request, + @PathVariable(value = "endpoint", required = false) Optional endpoint, + @PathVariable(value = "layername", required = false) Optional layername, + @PathVariable(value = "style", required = false) Optional style, + @PathVariable(value = "tilematrixset", required = false) Optional tilematrixset, + @PathVariable(value = "tilematrix", required = false) Optional tilematrix, + @PathVariable(value = "tilerow", required = false) Optional tilerow, + @PathVariable(value = "tilecol", required = false) Optional tilecol, + @PathVariable(value = "j", required = false) Optional j, + @PathVariable(value = "i", required = false) Optional i + ) { HttpHeaders responseHeaders = new HttpHeaders(); HttpStatus responseStatus = HttpStatus.OK; byte[] responseBody; @@ -88,7 +106,19 @@ public ResponseEntity interceptGeoServerRequest(HttpServletRequest request, @ try { LOG.trace("Trying to intercept a GeoServer resource."); - httpResponse = this.service.interceptGeoServerRequest(request, endpoint); + HashMap> optionals = new HashMap>(); + optionals.put("endpoint", endpoint); + optionals.put("layername", layername); + optionals.put("style", style); + optionals.put("tilematrixset", tilematrixset); + optionals.put("tilematrix", tilematrix); + optionals.put("tilerow", tilerow); + optionals.put("tilecol", tilecol); + optionals.put("j", j); + optionals.put("i", i); + + httpResponse = this.service.interceptGeoServerRequest( + request, optionals); responseStatus = httpResponse.getStatusCode(); responseBody = httpResponse.getBody(); diff --git a/src/shogun-core-main/src/test/java/de/terrestris/shoguncore/service/GeoServerInterceptorServiceTest.java b/src/shogun-core-main/src/test/java/de/terrestris/shoguncore/service/GeoServerInterceptorServiceTest.java index 598b991fc..0097981f3 100644 --- a/src/shogun-core-main/src/test/java/de/terrestris/shoguncore/service/GeoServerInterceptorServiceTest.java +++ b/src/shogun-core-main/src/test/java/de/terrestris/shoguncore/service/GeoServerInterceptorServiceTest.java @@ -36,6 +36,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Properties; @@ -96,7 +97,7 @@ public void send_get() throws InterceptorException, MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(httpRequest); when(ogcMessageDistributor.distributeToRequestInterceptor( - any(MutableHttpServletRequest.class), any(OgcMessage.class))).thenReturn(mutableRequest); + any(MutableHttpServletRequest.class), any(OgcMessage.class), any(HashMap.class))).thenReturn(mutableRequest); when(ogcMessageDistributor.distributeToResponseInterceptor( any(MutableHttpServletRequest.class), any(Response.class), any(OgcMessage.class))).thenReturn(resp); @@ -125,7 +126,7 @@ public void send_wms_get_to_reflector() throws InterceptorException, MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(httpRequest); when(ogcMessageDistributor.distributeToRequestInterceptor( - any(MutableHttpServletRequest.class), any(OgcMessage.class))).thenReturn(mutableRequest); + any(MutableHttpServletRequest.class), any(OgcMessage.class), any(HashMap.class))).thenReturn(mutableRequest); when(ogcMessageDistributor.distributeToResponseInterceptor( any(MutableHttpServletRequest.class), any(Response.class), any(OgcMessage.class))).thenReturn(resp); @@ -159,7 +160,7 @@ public void send_post_kvp() throws URISyntaxException, HttpException, MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(httpRequest); when(ogcMessageDistributor.distributeToRequestInterceptor( - any(MutableHttpServletRequest.class), any(OgcMessage.class))).thenReturn(mutableRequest); + any(MutableHttpServletRequest.class), any(OgcMessage.class), any(HashMap.class))).thenReturn(mutableRequest); when(ogcMessageDistributor.distributeToResponseInterceptor( any(MutableHttpServletRequest.class), any(Response.class), any(OgcMessage.class))).thenReturn(resp); @@ -200,7 +201,7 @@ public void send_post_kvp_and_query_params() throws URISyntaxException, HttpExce MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(httpRequest); when(ogcMessageDistributor.distributeToRequestInterceptor( - any(MutableHttpServletRequest.class), any(OgcMessage.class))).thenReturn(mutableRequest); + any(MutableHttpServletRequest.class), any(OgcMessage.class), any(HashMap.class))).thenReturn(mutableRequest); // for the next stub it would be even better if we could use .then(returnsFirstArg() but this needs java 8 when(ogcMessageDistributor.distributeToResponseInterceptor( @@ -244,7 +245,7 @@ public void send_post_body() throws URISyntaxException, HttpException, MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(httpRequest); when(ogcMessageDistributor.distributeToRequestInterceptor( - any(MutableHttpServletRequest.class), any(OgcMessage.class))).thenReturn(mutableRequest); + any(MutableHttpServletRequest.class), any(OgcMessage.class), any(HashMap.class))).thenReturn(mutableRequest); // for the next stub it would be even better if we could use .then(returnsFirstArg() but this needs java 8 when(ogcMessageDistributor.distributeToResponseInterceptor( @@ -296,7 +297,7 @@ public void send_post_body_and_query_params() throws URISyntaxException, HttpExc MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(httpRequest); when(ogcMessageDistributor.distributeToRequestInterceptor( - any(MutableHttpServletRequest.class), any(OgcMessage.class))).thenReturn(mutableRequest); + any(MutableHttpServletRequest.class), any(OgcMessage.class), any(HashMap.class))).thenReturn(mutableRequest); when(ogcMessageDistributor.distributeToResponseInterceptor( any(MutableHttpServletRequest.class), any(Response.class), any(OgcMessage.class))).thenReturn(resp); diff --git a/src/shogun-core-main/src/test/java/de/terrestris/shoguncore/web/GeoServerInterceptorControllerTest.java b/src/shogun-core-main/src/test/java/de/terrestris/shoguncore/web/GeoServerInterceptorControllerTest.java index 68d86b362..79720f0d8 100644 --- a/src/shogun-core-main/src/test/java/de/terrestris/shoguncore/web/GeoServerInterceptorControllerTest.java +++ b/src/shogun-core-main/src/test/java/de/terrestris/shoguncore/web/GeoServerInterceptorControllerTest.java @@ -19,6 +19,8 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import javax.servlet.http.HttpServletRequest; + +import java.util.HashMap; import java.util.Optional; import static de.terrestris.shoguncore.web.GeoServerInterceptorController.ERROR_MESSAGE; @@ -57,7 +59,7 @@ public void worksForHttpGet() throws Exception { Mockito.when(geoServerInterceptorService.interceptGeoServerRequest( Matchers.any(HttpServletRequest.class), - Matchers.any(Optional.class) + Matchers.any(HashMap.class) )).thenReturn(responseObject); MvcResult result = mockMvc.perform(MockMvcRequestBuilders.get(INTERCEPTOR_ENDPOINT)) @@ -75,7 +77,7 @@ public void worksForHttpPost() throws Exception { Mockito.when(geoServerInterceptorService.interceptGeoServerRequest( Matchers.any(HttpServletRequest.class), - Matchers.any(Optional.class) + Matchers.any(HashMap.class) )).thenReturn(responseObject); MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post(INTERCEPTOR_ENDPOINT)) From 7d0410eac9341e15e8b7d3c7fb2d20b94b78a746 Mon Sep 17 00:00:00 2001 From: Johannes Weskamm Date: Thu, 16 Jul 2020 08:55:00 +0200 Subject: [PATCH 3/4] Update src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java Co-authored-by: Daniel Koch --- .../shoguncore/service/GeoServerInterceptorService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java index 7e050f149..33b98e494 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java @@ -355,8 +355,7 @@ public Response interceptWmtsRequest(HttpServletRequest request, String serviceI } if (dataSource == null) { - Response response = new Response(HttpStatus.FORBIDDEN, null, new byte[0]); - return response; + return new Response(HttpStatus.FORBIDDEN, null, new byte[0]); } String baseUrl = dataSource.getUrl(); From 59d4f4fbc36523d2a8ae0e0543f972b9c11f43e3 Mon Sep 17 00:00:00 2001 From: Johannes Weskamm Date: Thu, 16 Jul 2020 11:02:14 +0200 Subject: [PATCH 4/4] refactoring --- .../service/GeoServerInterceptorService.java | 8 ++++ .../interceptor/secure/BaseInterceptor.java | 46 ++++++++++++++++++ .../secure/WfsRequestInterceptor.java | 47 ++++--------------- .../secure/WmsRequestInterceptor.java | 41 +--------------- .../secure/WmtsRequestInterceptor.java | 39 +-------------- 5 files changed, 67 insertions(+), 114 deletions(-) create mode 100644 src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/BaseInterceptor.java diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java index 33b98e494..770d132de 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/service/GeoServerInterceptorService.java @@ -54,6 +54,7 @@ public class GeoServerInterceptorService { */ private static final Logger LOG = getLogger( GeoServerInterceptorService.class); + /** * An array of whitelisted Headers to forward within the Interceptor. */ @@ -68,14 +69,20 @@ public class GeoServerInterceptorService { "geowebcache-tile-index", "geowebcache-miss-reason" }; + + /** + * + */ private static final Pattern WMTS_PATTERN = Pattern.compile("/[^/]+/wmts.action/\\d+/(.*)"); private static final String WMS_REFLECT_ENDPOINT = "/reflect"; private static final String USE_REFLECT_PARAM = "useReflect"; + /** * */ @Autowired OgcMessageDistributor ogcMessageDistributor; + /** * */ @@ -350,6 +357,7 @@ public Response interceptWmtsRequest(HttpServletRequest request, String serviceI WmtsLayerDataSource source = (WmtsLayerDataSource) layer.getSource(); if (source.getId().equals(id)) { dataSource = source; + break; } } } diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/BaseInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/BaseInterceptor.java new file mode 100644 index 000000000..51f3d1d61 --- /dev/null +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/BaseInterceptor.java @@ -0,0 +1,46 @@ +package de.terrestris.shoguncore.util.interceptor.secure; + +import de.terrestris.shoguncore.util.application.ShogunCoreContextUtil; +import de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest; + +import java.net.URI; +import java.net.URISyntaxException; + +/** + *

BaseInterceptor class.

+ */ +public class BaseInterceptor { + + /** + * + */ + private URI appUri = null; + + /** + *

forbidRequest.

+ * + * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. + * @return a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. + */ + protected MutableHttpServletRequest forbidRequest(MutableHttpServletRequest request) { + this.setAppUriFromRequest(request); + String redirectUri = appUri == null ? "" : appUri.toString(); + request.setRequestURI(redirectUri + "/response/forbidden.action"); + return request; + } + + /** + *

setAppUriFromRequest.

+ * + * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. + */ + protected void setAppUriFromRequest(MutableHttpServletRequest request) { + if (appUri == null) { + try { + appUri = ShogunCoreContextUtil.getApplicationURIFromRequest(request); + } catch (URISyntaxException e) { + // pass + } + } + } +} diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsRequestInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsRequestInterceptor.java index 9c77a48e4..70bc2b75b 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsRequestInterceptor.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WfsRequestInterceptor.java @@ -11,7 +11,7 @@ import de.terrestris.shoguncore.model.security.PermissionCollection; import de.terrestris.shoguncore.service.LayerService; import de.terrestris.shoguncore.service.UserService; -import de.terrestris.shoguncore.util.application.ShogunCoreContextUtil; +import de.terrestris.shoguncore.util.enumeration.OgcEnum; import de.terrestris.shoguncore.util.interceptor.GeoserverAuthHeaderRequest; import de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest; import de.terrestris.shoguncore.util.interceptor.WfsRequestInterceptorInterface; @@ -24,8 +24,6 @@ import static org.apache.logging.log4j.LogManager.getLogger; -import java.net.URI; -import java.net.URISyntaxException; import java.util.List; import java.util.Map; import java.util.Set; @@ -38,12 +36,7 @@ * @author Johannes Weskamm * */ -public class WfsRequestInterceptor implements WfsRequestInterceptorInterface { - - /** - * - */ - private URI appUri = null; +public class WfsRequestInterceptor extends BaseInterceptor implements WfsRequestInterceptorInterface { /** * @@ -82,6 +75,7 @@ public class WfsRequestInterceptor implements WfsRequestInterceptorInterface { @Override public MutableHttpServletRequest interceptGetCapabilities(MutableHttpServletRequest request) { LOG.debug("Intercepting WFS GetCapabilities"); + // response will be intercepted return new GeoserverAuthHeaderRequest(request, gsUser, gsPass); } @@ -91,6 +85,7 @@ public MutableHttpServletRequest interceptGetCapabilities(MutableHttpServletRequ @Override public MutableHttpServletRequest interceptDescribeFeatureType(MutableHttpServletRequest request) { LOG.debug("Intercepting WFS DescribeFeatureType"); + // response will be intercepted return new GeoserverAuthHeaderRequest(request, gsUser, gsPass); } @@ -136,6 +131,7 @@ public MutableHttpServletRequest interceptTransaction(MutableHttpServletRequest private boolean isAllowed( MutableHttpServletRequest request, String paramName, String method) { String typeNameParam = request.getParameterIgnoreCase(paramName); + List all = layerService.findAll(); boolean match = false; for (Layer layer : all) { @@ -152,6 +148,7 @@ private boolean isAllowed( // implicitly checked by findAll method match = true; } + break; } } else if (layer.getSource() instanceof ImageWmsLayerDataSource) { ImageWmsLayerDataSource source = (ImageWmsLayerDataSource) @@ -165,6 +162,7 @@ private boolean isAllowed( // implicitly checked by findAll method match = true; } + break; } } } @@ -179,9 +177,9 @@ private boolean isAllowed( * @return If the layer is allowed to read for current user */ private boolean isAllowed(MutableHttpServletRequest request, String method) { - String typeNameParam = "typeNames"; + String typeNameParam = OgcEnum.EndPoint.TYPENAMES.toString(); if (StringUtils.isEmpty(request.getParameterIgnoreCase(typeNameParam))) { - typeNameParam = "typeName"; + typeNameParam = OgcEnum.EndPoint.TYPENAME.toString(); } return isAllowed(request, typeNameParam, method); } @@ -217,31 +215,4 @@ private boolean checkForPermission(Layer layer, Permission permission) { return allowedOnUser || allowedOnGroup; } - /** - *

forbidRequest.

- * - * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. - * @return a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. - */ - protected MutableHttpServletRequest forbidRequest(MutableHttpServletRequest request) { - this.setAppUriFromRequest(request); - String redirectUri = appUri == null ? "" : appUri.toString(); - request.setRequestURI(redirectUri + "/response/forbidden.action"); - return request; - } - - /** - *

setAppUriFromRequest.

- * - * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. - */ - protected void setAppUriFromRequest(MutableHttpServletRequest request) { - if (appUri == null) { - try { - appUri = ShogunCoreContextUtil.getApplicationURIFromRequest(request); - } catch (URISyntaxException e) { - // pass - } - } - } } diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmsRequestInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmsRequestInterceptor.java index a37be576e..c509cf0a8 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmsRequestInterceptor.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmsRequestInterceptor.java @@ -4,7 +4,6 @@ import de.terrestris.shoguncore.model.layer.Layer; import de.terrestris.shoguncore.model.layer.source.ImageWmsLayerDataSource; import de.terrestris.shoguncore.service.LayerService; -import de.terrestris.shoguncore.util.application.ShogunCoreContextUtil; import de.terrestris.shoguncore.util.interceptor.GeoserverAuthHeaderRequest; import de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest; import de.terrestris.shoguncore.util.interceptor.WmsRequestInterceptorInterface; @@ -12,11 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; - import static org.apache.logging.log4j.LogManager.getLogger; - -import java.net.URI; -import java.net.URISyntaxException; import java.util.List; /** @@ -27,12 +22,7 @@ * @author Johannes Weskamm * */ -public class WmsRequestInterceptor implements WmsRequestInterceptorInterface { - - /** - * - */ - private URI appUri = null; +public class WmsRequestInterceptor extends BaseInterceptor implements WmsRequestInterceptorInterface { /** * @@ -138,6 +128,7 @@ private boolean isAllowed(MutableHttpServletRequest request, source.getUrl().equalsIgnoreCase(request.getContextPath() + "/geoserver.action")) { match = true; + break; } } } @@ -154,32 +145,4 @@ private boolean isAllowed(MutableHttpServletRequest request) { return isAllowed(request, "layers"); } - /** - *

forbidRequest.

- * - * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. - * @return a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. - */ - protected MutableHttpServletRequest forbidRequest(MutableHttpServletRequest request) { - this.setAppUriFromRequest(request); - String redirectUri = appUri == null ? "" : appUri.toString(); - request.setRequestURI(redirectUri + "/response/forbidden.action"); - return request; - } - - /** - *

setAppUriFromRequest.

- * - * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. - */ - protected void setAppUriFromRequest(MutableHttpServletRequest request) { - if (appUri == null) { - try { - appUri = ShogunCoreContextUtil.getApplicationURIFromRequest(request); - } catch (URISyntaxException e) { - // pass - } - } - } - } diff --git a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsRequestInterceptor.java b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsRequestInterceptor.java index eb25c54a7..689f25c44 100644 --- a/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsRequestInterceptor.java +++ b/src/shogun-core-main/src/main/java/de/terrestris/shoguncore/util/interceptor/secure/WmtsRequestInterceptor.java @@ -4,7 +4,6 @@ import de.terrestris.shoguncore.model.layer.Layer; import de.terrestris.shoguncore.model.layer.source.WmtsLayerDataSource; import de.terrestris.shoguncore.service.LayerService; -import de.terrestris.shoguncore.util.application.ShogunCoreContextUtil; import de.terrestris.shoguncore.util.interceptor.GeoserverAuthHeaderRequest; import de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest; import de.terrestris.shoguncore.util.interceptor.WmtsRequestInterceptorInterface; @@ -17,8 +16,6 @@ import static org.apache.logging.log4j.LogManager.getLogger; -import java.net.URI; -import java.net.URISyntaxException; import java.util.HashMap; import java.util.List; import java.util.Optional; @@ -31,12 +28,7 @@ * @author Johannes Weskamm * */ -public class WmtsRequestInterceptor implements WmtsRequestInterceptorInterface { - -/** - * - */ - private URI appUri = null; +public class WmtsRequestInterceptor extends BaseInterceptor implements WmtsRequestInterceptorInterface { /** * @@ -183,38 +175,11 @@ private boolean isAllowed(MutableHttpServletRequest request, } if (sourceLayer.equalsIgnoreCase(layer)) { match = true; + break; } } } return match; } - /** - *

forbidRequest.

- * - * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. - * @return a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. - */ - protected MutableHttpServletRequest forbidRequest(MutableHttpServletRequest request) { - this.setAppUriFromRequest(request); - String redirectUri = appUri == null ? "" : appUri.toString(); - request.setRequestURI(redirectUri + "/response/forbidden.action"); - return request; - } - - /** - *

setAppUriFromRequest.

- * - * @param request a {@link de.terrestris.shoguncore.util.interceptor.MutableHttpServletRequest} object. - */ - protected void setAppUriFromRequest(MutableHttpServletRequest request) { - if (appUri == null) { - try { - appUri = ShogunCoreContextUtil.getApplicationURIFromRequest(request); - } catch (URISyntaxException e) { - // pass - } - } - } - }