Skip to content

Commit

Permalink
[EWT-191] Add support for custom proxy configurations with authentica…
Browse files Browse the repository at this point in the history
…tion (#202)
  • Loading branch information
dili91 authored May 4, 2023
1 parent f84b438 commit 0580d89
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 17 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Main properties
group=com.truelayer
archivesBaseName=truelayer-java
version=6.2.0
version=6.3.0

# Artifacts properties
sonatype_repository_url=https://s01.oss.sonatype.org/service/local/
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/truelayer/java/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static final class HeaderNames {
public static final String TL_SIGNATURE = "Tl-Signature";
public static final String TL_AGENT = "TL-Agent";
public static final String AUTHORIZATION = "Authorization";
public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
public static final String X_FORWARDED_FOR = "X-Forwarded-For";
public static final String COOKIE = "Cookie";
public static final String TL_CORRELATION_ID = "X-Tl-Correlation-Id";
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/com/truelayer/java/ProxyConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.truelayer.java;

import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
* Class that represent a custom proxy configuration
*/
@Builder
@Getter
@EqualsAndHashCode
@ToString
@Accessors(fluent = true)
public class ProxyConfiguration {

/**
* Hostname of the proxy
*/
String hostname;

/**
* Port of the proxy
*/
int port;

/**
* Optional credentials for authenticating proxy requests
*/
Credentials credentials;

@Builder
@Getter
@EqualsAndHashCode
@ToString
@Accessors(fluent = true)
public static class Credentials {
String username;

String password;
}
}
14 changes: 13 additions & 1 deletion src/main/java/com/truelayer/java/TrueLayerClientBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public class TrueLayerClientBuilder {

private ICredentialsCache credentialsCache;

private ProxyConfiguration proxyConfiguration;

TrueLayerClientBuilder() {}

/**
Expand Down Expand Up @@ -167,6 +169,16 @@ public TrueLayerClientBuilder withCredentialsCaching(ICredentialsCache credentia
return this;
}

/**
* Utility to configure a custom proxy, optionally including an authentication.
* @param proxyConfiguration the configuration describing the custom proxy
* @return the instance of the client builder used
*/
public TrueLayerClientBuilder withProxyConfiguration(ProxyConfiguration proxyConfiguration) {
this.proxyConfiguration = proxyConfiguration;
return this;
}

/**
* Builds the Java library main class to interact with TrueLayer APIs.
* @return a client instance
Expand All @@ -180,7 +192,7 @@ public TrueLayerClient build() {
OkHttpClientFactory httpClientFactory = new OkHttpClientFactory(new LibraryInfoLoader());

OkHttpClient baseHttpClient = httpClientFactory.buildBaseApiClient(
timeout, connectionPoolOptions, requestExecutor, logMessageConsumer);
timeout, connectionPoolOptions, requestExecutor, logMessageConsumer, proxyConfiguration);

OkHttpClient authHttpClient = httpClientFactory.buildAuthApiClient(baseHttpClient, clientCredentials);

Expand Down
32 changes: 24 additions & 8 deletions src/main/java/com/truelayer/java/http/OkHttpClientFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
import static org.apache.commons.lang3.ObjectUtils.isEmpty;
import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;

import com.truelayer.java.ClientCredentials;
import com.truelayer.java.ConnectionPoolOptions;
import com.truelayer.java.*;
import com.truelayer.java.ConnectionPoolOptions.KeepAliveDuration;
import com.truelayer.java.SigningOptions;
import com.truelayer.java.TrueLayerException;
import com.truelayer.java.auth.IAuthenticationHandler;
import com.truelayer.java.http.auth.AccessTokenInvalidator;
import com.truelayer.java.http.auth.AccessTokenManager;
Expand All @@ -19,13 +16,13 @@
import com.truelayer.java.http.interceptors.logging.HttpLoggingInterceptor;
import com.truelayer.java.http.interceptors.logging.SensitiveHeaderGuard;
import com.truelayer.java.versioninfo.LibraryInfoLoader;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import lombok.Value;
import okhttp3.ConnectionPool;
import okhttp3.Dispatcher;
import okhttp3.OkHttpClient;
import okhttp3.*;

@Value
public class OkHttpClientFactory {
Expand All @@ -41,7 +38,8 @@ public OkHttpClient buildBaseApiClient(
Duration timeout,
ConnectionPoolOptions connectionPoolOptions,
ExecutorService requestExecutor,
Consumer<String> logMessageConsumer) {
Consumer<String> logMessageConsumer,
ProxyConfiguration proxyConfiguration) {

OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();

Expand Down Expand Up @@ -76,6 +74,24 @@ public OkHttpClient buildBaseApiClient(
new HttpLoggingInterceptor(logMessageConsumer, new SensitiveHeaderGuard()));
}

if (isNotEmpty(proxyConfiguration)) {
clientBuilder.proxy(new Proxy(
Proxy.Type.HTTP, new InetSocketAddress(proxyConfiguration.hostname(), proxyConfiguration.port())));

if (isNotEmpty(proxyConfiguration.credentials())) {
clientBuilder.proxyAuthenticator((route, response) -> {
String credentials = Credentials.basic(
proxyConfiguration.credentials().username(),
proxyConfiguration.credentials().password());

return response.request()
.newBuilder()
.header("Proxy-Authorization", credentials)
.build();
});
}
}

clientBuilder.addInterceptor(new TrueLayerAgentInterceptor(libraryInfoLoader.load()));

return clientBuilder.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.truelayer.java;

import static com.truelayer.java.TestUtils.getClientCredentials;
import static com.truelayer.java.TestUtils.getSigningOptions;
import static com.truelayer.java.TestUtils.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.truelayer.java.http;

import static com.truelayer.java.Constants.HeaderNames.PROXY_AUTHORIZATION;
import static com.truelayer.java.TestUtils.getClientCredentials;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.truelayer.java.ConnectionPoolOptions;
import com.truelayer.java.ProxyConfiguration;
import com.truelayer.java.TestUtils;
import com.truelayer.java.auth.AuthenticationHandler;
import com.truelayer.java.auth.IAuthenticationHandler;
Expand All @@ -17,13 +19,15 @@
import com.truelayer.java.http.interceptors.logging.HttpLoggingInterceptor;
import com.truelayer.java.versioninfo.LibraryInfoLoader;
import com.truelayer.java.versioninfo.VersionInfo;
import java.net.Proxy;
import java.net.URI;
import java.time.Clock;
import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import okhttp3.OkHttpClient;
import lombok.SneakyThrows;
import okhttp3.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

Expand All @@ -39,7 +43,7 @@ public void shouldCreateABaseAuthApiClient() {

OkHttpClient baseApiClient = getOkHttpClientFactory()
.buildBaseApiClient(
null, ConnectionPoolOptions.builder().build(), customExecutor, customLogMessageConsumer);
null, ConnectionPoolOptions.builder().build(), customExecutor, customLogMessageConsumer, null);

assertNotNull(baseApiClient);
assertTrue(
Expand Down Expand Up @@ -78,7 +82,8 @@ public void shouldCreateABaseAuthApiClientWithCustomCallTimeout() {
customTimeout,
ConnectionPoolOptions.builder().build(),
customExecutor,
customLogMessageConsumer);
customLogMessageConsumer,
null);

assertNotNull(baseApiClient);
assertEquals(0, baseApiClient.connectTimeoutMillis(), "Unexpected connect timeout configured");
Expand All @@ -87,6 +92,80 @@ public void shouldCreateABaseAuthApiClientWithCustomCallTimeout() {
assertEquals(customTimeout.toMillis(), baseApiClient.callTimeoutMillis(), "Unexpected call timeout configured");
}

@SneakyThrows
@Test
@DisplayName("It should build a Base API client with custom unauthenticated proxy configuration")
public void shouldCreateABaseAuthApiClientWithCustomProxyConfigNoAuthentication() {
ProxyConfiguration customProxyConfig =
ProxyConfiguration.builder().hostname("127.0.0.1").port(9999).build();

OkHttpClient baseApiClient = getOkHttpClientFactory()
.buildBaseApiClient(null, ConnectionPoolOptions.builder().build(), null, null, customProxyConfig);

assertNotNull(baseApiClient);
Proxy configuredProxy = baseApiClient.proxy();
assertNotNull(configuredProxy, "proxy not configured");
assertEquals(Proxy.Type.HTTP, configuredProxy.type(), "unexpected proxy type configured");
assertTrue(
configuredProxy
.address()
.toString()
.endsWith(customProxyConfig.hostname() + ":" + customProxyConfig.port()),
"unexpected proxy address found");
Authenticator proxyAuthenticator = baseApiClient.proxyAuthenticator();
// make sure the authenticator is the default used by OkHttp.
assertInstanceOf(Authenticator.NONE.getClass(), proxyAuthenticator);
}

@SneakyThrows
@Test
@DisplayName("It should build a Base API client with custom proxy configuration with authentication")
public void shouldCreateABaseAuthApiClientWithCustomProxyConfigWithAuthentication() {
ProxyConfiguration customProxyConfig = ProxyConfiguration.builder()
.hostname("127.0.0.1")
.port(9999)
.credentials(ProxyConfiguration.Credentials.builder()
.username("john")
.password("doe")
.build())
.build();

OkHttpClient baseApiClient = getOkHttpClientFactory()
.buildBaseApiClient(null, ConnectionPoolOptions.builder().build(), null, null, customProxyConfig);

assertNotNull(baseApiClient);
Proxy configuredProxy = baseApiClient.proxy();
assertNotNull(configuredProxy, "proxy not configured");
assertEquals(Proxy.Type.HTTP, configuredProxy.type(), "unexpected proxy type configured");
assertTrue(
configuredProxy
.address()
.toString()
.endsWith(customProxyConfig.hostname() + ":" + customProxyConfig.port()),
"unexpected proxy address found");
Authenticator proxyAuthenticator = baseApiClient.proxyAuthenticator();
assertNotNull(proxyAuthenticator, "proxy authenticator not configured");
Request testRequest = proxyAuthenticator.authenticate(
null,
new Response.Builder()
.protocol(Protocol.HTTP_2)
.code(200)
.message("A response")
.request(new Request.Builder()
.url("https://localhost/test")
.get()
.build())
.build());
assertNotNull(testRequest, "invalid request generated");
String expectedProxyAuthHeader = Credentials.basic(
customProxyConfig.credentials().username(),
customProxyConfig.credentials().password());
assertEquals(
expectedProxyAuthHeader,
testRequest.header(PROXY_AUTHORIZATION),
"unexpected Proxy-Authentication header found");
}

@Test
@DisplayName("It should build an Auth API client")
public void shouldCreateAnAuthApiClient() {
Expand All @@ -99,7 +178,8 @@ public void shouldCreateAnAuthApiClient() {
customTimeout,
ConnectionPoolOptions.builder().build(),
customExecutor,
customLogMessageConsumer);
customLogMessageConsumer,
null);

OkHttpClient authClient = getOkHttpClientFactory().buildAuthApiClient(baseApiClient, getClientCredentials());

Expand All @@ -121,7 +201,7 @@ public void shouldCreateAnAuthApiClient() {
@Test
@DisplayName("It should build a Payments API client")
public void shouldThrowCredentialsMissingException() {
OkHttpClient baseHttpClient = getOkHttpClientFactory().buildBaseApiClient(null, null, null, null);
OkHttpClient baseHttpClient = getOkHttpClientFactory().buildBaseApiClient(null, null, null, null, null);
OkHttpClient authClient = getOkHttpClientFactory().buildAuthApiClient(baseHttpClient, getClientCredentials());

IAuthenticationHandler authenticationHandler = AuthenticationHandler.New()
Expand Down

0 comments on commit 0580d89

Please sign in to comment.