diff --git a/README.md b/README.md index 800f992..85a40bd 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,8 @@ The configuration for portaliwaqa is for an ArcGIS Portal Server that uses Integ ``` From the JSON, we see that the token url is at https://portaliwaqa/gis/sharing/rest/generateToken. So the property portaliwaqa.tokenUrl=https://portaliwaqa/gis/sharing/rest/generateToken gets set. -For this configuration to work, the Tomcat process must be run as a user that has the appropriate access privileges to consume the services since we did not provide neither username nor password for the WebTier nor the GIS Tier. The proxy will impersonate the user that is running the Web Application process when communicating with the server in this case. If the user that is running the process cannot consume the services, then this configuration would need the portaliwaqa.username and portaliwaqa.password properties set to consume the services. IWA enabled Portals return a token as soon as you visit the generateToken endpoint, so there is no need to supply values for the properties portaliwaqa.gisTierUsername andportaliwaqa.gisTierPassword. +For this configuration to work, the Tomcat process must be run as a user that has the appropriate access privileges to consume the services since we did not provide neither username nor password for the WebTier nor the GIS Tier. The proxy will impersonate the user that is running the Web Application process when communicating with the server in this case. If the user that is running the process cannot consume the services, then this configuration would need the portaliwaqa.username and portaliwaqa.password properties set to consume the services. IWA enabled Portals return a token as soon as you visit the generateToken endpoint, so there is no need to supply values for the properties portaliwaqa.gisTierUsername andportaliwaqa.gisTierPassword. When configuring the username with a DOMAIN/username format, use the forward slash ```/``` or an escaped backslash ```\\``` otherwise the username will not be interpreted correctly. So the following 2 entries would be valid ```portaliwaqa.username=DOMAIN/user``` or ```portaliwaqa.username=DOMAIN\\user```. + ####Certificates If any of the sites you're connecting to use self-signed Https certificates, then you can export them using the browser and place them in the directory <TOMCAT>/webapps/geoevent-datastore-proxy/WEB-INF/classes/certificates with the ending of .crt, .cer, or .pem. Any certificate listed in the directory will be trusted by the proxy when making https connections. diff --git a/pom.xml b/pom.xml index 63b76b6..042099e 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ 4.0.0 com.esri.geoevent.datastore geoevent-datastore-proxy - 1.0 + 1.1 war GeoEvent DataStore Proxy diff --git a/src/main/java/com/esri/geoevent/datastore/GeoEventDataStoreProxy.java b/src/main/java/com/esri/geoevent/datastore/GeoEventDataStoreProxy.java index 410accc..e03e1fc 100644 --- a/src/main/java/com/esri/geoevent/datastore/GeoEventDataStoreProxy.java +++ b/src/main/java/com/esri/geoevent/datastore/GeoEventDataStoreProxy.java @@ -23,17 +23,73 @@ */ package com.esri.geoevent.datastore; +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.Response.Status; + import org.apache.commons.collections.CollectionUtils; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVRecord; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.cxf.jaxrs.ext.MessageContext; -import org.apache.http.*; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.NameValuePair; +import org.apache.http.StatusLine; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; +import org.apache.http.auth.NTCredentials; import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.methods.*; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.config.Registry; @@ -46,7 +102,11 @@ import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.auth.win.WindowsCredentialsProvider; -import org.apache.http.impl.client.*; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.client.SystemDefaultCredentialsProvider; +import org.apache.http.impl.client.WinHttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HttpContext; @@ -56,33 +116,6 @@ import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.node.ArrayNode; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.ResponseBuilder; -import javax.ws.rs.core.Response.Status; -import java.io.*; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.Charset; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; - @Path("/") public class GeoEventDataStoreProxy { @@ -107,12 +140,13 @@ public class GeoEventDataStoreProxy private static class ServerInfo { - URL url, tokenUrl; - AuthScope authscope; - Credentials credentials, ntCredentials; - HttpContext httpContext = null; - String encryptedToken, gisTierUsername, gisTierEncryptedPassword, name; - Long tokenExpiration; + URL url, tokenUrl; + AuthScope authscope; + Credentials credentials; + NTCredentials ntCredentials; + HttpContext httpContext = null; + String encryptedToken, gisTierUsername, gisTierEncryptedPassword, name; + Long tokenExpiration; } private Map serverInfos = new HashMap<>(); @@ -149,6 +183,7 @@ public GeoEventDataStoreProxy() password = props.getProperty(currServerName + ".password", ""); if (!StringUtils.isEmpty(username)) { + username = username.replace('\\', '/'); String encryptedPassword = Crypto.doEncrypt(password); currInfo.credentials = new UsernameEncryptedPasswordCredentials(username, encryptedPassword); currInfo.ntCredentials = new NTCredentialsEncryptedPassword(username + ":" + encryptedPassword); @@ -252,7 +287,7 @@ private String encodeParam(String param) throws UnsupportedEncodingException synchronized private void getTokenForServer(ServerInfo serverInfo, MessageContext context) throws IOException, URISyntaxException, GeneralSecurityException { ensureCertsAreLoaded(context); - try (CloseableHttpClient http = createHttpClient()) + try (CloseableHttpClient http = createHttpClient(serverInfo)) { String query = "f=json&username=" + encodeParam(serverInfo.gisTierUsername) + "&password=" + encodeParam((serverInfo.gisTierEncryptedPassword == null) ? "" : Crypto.doDecrypt(serverInfo.gisTierEncryptedPassword)) + "&client=requestip&expiration=60"; serverInfo.encryptedToken = null; @@ -423,7 +458,8 @@ private HttpContext createContextForServer(ServerInfo serverInfo) if (serverInfo.credentials != null || serverInfo.ntCredentials != null) { HttpClientContext context = HttpClientContext.create(); - CredentialsProvider credsProvider = (isWindows) ? new WindowsCredentialsProvider(new SystemDefaultCredentialsProvider()) : new SystemDefaultCredentialsProvider(); + CredentialsProvider credsProvider = (useBuiltinWindowsAuthentication(serverInfo)) ? credsProvider = new WindowsCredentialsProvider(new SystemDefaultCredentialsProvider()) : + new HttpClientCredentialsProvider(serverInfo.credentials, serverInfo.ntCredentials); if (serverInfo.credentials != null) { credsProvider.setCredentials(serverInfo.authscope, serverInfo.credentials); @@ -444,7 +480,7 @@ private Response execute(CloseableHttpClient http, ServerInfo serverInfo, HttpRe CloseableHttpResponse response = http.execute(request, serverInfo.httpContext); if (response == null) { - return Response.status(502).build(); + return Response.status(Response.Status.BAD_GATEWAY).build(); } Header[] responseHeaders = response.getAllHeaders(); ResponseBuilder builder = Response.status(response.getStatusLine().getStatusCode()); @@ -458,11 +494,16 @@ private Response execute(CloseableHttpClient http, ServerInfo serverInfo, HttpRe builder.entity(strReply.replaceAll(serverInfo.url.toExternalForm(), entireRequestUrl.substring(0, entireRequestUrl.indexOf(servletRequest.getPathInfo()))+"/"+serverInfo.name+"/")); return builder.build(); } + + private boolean useBuiltinWindowsAuthentication(ServerInfo serverInfo) + { + return isWindows && serverInfo.ntCredentials == null; + } - private CloseableHttpClient createHttpClient() + private CloseableHttpClient createHttpClient(ServerInfo serverInfo) { - HttpClientBuilder builder = (isWindows) ? WinHttpClients.custom() : HttpClients.custom(); + HttpClientBuilder builder = (useBuiltinWindowsAuthentication(serverInfo)) ? WinHttpClients.custom() : HttpClients.custom(); HttpClientConnectionManager connMgr = createConnectionManagerIfNecessary(); if (connMgr != null) { @@ -483,7 +524,7 @@ private void ensureCertsAreLoaded(MessageContext context) private Response execute(ServerInfo serverInfo, HttpRequestBase request, MessageContext context) throws IOException { ensureCertsAreLoaded(context); - try (CloseableHttpClient http = createHttpClient()) + try (CloseableHttpClient http = createHttpClient(serverInfo)) { return execute(http, serverInfo, request, context); } diff --git a/src/main/java/com/esri/geoevent/datastore/HttpClientCredentialsProvider.java b/src/main/java/com/esri/geoevent/datastore/HttpClientCredentialsProvider.java new file mode 100644 index 0000000..340e3fa --- /dev/null +++ b/src/main/java/com/esri/geoevent/datastore/HttpClientCredentialsProvider.java @@ -0,0 +1,49 @@ +package com.esri.geoevent.datastore; + +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.NTCredentials; +import org.apache.http.client.config.AuthSchemes; +import org.apache.http.impl.client.SystemDefaultCredentialsProvider; + +public class HttpClientCredentialsProvider extends SystemDefaultCredentialsProvider +{ + Credentials credentials; + NTCredentials ntCredentials; + + HttpClientCredentialsProvider(Credentials credentials, NTCredentials ntCredentials) + { + this.credentials = credentials; + this.ntCredentials = ntCredentials; + } + + @Override + public Credentials getCredentials(AuthScope authscope) + { + Credentials retCredentials = null; + String host = authscope.getHost(); + int port = authscope.getPort(); + if (host != AuthScope.ANY_HOST && port != AuthScope.ANY_PORT) + { + String scheme = authscope.getScheme().toUpperCase(); + switch (scheme) + { + case "BASIC": + case "DIGEST": + retCredentials = credentials; + break; + case AuthSchemes.NTLM: + retCredentials = ntCredentials; + break; + default: + break; + } + } + + if (retCredentials == null) + { + retCredentials = super.getCredentials(authscope); + } + return retCredentials; + } +}