Skip to content

Instantly share code, notes, and snippets.

@michimau
Created April 22, 2021 08:24
Show Gist options
  • Save michimau/97487bc3eaced17a4ff9feb14935754f to your computer and use it in GitHub Desktop.
Save michimau/97487bc3eaced17a4ff9feb14935754f to your computer and use it in GitHub Desktop.
ArcGISProxy.java
package eu.europa.ec.dg_env.lpd.web.arcgis_proxy;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map.Entry;
import java.util.Set;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import eu.europa.ec.dg_env.lpd.common.configuration.SystemConfiguration;
import eu.europa.ec.dg_env.lpd.common.configuration.SystemConfiguration.SystemConfigurationKeys;
import eu.europa.ec.dg_env.lpd.model.ModelLayer;
import eu.europa.ec.dg_env.lpd.service.beneficiary_context.BeneficiaryContext;
import eu.europa.ec.dg_env.lpd.web.security.BeneficiaryContextTokenMap;
/**
* Proxy servlet that protects the ArcGIS (Feature) Layer used by the
* esri.layers.FeatureLayer (Dojo Toolkit, see "6.4.1.2 Integration of the GIS
* Viewer for the Beneficiary" in LPD-CAD)
*
* Example of client side code :
* "var featureLayer = new esri.layers.FeatureLayer("
* http://lpd-vm:8080/web/ArcGISProxy",{ ..."
*
* Parameters security
*
* [1] when specified multiple time in the same URL, the occurrence of the
* parameter that is used by ArcGIS Server 10.1 is the first one.
*
* [2] encode URL parameters before forwarding them to ArcGIS.
*
* [3] does not forward other parameters than "callback" and "outSR", which have
* no impact on the filtering.
*
* Test : because of (([1] or [2]) and [3]), trying to inject the
* "objectIds=109" criteria via the "callabck" parameter forwarded from the
* initial URL won't have any effect.
* http://lpd-vm:8080/web/ArcGISProxy?callback=%26objectIds=109
*
* @author detailoc
*/
@WebServlet(description = "Map Service proxy", urlPatterns = {
ArcGISProxy.URL_PATTERN_1, ArcGISProxy.URL_PATTERN_2,
ArcGISProxy.URL_PATTERN_RENDERER_GENERATION })
public class ArcGISProxy extends HttpServlet {
private final transient Logger log = Logger.getLogger(this.getClass()
.getSimpleName());
private final String arcGISURL = SystemConfiguration
.getValueWithNullCheck(SystemConfigurationKeys.SCK_BENEFICIARY_FEATURE_LAYER_URL);
public ArcGISProxy() {
}
private static final long serialVersionUID = 1L;
protected static final String URL_PATTERN_1 = "/ArcGISProxy";
protected static final String URL_PATTERN_2 = URL_PATTERN_1 + "/query";
protected static final String URL_PATTERN_RENDERER_GENERATION = URL_PATTERN_1
+ "/generateRenderer";
@Inject
private BeneficiaryContextTokenMap beneficiaryAccessTokenMap;
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
* response)
*
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String requestURL = request.getRequestURL().toString();
StringBuilder targetURLBuilder = new StringBuilder(arcGISURL);
String token = urlEncoding(request.getParameter("token"));
String role = urlEncoding(request.getParameter("role"));
String lifeProjectNumber = request.getParameter("projectNumber");
String tblPrefix = SystemConfiguration
.getValueWithNullCheck(SystemConfigurationKeys.SCK_TABLE_PREFIX);
StringBuilder filterParameter = new StringBuilder("where=")
.append(urlEncoding("1=1"));
if (log.isDebugEnabled()) {
log.debug("requestURL=" + request.getRequestURL().toString());
}
if (requestURL.endsWith(URL_PATTERN_RENDERER_GENERATION)) {
targetURLBuilder.append("/generateRenderer?").append(
request.getQueryString());
forwardToActualMapService(targetURLBuilder.toString(), response);
return;
}
ModelLayer layer = null;
if (role != null && role.equals("public")) { // public access
layer = ModelLayer.PUBLISHED;
filterParameter.append(urlEncoding(" AND " + tblPrefix
+ ".DATA_LIFEPROJECT.sensitiveFlag=0 AND " + tblPrefix
+ ".DATA_PARCEL.sensitiveFlag=0"));
} else { // private access
BeneficiaryContext beneficiaryContext = beneficiaryAccessTokenMap
.get(role, token);
layer = ModelLayer.SOURCE;
if (beneficiaryContext == null) {
log.warn("Tried to access " + requestURL + " from "
+ request.getLocalAddr()
+ " without being authenticated");
return;
}
lifeProjectNumber = beneficiaryContext.getLifeProjectNumber();
}
// Parameters of the original GET must to forwarded to the actual
// map
// service.
// Note : do not forward 'where' and 'objectIds'.
// Filter that must apply
// Note : place the 'filterParameter' BEFORE all forwarded
// parameters
// (safer since ArcGIS 10.1 use the first occurrence of each
// parameter :
// objectIds=110&objectIds=109 => objectIds will be 110).
filterParameter.append(
urlEncoding(" AND " + tblPrefix + ".DATA_LIFEPROJECT.number='"
+ lifeProjectNumber + "' AND " + tblPrefix
+ ".DATA_LIFEPROJECT.layer='" + layer + "'")).append(
"&");
if (requestURL.endsWith(URL_PATTERN_1)) {
// First query issued by the esri.layers.FeatureLayer
targetURLBuilder.append("?").append(getQueryString(request));
forwardToActualMapService(targetURLBuilder.toString(), response);
} else if (requestURL.endsWith(URL_PATTERN_2)) {
// Second query issued by the esri.layers.FeatureLayer
targetURLBuilder.append("/query?").append(filterParameter)
.append(getQueryString(request));
forwardToActualMapService(targetURLBuilder.toString(), response);
} else {
// should not happen since specified in 'urlPatterns', but ...
log.warn("Tried to access unauthorized url " + requestURL
+ " from " + request.getLocalAddr());
}
}
private String getQueryString(HttpServletRequest request)
throws UnsupportedEncodingException {
StringBuilder query = new StringBuilder();
Set<Entry<String, String[]>> set = request.getParameterMap().entrySet();
for (Entry<String, String[]> entry : set) {
if (!"token".equals(entry.getKey()) // filter the proxy security
// params
&& !"role".equals(entry.getKey())) {
for (String value : entry.getValue()) {
query.append(entry.getKey()).append("=")
.append(urlEncoding(value)).append("&");
}
}
}
return query.toString();
}
/**
* Performs URL encoding.
*
* @param s
* @return s == null ? null : url encoded version of s (e.g.:
* @throws UnsupportedEncodingException
*/
private String urlEncoding(String s) throws UnsupportedEncodingException {
return s == null ? s : URLEncoder.encode(s, "UTF-8");
}
/**
* Invoke 'target' and copy the result to 'response'.
*
* @param target
* @param response
* @throws IOException
*/
private void forwardToActualMapService(String target,
HttpServletResponse response) throws IOException {
if (log.isDebugEnabled()) {
log.debug("forwarding to " + target);
}
URL url = new URL(target);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setDoOutput(true);
con.setRequestMethod("GET");
response.setContentType(con.getContentType());
response.setContentLength(con.getContentLength());
if (log.isTraceEnabled()) {
log.trace("Etag=" + con.getHeaderField("Etag"));
}
response.addHeader("Etag", con.getHeaderField("Etag"));
IOUtils.copy(con.getInputStream(), response.getOutputStream());
con.getInputStream().close();
response.getOutputStream().close();
con.disconnect();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment