Created
April 22, 2021 08:24
-
-
Save michimau/97487bc3eaced17a4ff9feb14935754f to your computer and use it in GitHub Desktop.
ArcGISProxy.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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