diff --git a/README.md b/README.md index 96f8c1d..5351497 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,15 @@ If you already have an access token and endpoint (e.g. from a cookie), you can p ForceApi api = new ForceApi(c,s); +### Instantiate with proxy + + ForceApi api = new ForceApi(new ApiConfig() + .setProxyHost("127.0.0.1") + .setProxyPort(8080) + .setProxyUsername("proxy-user") + .setProxyPassword("proxy-password")); + + ## CRUD and Query Operations ### Get an SObject diff --git a/pom.xml b/pom.xml index bbac1fe..16fc353 100644 --- a/pom.xml +++ b/pom.xml @@ -176,8 +176,8 @@ maven-compiler-plugin 2.3.2 - 8 - 8 + 9 + 9 diff --git a/src/main/java/com/force/api/ApiConfig.java b/src/main/java/com/force/api/ApiConfig.java index be1a24b..014fdef 100644 --- a/src/main/java/com/force/api/ApiConfig.java +++ b/src/main/java/com/force/api/ApiConfig.java @@ -3,8 +3,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; -import java.net.URI; -import java.net.URLDecoder; +import java.net.*; public class ApiConfig { @@ -16,6 +15,10 @@ public class ApiConfig { String clientId; String clientSecret; String redirectURI; + String proxyHost; + Integer proxyPort; + String proxyUsername; + String proxyPassword; SessionRefreshListener sessionRefreshListener; ObjectMapper objectMapper; int requestTimeout = 0; // in milliseconds, defaults to 0 which is no timeout (infinity) @@ -35,7 +38,11 @@ public ApiConfig clone() { .setClientSecret(clientSecret) .setRedirectURI(redirectURI) .setObjectMapper(objectMapper) - .setRequestTimeout(requestTimeout); + .setRequestTimeout(requestTimeout) + .setProxyHost(proxyHost) + .setProxyPort(proxyPort) + .setProxyUsername(proxyUsername) + .setProxyPassword(proxyPassword); } public ApiConfig setForceURL(String url) { @@ -102,6 +109,37 @@ public ApiConfig setClientSecret(String value) { clientSecret = value; return this; } + + public ApiConfig setProxyHost(String value) { + proxyHost = value; + return this; + } + + public ApiConfig setProxyPort(String value) { + try { + proxyPort = Integer.parseInt(value.trim()); + } + catch(NullPointerException | NumberFormatException e){ + proxyPort = null; + } + return this; + } + + public ApiConfig setProxyPort(int value) { + proxyPort = value; + return this; + } + + public ApiConfig setProxyUsername(String value) { + proxyUsername = value; + return this; + } + + public ApiConfig setProxyPassword(String value) { + proxyPassword = value; + return this; + } + public ApiConfig setSessionRefreshListener(SessionRefreshListener value) { sessionRefreshListener = value; return this; @@ -147,6 +185,27 @@ public String getRedirectURI() { return redirectURI; } + public ProxySettings getProxySettings() { + ApiConfig config = this; + Proxy proxy = null; + Authenticator authenticator = null; + if(proxyHost != null && proxyPort != null) { + proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); + if(proxyUsername != null && proxyPassword != null) { + authenticator = new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + if (getRequestorType() == RequestorType.PROXY && config.proxyUsername != null && config.proxyPassword != null) { + return new PasswordAuthentication(config.proxyUsername, config.proxyPassword.toCharArray()); + } + return null; + } + }; + } + } + return new ProxySettings(proxy, authenticator); + } + public SessionRefreshListener getSessionRefreshListener() { return sessionRefreshListener; } public ObjectMapper getObjectMapper() { return objectMapper; } diff --git a/src/main/java/com/force/api/Auth.java b/src/main/java/com/force/api/Auth.java index 282d269..071749b 100644 --- a/src/main/java/com/force/api/Auth.java +++ b/src/main/java/com/force/api/Auth.java @@ -4,10 +4,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLEncoder; +import java.net.*; import java.util.Map; @@ -40,7 +37,8 @@ static public final ApiSession oauthLoginPasswordFlow(ApiConfig c) { .param("client_id",c.getClientId()) .param("client_secret", c.getClientSecret()) .param("username",c.getUsername()) - .param("password",c.getPassword()) + .param("password",c.getPassword()), + c.getProxySettings() ); if(r.getResponseCode()!=200) { throw new AuthException(r.getResponseCode(),"Auth.oauthLoginPasswordFlow failed: "+r.getString()); @@ -79,7 +77,12 @@ static public final ApiSession soaploginPasswordFlow(ApiConfig c) { if(c.getPassword()==null) throw new IllegalStateException("password cannot be null"); try { URL url = new URL(c.getLoginEndpoint()+"/services/Soap/u/33.0"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + Proxy proxy = c.getProxySettings().getProxy(); + Authenticator authenticator = c.getProxySettings().getProxyAuthenticator(); + HttpURLConnection conn = (HttpURLConnection) (proxy == null ? url.openConnection() : url.openConnection(proxy)); + if(proxy != null && authenticator != null) { + conn.setAuthenticator(authenticator); + } conn.setDoOutput(true); conn.addRequestProperty("Content-Type", "text/xml"); conn.addRequestProperty("SOAPAction", "login"); @@ -161,7 +164,8 @@ static public final ApiSession completeOAuthWebServerFlow(AuthorizationResponse .param("client_id",res.apiConfig.getClientId()) .param("client_secret", res.apiConfig.getClientSecret()) .param("redirect_uri",res.apiConfig.getRedirectURI()) - .preEncodedParam("code",res.code) + .preEncodedParam("code",res.code), + res.apiConfig.getProxySettings() ); if(r.getResponseCode()!=200) { throw new AuthException(r.getResponseCode(),r.getString()); @@ -191,7 +195,8 @@ static public final ApiSession refreshOauthTokenFlow(ApiConfig config, String re .param("grant_type","refresh_token") .param("client_id",config.getClientId()) .param("client_secret", config.getClientSecret()) - .param("refresh_token", refreshToken) + .param("refresh_token", refreshToken), + config.getProxySettings() ); if(r.getResponseCode()!=200) { throw new AuthException(r.getResponseCode(),r.getString()); @@ -222,7 +227,8 @@ static public void revokeToken(ApiConfig config, String token) { Http.send(HttpRequest.formPost() .header("Accept","*/*") .url(config.getLoginEndpoint()+"/services/oauth2/revoke") - .param("token", token)); + .param("token", token), + config.getProxySettings()); } catch(Throwable t) { // Looks like revoke endpoint closes stream when trying to revoke // an already revoked token. It doesn't return an error code. So diff --git a/src/main/java/com/force/api/ForceApi.java b/src/main/java/com/force/api/ForceApi.java index d9277bf..19d0194 100644 --- a/src/main/java/com/force/api/ForceApi.java +++ b/src/main/java/com/force/api/ForceApi.java @@ -68,11 +68,11 @@ public ForceApi(ApiSession session) { } public ForceApi(ApiConfig apiConfig) { + System.setProperty("jdk.http.auth.tunneling.disabledSchemes", ""); config = apiConfig; jsonMapper = config.getObjectMapper(); session = Auth.authenticate(apiConfig); autoRenew = true; - } public ApiSession getSession() { @@ -496,7 +496,7 @@ private final String uriBaseOrRoot() { private final HttpResponse apiRequest(HttpRequest req) { req.setAuthorization("Bearer "+session.getAccessToken()); req.setRequestTimeout(this.config.getRequestTimeout()); - HttpResponse res = Http.send(req); + HttpResponse res = Http.send(req, this.config.getProxySettings()); if(res.getResponseCode()==401) { // Perform one attempt to auto renew session if possible if (autoRenew) { @@ -510,7 +510,7 @@ private final HttpResponse apiRequest(HttpRequest req) { config.getSessionRefreshListener().sessionRefreshed(session); } req.setAuthorization("Bearer "+session.getAccessToken()); - res = Http.send(req); + res = Http.send(req, this.config.getProxySettings()); } } // 304 is a special case when the "If-Modified-Since" header is used, it is not an error, diff --git a/src/main/java/com/force/api/ProxySettings.java b/src/main/java/com/force/api/ProxySettings.java new file mode 100644 index 0000000..1704136 --- /dev/null +++ b/src/main/java/com/force/api/ProxySettings.java @@ -0,0 +1,22 @@ +package com.force.api; + +import java.net.Authenticator; +import java.net.Proxy; + +public class ProxySettings { + private final Proxy proxy; + private final Authenticator proxyAuthenticator; + + ProxySettings(Proxy proxy, Authenticator proxyAuthenticator) { + this.proxy = proxy; + this.proxyAuthenticator = proxyAuthenticator; + } + + public Proxy getProxy() { + return proxy; + } + + public Authenticator getProxyAuthenticator() { + return proxyAuthenticator; + } +} diff --git a/src/main/java/com/force/api/http/Http.java b/src/main/java/com/force/api/http/Http.java index fdc75ed..512c726 100644 --- a/src/main/java/com/force/api/http/Http.java +++ b/src/main/java/com/force/api/http/Http.java @@ -1,5 +1,6 @@ package com.force.api.http; +import com.force.api.ProxySettings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -8,11 +9,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; +import java.net.*; public class Http { @@ -60,9 +57,14 @@ static final byte[] readResponse(InputStream stream) throws IOException { return bout.toByteArray(); } - public static final HttpResponse send(HttpRequest req) { + public static final HttpResponse send(HttpRequest req, ProxySettings proxySettings) { try { - HttpURLConnection conn = (HttpURLConnection) new URL(req.getUrl()).openConnection(); + URL url = new URL(req.getUrl()); + Proxy proxy = proxySettings.getProxy(); + HttpURLConnection conn = (HttpURLConnection) (proxy == null ? url.openConnection() : url.openConnection(proxy)); + if(proxy != null && proxySettings.getProxyAuthenticator() != null) { + conn.setAuthenticator(proxySettings.getProxyAuthenticator()); + } if(req.getRequestTimeout()>0){ conn.setConnectTimeout(req.getRequestTimeout()); conn.setReadTimeout(req.getRequestTimeout());