/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.esri.gpt.sdisuite; import com.esri.gpt.framework.context.RequestContext; import com.esri.gpt.framework.security.identity.NotAuthorizedException; import com.esri.gpt.framework.security.principal.User; import com.esri.gpt.framework.util.Val; import java.io.IOException; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Callback servlet when certain links associated with a discovered resource are clicked. */ public class IntegrationLinkServlet extends HttpServlet { /** The Logger. */ private static Logger LOGGER = Logger.getLogger(IntegrationLinkServlet.class.getName()); /** User attribute key holding the SAML security token */ private static String SDI_SECURITY_TOKEN = "sdi.security.token"; @Override public void destroy() { super.destroy(); } @Override public void init() throws ServletException { super.init(); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.execute(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.execute(request,response); } /** * Processes the HTTP request. * @param request the HTTP request * @param response HTTP response * @throws ServletException if an exception occurs * @throws IOException if an I/O exception occurs */ private void execute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { LOGGER.finer("Query string="+request.getQueryString()); // initialize parameters execute the appropriate request String lcb = request.getParameter("lcb"); String act = request.getParameter("act"); if ((lcb != null) && lcb.equals("true")) { this.executeLicenseCallback(request,response); } else if (act != null) { this.executeClick(request,response); } else { this.writeError(request,response,"No action was specified.",null); } } /** * Processes a click on a resource link. * @param request the HTTP request * @param response HTTP response * @throws ServletException if an exception occurs * @throws IOException if an I/O exception occurs */ private void executeClick(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { LOGGER.finer("Processing resource link click..."); // initialize parameters String act = request.getParameter("act"); String fwd = request.getParameter("fwd"); String resourceUrl = null; String addToMapHint = null; // determine the resource URL to be checked if (act != null) { if (act.equals("open")) { resourceUrl = fwd; } else if (act.equals("preview")) { Map<String,String> params = this.gatherParams(fwd); resourceUrl = params.get("url"); } else if (act.equals("addToMap")) { Map<String,String> params = this.gatherParams(fwd); resourceUrl = params.get("resource"); if ((resourceUrl != null) && (resourceUrl.length() > 0)) { if (!resourceUrl.toLowerCase().startsWith("http")) { int idx = resourceUrl.indexOf(":"); if (idx != -1) { addToMapHint = resourceUrl.substring(0,idx); resourceUrl = resourceUrl.substring(idx+1); } } } } else { resourceUrl = fwd; } } // check the resource URL if ((resourceUrl != null) && (resourceUrl.length() > 0)) { LOGGER.finer("Checking resource URL: "+resourceUrl); RequestContext rc = null; String samlToken = null; try { rc = RequestContext.extract(request); User user = rc.getUser(); IntegrationResponse resp = null; IntegrationContextFactory icf = new IntegrationContextFactory(); if (icf.isIntegrationEnabled()) { IntegrationContext ic = icf.newIntegrationContext(); if (ic != null) { resp = ic.checkUrl(resourceUrl,user,null,null,null); if ((resp != null) && resp.isLicensed()) { if ((user != null) && (user.getProfile() != null)) { if (user.getProfile().containsKey(SDI_SECURITY_TOKEN)) { samlToken = ic.getBase64EncodedToken(user); } } } } } // handle a licensed URL if ((resp != null) && resp.isLicensed()) { String wssUrl = resp.getUrl(); String licenseSelectionUrl = resp.getLicenseSelectionClientUrl(); if ((licenseSelectionUrl != null) && (licenseSelectionUrl.length() > 0) && (wssUrl != null) && (wssUrl.length() > 0)) { // save resource URL parameters String wssUrlParams = null; int idx = wssUrl.indexOf("?"); if (idx != -1) { wssUrlParams = wssUrl.substring(idx+1).trim(); wssUrl = wssUrl.substring(0,idx); } // make the callback URL String callbackUrl = RequestContext.resolveBaseContextPath(request)+"/link"; callbackUrl += "?lcb="+URLEncoder.encode("true","UTF-8"); callbackUrl += "&act="+URLEncoder.encode(act,"UTF-8"); callbackUrl += "&fwd="+URLEncoder.encode(fwd,"UTF-8"); if ((wssUrlParams != null) && (wssUrlParams.length() > 0)) { callbackUrl += "&rqs="+URLEncoder.encode(wssUrlParams,"UTF-8"); } if ((addToMapHint != null) && (addToMapHint.length() > 0)) { callbackUrl += "&atmh="+URLEncoder.encode(addToMapHint,"UTF-8"); } // make the full license selection URL (can set &embedded=true) licenseSelectionUrl += "?WSS="+URLEncoder.encode(wssUrl,"UTF-8"); licenseSelectionUrl += "&returnURL="+URLEncoder.encode(callbackUrl,"UTF-8"); // if user is logged in, // return an HTML response that immediately posts the SAML token to the license selection URL // else // forward to the licenseSelectionUrl if ((samlToken != null) && (samlToken.length() > 0)) { LOGGER.finer("Sending POST redirect with token to: " + licenseSelectionUrl); fwd = null; String title = "License redirect SSO page"; StringBuilder sbHtml = new StringBuilder(); sbHtml.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"); sbHtml.append("\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">"); sbHtml.append("\r\n<head>"); sbHtml.append("\r\n<title>").append(Val.escapeXmlForBrowser(title)).append("</title>"); sbHtml.append("\r\n<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\"/>"); sbHtml.append("\r\n<meta http-equiv=\"Expires\" content=\"Mon, 01 Jan 1990 00:00:01 GMT\"/>"); sbHtml.append("\r\n<meta http-equiv=\"pragma\" content=\"no-cache\"/>"); sbHtml.append("\r\n<meta http-equiv=\"cache-control\" content=\"no-cache\"/>"); sbHtml.append("\r\n<meta name=\"robots\" content=\"noindex\"/>"); sbHtml.append("\r\n</head>"); sbHtml.append("\r\n<body onload=\"document.forms[0].submit();\">"); sbHtml.append("\r\n<form method=\"post\" action=\"").append(Val.escapeXmlForBrowser(licenseSelectionUrl)).append("\">"); sbHtml.append("\r\n<input type=\"hidden\" name=\"ticket\" value=\"").append(Val.escapeXmlForBrowser(samlToken)).append("\"/>"); sbHtml.append("\r\n</form>"); sbHtml.append("\r\n</body>"); sbHtml.append("\r\n</html>"); this.writeCharacterResponse(response,sbHtml.toString(),"UTF-8","text/html; charset=UTF-8"); } else { fwd = licenseSelectionUrl; } } else { String msg = "IntegrationResponse isLicensed() was true, but getLicenseSelectionClientUrl() was empty."; LOGGER.warning(msg); } // handle a secured URL } else if ((resp != null) && resp.isSecured()) { String securedUrl = resp.getUrl(); if ((securedUrl != null) && !securedUrl.equals(resourceUrl)) { if (act.equals("open")) { fwd = securedUrl; } else if (act.equals("preview")) { fwd = this.replaceParam(fwd,"url",securedUrl); } else if (act.equals("addToMap")) { if ((addToMapHint != null) && (addToMapHint.length() > 0)) { securedUrl = addToMapHint+":"+securedUrl; } fwd = this.replaceParam(fwd,"resource",securedUrl); } else { fwd = securedUrl; } } } } catch (NotAuthorizedException e) { String msg = "Error checking resource URL"; LOGGER.log(Level.SEVERE,msg,e); this.writeError(request,response,msg+": "+e.toString(),null); return; } catch (Exception e) { String msg = "Error checking resource URL"; LOGGER.log(Level.SEVERE,msg,e); this.writeError(request,response,msg+": "+e.toString(),null); return; } finally { if (rc != null) rc.onExecutionPhaseCompleted(); } } // send the redirect if ((fwd != null) && (fwd.length() > 0)) { LOGGER.finer("Redirecting to: "+fwd); response.sendRedirect(fwd); } } /** * Processes the response of a license selection. * @param request the HTTP request * @param response HTTP response * @throws ServletException if an exception occurs * @throws IOException if an I/O exception occurs */ private void executeLicenseCallback(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { LOGGER.finer("Processing license selection callback..."); // this code is a modification of Oliver's returnAction.jsp // Geoportal parameters String act = request.getParameter("act"); if ((act == null) || (act.length() == 0)) act = request.getParameter("amp;act"); String fwd = request.getParameter("fwd"); if ((fwd == null) || (fwd.length() == 0)) fwd = request.getParameter("amp;fwd"); String wssUrlParams = request.getParameter("rqs"); if ((wssUrlParams == null) || (wssUrlParams.length() == 0)) wssUrlParams = request.getParameter("amp;rqs"); String addToMapHint = request.getParameter("atmh"); if ((addToMapHint == null) || (addToMapHint.length() == 0)) addToMapHint = request.getParameter("amp;atmh"); // license parameters String lLicenseReference = request.getParameter("licenseReference"); String lWssUrl = request.getParameter("WSS"); String lSuccess = request.getParameter("success"); String lError = request.getParameter("errorMessage"); // check for error if ((lSuccess == null) || !lSuccess.equals("true")) { if ((lError != null) && (lError.length() > 0)) { this.writeError(request,response,"An error occurred while acquiring a license: "+lError,Level.SEVERE); } else { this.writeError(request,response,"Canceled",null); } } else if ((act == null) || (act.length() == 0)) { this.writeError(request,response,"Empty parameter on license callback (act)",Level.SEVERE); } else if ((fwd == null) || (fwd.length() == 0)) { this.writeError(request,response,"Empty parameter on license callback (fwd)",Level.SEVERE); } else { // get license id String lLicenseId = null; IntegrationContextFactory icf = new IntegrationContextFactory(); if (icf.isIntegrationEnabled()) { try { LOGGER.finer("Getting license id form licenseReference="+lLicenseReference); IntegrationContext ic = icf.newIntegrationContext(); lLicenseId = ic.getLicenseId(lLicenseReference); LOGGER.finer("License id="+lLicenseId); } catch (Exception e) { String msg = "Error getting license id."; LOGGER.log(Level.SEVERE,msg,e); this.writeError(request,response,msg+": "+e.toString(),null); return; } } // change wss to http auth url if ((lWssUrl != null) && (lWssUrl.length() > 0)) { if ((lLicenseId != null) && (lLicenseId.length() > 0)) { lWssUrl = lWssUrl.replace("/WSS","/httpauth/licid-" + lLicenseId); } if (!lWssUrl.endsWith("?")) { lWssUrl += "?"; } if ((wssUrlParams != null) && (wssUrlParams.length() > 0)) { lWssUrl += wssUrlParams; } } else { lWssUrl = ""; } LOGGER.finer("Licensed WSS endpoint: "+lWssUrl); // determine the redirection endpoint based upon the resource link action if ((lWssUrl != null) && (lWssUrl.length() > 0)) { if (act.equals("open")) { fwd = lWssUrl; } else if (act.equals("preview")) { fwd = this.replaceParam(fwd,"url",lWssUrl); } else if (act.equals("addToMap")) { if ((addToMapHint != null) && (addToMapHint.length() > 0)) { lWssUrl = addToMapHint+":"+lWssUrl; } fwd = this.replaceParam(fwd,"resource",lWssUrl); } else { fwd = lWssUrl; } } // send the redirect if ((fwd != null) && (fwd.length() > 0)) { LOGGER.finer("Redirecting to: "+fwd); response.sendRedirect(fwd); } } } /** * Gather the parameters of a target URL. * <br/>This won't work for any URL, we are expecting a URL constructed by a ResourceLinkBuilder. * @param url the URL * @return a map of the parameters * @throws UnsupportedEncodingException if UTF-8 is unsupported (should never happen) */ private Map<String,String> gatherParams(String url) throws UnsupportedEncodingException { Map<String,String> params = new HashMap<String,String>(); int idx = url.indexOf("?"); if (idx != -1) { String queryString = url.substring(idx+1); String[] pairs = queryString.split("&"); for (String pair: pairs) { idx = pair.indexOf("="); if (idx > 0) { String key = pair.substring(0,idx); String value = pair.substring(idx+1); value = URLDecoder.decode(value,"UTF-8"); params.put(key,value); } } } return params; } /** * Replaces a parameter value within a target URL. * <br/>This won't work for any URL, we are expecting a URL constructed by a ResourceLinkBuilder. * @param url the URL * @return the modified URL * @throws UnsupportedEncodingException if UTF-8 is unsupported (should never happen) */ private String replaceParam(String url, String paramName, String paramValue) throws UnsupportedEncodingException { String result = url; int idx = url.indexOf("?"); if (idx != -1) { boolean bFound = true; String path = url.substring(0,idx); String queryString = url.substring(idx+1); String[] pairs = queryString.split("&"); StringBuilder params = new StringBuilder(); for (String pair: pairs) { idx = pair.indexOf("="); if (idx > 0) { String key = pair.substring(0,idx); String value = pair.substring(idx+1); value = URLDecoder.decode(value,"UTF-8"); if (paramName.equals(key)) { value = paramValue; bFound = true; } if (params.length() > 0) params.append("&"); params.append(URLEncoder.encode(key,"UTF-8")); params.append("="); params.append(URLEncoder.encode(value,"UTF-8")); } } if (bFound) { result = path+"?"+params.toString(); } } return result; } /** * Writes characters to the response stream. * @param response the servlet response * @param content the content to write * @param charset the response character encoding * @param contentType the response content type * @throws IOException if an IO exception occurs */ private void writeCharacterResponse(HttpServletResponse response, String content, String charset, String contentType) throws IOException { PrintWriter writer = null; try { if (content.length() > 0) { response.setCharacterEncoding(charset); response.setContentType(contentType); writer = response.getWriter(); writer.write(content); writer.flush(); } } finally { try { if (writer != null) { writer.flush(); writer.close(); } } catch (Exception ef) { LOGGER.log(Level.SEVERE,"Error closing PrintWriter.",ef); } } } /** * Writes an error message to the response stream. * @param request the HTTP request * @param response the servlet response * @param message the error message * @param level if not null, the service side log file level * @throws IOException if an IO exception occurs */ private void writeError(HttpServletRequest request, HttpServletResponse response, String message, Level level) throws IOException { if (level != null) { LOGGER.log(level,message); } String fwd = request.getContextPath()+"/catalog/tc/error.page?error="+URLEncoder.encode(message,"UTF-8"); response.sendRedirect(fwd); /* String gptTitle = "Geoportal"; String pageTitle = "Error"; String css = request.getContextPath()+"/catalog/skins/themes/red/main.css"; StringBuilder sbHtml = new StringBuilder(); sbHtml.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">"); sbHtml.append("\r\n<html>"); sbHtml.append("\r\n<head>"); sbHtml.append("\r\n<title>").append(Val.escapeXmlForBrowser(pageTitle)).append("</title>"); sbHtml.append("\r\n<link rel=\"stylesheet\" type=\"text/css\" href=\""+Val.escapeXmlForBrowser(css)+"\"/>"); sbHtml.append("\r\n<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\"/>"); sbHtml.append("\r\n<meta name=\"robots\" content=\"noindex\"/>"); sbHtml.append("\r\n</head>"); sbHtml.append("\r\n<body>"); sbHtml.append("\r\n<div id=\"gptMainWrap\">"); sbHtml.append("\r\n <div id=\"gptBanner\">"); sbHtml.append("\r\n <div id=\"gptTitle\">").append(Val.escapeXmlForBrowser(gptTitle)).append("</div>"); sbHtml.append("\r\n </div>"); sbHtml.append("\r\n <p class=\"errorMessage\">").append(Val.escapeXmlForBrowser(message)).append("</p>"); sbHtml.append("\r\n</div>"); sbHtml.append("\r\n</body>"); sbHtml.append("\r\n</html>"); this.writeCharacterResponse(response,sbHtml.toString(),"UTF-8","text/html; charset=UTF-8"); this.writeCharacterResponse(response,message,"UTF-8","text/plain; charset=UTF-8"); */ } }