Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interceptor security #386

Merged
merged 4 commits into from
Jul 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -52,6 +54,7 @@ public class GeoServerInterceptorService {
*/
private static final Logger LOG = getLogger(
GeoServerInterceptorService.class);

/**
* An array of whitelisted Headers to forward within the Interceptor.
*/
Expand All @@ -66,22 +69,33 @@ 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;

/**
*
*/
@Autowired
InterceptorRuleService<InterceptorRule, ?> interceptorRuleService;

/**
*
*/
@Autowired
@Qualifier("layerDataSourceDao")
private LayerDataSourceDao<WmtsLayerDataSource> wmtsLayerDataSourceDao;
@Qualifier("layerService")
protected LayerService<Layer, LayerDao<Layer>> layerService;

/**
* The autowired properties file containing the (application driven)
* GeoServer namespace - GeoServer BaseURI mapping, e.g.:
Expand Down Expand Up @@ -334,7 +348,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<Layer> 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;
weskamm marked this conversation as resolved.
Show resolved Hide resolved
break;
}
}
}

if (dataSource == null) {
return new Response(HttpStatus.FORBIDDEN, null, new byte[0]);
}

String baseUrl = dataSource.getUrl();
Response response = HttpUtil.get(baseUrl + "/" + path);

Expand All @@ -345,6 +376,7 @@ public Response interceptWmtsRequest(HttpServletRequest request, String serviceI
}

/**
* Calls the main method with empty optionals hashmap
* @param request
* @return
* @throws InterceptorException
Expand All @@ -353,34 +385,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<String, Optional<String>>());
}

/**
*
* @param request
* @param endpoint
* @param optionals
* @return
* @throws InterceptorException
* @throws URISyntaxException
* @throws HttpException
* @throws IOException
*/
public Response interceptGeoServerRequest(HttpServletRequest request, Optional<String> endpoint)
public Response interceptGeoServerRequest(
HttpServletRequest request,
HashMap<String, Optional<String>> optionals)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have the optionals typed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure here, do you want me to instantiate the hashmap with null values as class variable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of a simple class definition for optionals that defines the structure of the parameter.

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);
Expand All @@ -393,7 +442,7 @@ public Response interceptGeoServerRequest(HttpServletRequest request, Optional<S

// intercept the request (if needed)
mutableRequest = ogcMessageDistributor
.distributeToRequestInterceptor(mutableRequest, message);
.distributeToRequestInterceptor(mutableRequest, message, optionals);

// send the request
// TODO: Move to global proxy class
Expand Down Expand Up @@ -440,11 +489,14 @@ private boolean shouldReflectEndpointBeCalled(MutableHttpServletRequest mutableR

/**
* @param mutableRequest
* @param isRestfulWmtsGetFeatureinfo
* @param isRestfulWmts
* @return
* @throws InterceptorException
* @throws IOException
*/
private OgcMessage getOgcMessage(MutableHttpServletRequest mutableRequest)
private OgcMessage getOgcMessage(MutableHttpServletRequest mutableRequest, boolean isRestfulWmts,
boolean isRestfulWmtsGetFeatureinfo)
throws InterceptorException, IOException {

LOG.trace("Building the OGC message from the given request.");
Expand All @@ -458,6 +510,18 @@ private OgcMessage getOgcMessage(MutableHttpServletRequest mutableRequest)
String requestEndPoint = MutableHttpServletRequest.getRequestParameterValue(
mutableRequest, OgcEnum.EndPoint.getAllValues());

if (isRestfulWmts) {
requestService = ServiceType.WMTS.toString();
String format = mutableRequest.getParameterIgnoreCase("format");
if (isRestfulWmtsGetFeatureinfo) {
requestOperation = OperationType.GET_FEATURE_INFO.toString();
} else if (format != null && format.toLowerCase(Locale.ROOT).contains("image")) {
requestOperation = OperationType.GET_TILE.toString();
} else {
requestOperation = OperationType.GET_CAPABILITIES.toString();
}
}

if (StringUtils.isEmpty(requestService) ||
StringUtils.isEmpty(requestOperation) ||
StringUtils.isEmpty(requestEndPoint)) {
Expand Down
Loading