Skip to content

Commit

Permalink
Add code that over-rides process IWA credentials when user provides c…
Browse files Browse the repository at this point in the history
…redentials in the properties file.
  • Loading branch information
jdelgadillo committed Mar 14, 2016
1 parent 12560f5 commit 9b19fb5
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 44 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.esri.geoevent.datastore</groupId>
<artifactId>geoevent-datastore-proxy</artifactId>
<version>1.0</version>
<version>1.1</version>
<packaging>war</packaging>
<name>GeoEvent DataStore Proxy</name>
<description>
Expand Down
125 changes: 83 additions & 42 deletions src/main/java/com/esri/geoevent/datastore/GeoEventDataStoreProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
{
Expand All @@ -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<String, ServerInfo> serverInfos = new HashMap<>();
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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());
Expand All @@ -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)
{
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}

0 comments on commit 9b19fb5

Please sign in to comment.