From d6de7033b4bfd3b07621d9472904aa39ee09b442 Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Thu, 19 Oct 2023 17:56:54 +0100 Subject: [PATCH 01/16] feat: added proxy and ssl for well known uri --- build.gradle | 1 + .../authentication/ConfidentialClient.java | 69 +++++++++++++++++++ .../utils/authentication/RequestOptions.java | 20 ++++++ 3 files changed, 90 insertions(+) create mode 100644 src/main/java/com/factset/sdk/utils/authentication/RequestOptions.java diff --git a/build.gradle b/build.gradle index 142e729..da6c291 100644 --- a/build.gradle +++ b/build.gradle @@ -17,6 +17,7 @@ dependencies { implementation 'org.json:json:20231013' implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'com.nimbusds:oauth2-oidc-sdk:10.14.2' + implementation 'org.projectlombok:lombok:1.18.28' testImplementation 'org.mockito:mockito-core:4.9.0' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' diff --git a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java index 4ad490b..6c1e737 100644 --- a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java +++ b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java @@ -26,6 +26,10 @@ import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import java.io.IOException; import java.io.InputStream; +import java.net.Proxy; +import java.net.HttpURLConnection; +import java.net.URL; +import javax.net.ssl.HttpsURLConnection; import java.util.Date; import java.util.List; import java.util.Objects; @@ -43,6 +47,7 @@ public class ConfidentialClient implements OAuth2Client { private static final Logger LOGGER = LoggerFactory.getLogger(ConfidentialClient.class); private final Configuration config; private final OIDCProviderMetadata providerMetadata; + private RequestOptions requestOptions; private TokenRequestBuilder tokenRequestBuilder; private long jwsIssuedAt; private long accessTokenExpireTime; @@ -65,6 +70,24 @@ public ConfidentialClient(final String configPath) this(new Configuration(configPath)); } + /** + * Creates a new ConfidentialClient. When setting up the OAuth 2.0 client, this constructor reaches out to + * FactSet's well-known URI to retrieve metadata about its authorization server. This information along with + * information about the OAuth 2.0 client is stored and used whenever a new access token is fetched. + * + * @param configPath The path towards the file to pe parsed. + * @param requestOptions Object that can configure options like proxy and SSL settings + * @throws AuthServerMetadataContentException If Meta Issuer or Meta Token Endpoint is missing. + * @throws AuthServerMetadataException If reading from URL is unsuccessful. + * @throws ConfigurationException If JWK required keys are missing from the RSA or any keys with a value + * that is null or an empty string. + */ + public ConfidentialClient(final String configPath, RequestOptions requestOptions) + throws AuthServerMetadataContentException, AuthServerMetadataException, + ConfigurationException { + this(new Configuration(configPath), requestOptions); + } + /** * Creates a new ConfidentialClient. When setting up the OAuth 2.0 client, this constructor reaches out to * FactSet's well-known URI to retrieve metadata about its authorization server. This information along with @@ -98,6 +121,52 @@ public ConfidentialClient(final Configuration config) new TokenRequestBuilder().uri(this.providerMetadata.getTokenEndpointURI()); } + /** + * Creates a new ConfidentialClient. When setting up the OAuth 2.0 client, this constructor reaches out to + * FactSet's well-known URI to retrieve metadata about its authorization server. This information along with + * information about the OAuth 2.0 client is stored and used whenever a new access token is fetched. + * + * @param config Configuration object. + * @param requestOptions Object that can configure options like proxy and SSL settings + * @throws AuthServerMetadataContentException If Meta Issuer or Meta Token Endpoint is missing. + * @throws AuthServerMetadataException If reading from URL is unsuccessful. + * @throws NullPointerException Unchecked exception, if config is null. + */ + public ConfidentialClient(final Configuration config, RequestOptions requestOptions) + throws AuthServerMetadataContentException, AuthServerMetadataException { + Objects.requireNonNull(config, "Configuration object must not be null"); + this.config = config; + LOGGER.debug("Finished initialising configuration"); + + LOGGER.debug("Attempting to get response from Well Known URI"); + URL wellKnownURL = config.getWellKnownUrl(); + + try { + HttpURLConnection conn = (HttpURLConnection) wellKnownURL.openConnection(requestOptions.getProxy()); + HttpsURLConnection sslConn = null; + if (conn instanceof HttpsURLConnection) { + sslConn = (HttpsURLConnection)conn; + sslConn.setHostnameVerifier(requestOptions.getHostnameVerifier()); + sslConn.setSSLSocketFactory(requestOptions.getSslSocketFactory()); + } + + InputStream stream = conn.getInputStream(); + + final String providerInfo = IOUtils.readInputStreamToString(stream); + this.providerMetadata = OIDCProviderMetadata.parse(providerInfo); + } catch (final ParseException e) { + throw new AuthServerMetadataContentException("Content of WellKnownUri has errors: " + + config.getWellKnownUrl().toString(), e); + } catch (final IOException e) { + throw new AuthServerMetadataException("Error retrieving contents from WellKnownUri: " + + config.getWellKnownUrl().toString(), e); + } + LOGGER.debug("Response received from Well Known URI"); + + this.tokenRequestBuilder = + new TokenRequestBuilder().uri(this.providerMetadata.getTokenEndpointURI()); + } + /** * Creates a new ConfidentialClient. When setting up the OAuth 2.0 client, this constructor reaches out to * FactSet's well-known URI to retrieve metadata about its authorization server. This information along with diff --git a/src/main/java/com/factset/sdk/utils/authentication/RequestOptions.java b/src/main/java/com/factset/sdk/utils/authentication/RequestOptions.java new file mode 100644 index 0000000..e6b01fd --- /dev/null +++ b/src/main/java/com/factset/sdk/utils/authentication/RequestOptions.java @@ -0,0 +1,20 @@ +package com.factset.sdk.utils.authentication; + +import lombok.Builder; +import lombok.Value; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; +import java.net.Proxy; + +@Value +@Builder +public class RequestOptions { + @Builder.Default + Proxy proxy = Proxy.NO_PROXY; + @Builder.Default + HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); + @Builder.Default + SSLSocketFactory sslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); +} From 95b97ea7cee5f7a027a564979a2d300efcc29e0c Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Fri, 20 Oct 2023 10:39:41 +0100 Subject: [PATCH 02/16] chore: modify build gradle --- build.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index da6c291..79409d3 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,6 @@ dependencies { implementation 'org.json:json:20231013' implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'com.nimbusds:oauth2-oidc-sdk:10.14.2' - implementation 'org.projectlombok:lombok:1.18.28' testImplementation 'org.mockito:mockito-core:4.9.0' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' @@ -27,6 +26,11 @@ dependencies { testImplementation "org.hamcrest:hamcrest:2.2" testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.10.0' testRuntimeOnly 'ch.qos.logback:logback-core:1.2.11' + + compileOnly 'org.projectlombok:lombok:1.18.30' + annotationProcessor 'org.projectlombok:lombok:1.18.30' + testCompileOnly 'org.projectlombok:lombok:1.18.30' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.30' } task sourcesJar(type: Jar) { From 4a4455cd6c477b2c7d1e598cd1a78e23e3e8ab3d Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Fri, 20 Oct 2023 11:30:14 +0100 Subject: [PATCH 03/16] refactor: extracted constructor code to method --- .../authentication/ConfidentialClient.java | 81 +++++++++---------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java index 6c1e737..d02c887 100644 --- a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java +++ b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java @@ -26,7 +26,6 @@ import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; import java.io.IOException; import java.io.InputStream; -import java.net.Proxy; import java.net.HttpURLConnection; import java.net.URL; import javax.net.ssl.HttpsURLConnection; @@ -46,7 +45,7 @@ public class ConfidentialClient implements OAuth2Client { private static final Logger LOGGER = LoggerFactory.getLogger(ConfidentialClient.class); private final Configuration config; - private final OIDCProviderMetadata providerMetadata; + private OIDCProviderMetadata providerMetadata; private RequestOptions requestOptions; private TokenRequestBuilder tokenRequestBuilder; private long jwsIssuedAt; @@ -104,21 +103,7 @@ public ConfidentialClient(final Configuration config) this.config = config; LOGGER.debug("Finished initialising configuration"); - LOGGER.debug("Attempting to get response from Well Known URI"); - try (InputStream stream = config.getWellKnownUrl().openStream()) { - final String providerInfo = IOUtils.readInputStreamToString(stream); - this.providerMetadata = OIDCProviderMetadata.parse(providerInfo); - } catch (final ParseException e) { - throw new AuthServerMetadataContentException("Content of WellKnownUri has errors: " + - config.getWellKnownUrl().toString(), e); - } catch (final IOException e) { - throw new AuthServerMetadataException("Error retrieving contents from WellKnownUri: " + - config.getWellKnownUrl().toString(), e); - } - LOGGER.debug("Response received from Well Known URI"); - - this.tokenRequestBuilder = - new TokenRequestBuilder().uri(this.providerMetadata.getTokenEndpointURI()); + this.requestProviderMetadata(config, null); } /** @@ -138,33 +123,7 @@ public ConfidentialClient(final Configuration config, RequestOptions requestOpti this.config = config; LOGGER.debug("Finished initialising configuration"); - LOGGER.debug("Attempting to get response from Well Known URI"); - URL wellKnownURL = config.getWellKnownUrl(); - - try { - HttpURLConnection conn = (HttpURLConnection) wellKnownURL.openConnection(requestOptions.getProxy()); - HttpsURLConnection sslConn = null; - if (conn instanceof HttpsURLConnection) { - sslConn = (HttpsURLConnection)conn; - sslConn.setHostnameVerifier(requestOptions.getHostnameVerifier()); - sslConn.setSSLSocketFactory(requestOptions.getSslSocketFactory()); - } - - InputStream stream = conn.getInputStream(); - - final String providerInfo = IOUtils.readInputStreamToString(stream); - this.providerMetadata = OIDCProviderMetadata.parse(providerInfo); - } catch (final ParseException e) { - throw new AuthServerMetadataContentException("Content of WellKnownUri has errors: " + - config.getWellKnownUrl().toString(), e); - } catch (final IOException e) { - throw new AuthServerMetadataException("Error retrieving contents from WellKnownUri: " + - config.getWellKnownUrl().toString(), e); - } - LOGGER.debug("Response received from Well Known URI"); - - this.tokenRequestBuilder = - new TokenRequestBuilder().uri(this.providerMetadata.getTokenEndpointURI()); + this.requestProviderMetadata(config, requestOptions); } /** @@ -225,6 +184,40 @@ public String getAccessToken() throws AccessTokenException, SigningJwsException return this.fetchAccessToken(); } + private void requestProviderMetadata(Configuration config, RequestOptions requestOptions) throws AuthServerMetadataContentException, AuthServerMetadataException { + LOGGER.debug("Attempting to get response from Well Known URI"); + URL wellKnownURL = config.getWellKnownUrl(); + InputStream stream; + + try { + if (requestOptions == null) stream = wellKnownURL.openStream(); + else { + HttpURLConnection conn = (HttpURLConnection) wellKnownURL.openConnection(requestOptions.getProxy()); + HttpsURLConnection sslConn = null; + if (conn instanceof HttpsURLConnection) { + sslConn = (HttpsURLConnection) conn; + sslConn.setHostnameVerifier(requestOptions.getHostnameVerifier()); + sslConn.setSSLSocketFactory(requestOptions.getSslSocketFactory()); + } + + stream = conn.getInputStream(); + } + + final String providerInfo = IOUtils.readInputStreamToString(stream); + this.providerMetadata = OIDCProviderMetadata.parse(providerInfo); + } catch (final ParseException e) { + throw new AuthServerMetadataContentException("Content of WellKnownUri has errors: " + + config.getWellKnownUrl().toString(), e); + } catch (final IOException e) { + throw new AuthServerMetadataException("Error retrieving contents from WellKnownUri: " + + config.getWellKnownUrl().toString(), e); + } + LOGGER.debug("Response received from Well Known URI"); + + this.tokenRequestBuilder = + new TokenRequestBuilder().uri(this.providerMetadata.getTokenEndpointURI()); + } + private boolean isCachedTokenValid() { if (this.accessToken == null) { return false; From 14d8a30993852f6e7669647ed5e37afaded477c6 Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Fri, 20 Oct 2023 11:35:46 +0100 Subject: [PATCH 04/16] refactor: further refactored constructor --- .../authentication/ConfidentialClient.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java index d02c887..0e76cc8 100644 --- a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java +++ b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java @@ -102,8 +102,9 @@ public ConfidentialClient(final Configuration config) Objects.requireNonNull(config, "Configuration object must not be null"); this.config = config; LOGGER.debug("Finished initialising configuration"); + this.requestOptions = null; - this.requestProviderMetadata(config, null); + this.requestProviderMetadata(); } /** @@ -122,8 +123,9 @@ public ConfidentialClient(final Configuration config, RequestOptions requestOpti Objects.requireNonNull(config, "Configuration object must not be null"); this.config = config; LOGGER.debug("Finished initialising configuration"); + this.requestOptions = requestOptions; - this.requestProviderMetadata(config, requestOptions); + this.requestProviderMetadata(); } /** @@ -184,20 +186,20 @@ public String getAccessToken() throws AccessTokenException, SigningJwsException return this.fetchAccessToken(); } - private void requestProviderMetadata(Configuration config, RequestOptions requestOptions) throws AuthServerMetadataContentException, AuthServerMetadataException { + private void requestProviderMetadata() throws AuthServerMetadataContentException, AuthServerMetadataException { LOGGER.debug("Attempting to get response from Well Known URI"); - URL wellKnownURL = config.getWellKnownUrl(); + URL wellKnownURL = this.config.getWellKnownUrl(); InputStream stream; try { - if (requestOptions == null) stream = wellKnownURL.openStream(); + if (this.requestOptions == null) stream = wellKnownURL.openStream(); else { - HttpURLConnection conn = (HttpURLConnection) wellKnownURL.openConnection(requestOptions.getProxy()); + HttpURLConnection conn = (HttpURLConnection) wellKnownURL.openConnection(this.requestOptions.getProxy()); HttpsURLConnection sslConn = null; if (conn instanceof HttpsURLConnection) { sslConn = (HttpsURLConnection) conn; - sslConn.setHostnameVerifier(requestOptions.getHostnameVerifier()); - sslConn.setSSLSocketFactory(requestOptions.getSslSocketFactory()); + sslConn.setHostnameVerifier(this.requestOptions.getHostnameVerifier()); + sslConn.setSSLSocketFactory(this.requestOptions.getSslSocketFactory()); } stream = conn.getInputStream(); @@ -207,10 +209,10 @@ private void requestProviderMetadata(Configuration config, RequestOptions reques this.providerMetadata = OIDCProviderMetadata.parse(providerInfo); } catch (final ParseException e) { throw new AuthServerMetadataContentException("Content of WellKnownUri has errors: " + - config.getWellKnownUrl().toString(), e); + this.config.getWellKnownUrl().toString(), e); } catch (final IOException e) { throw new AuthServerMetadataException("Error retrieving contents from WellKnownUri: " + - config.getWellKnownUrl().toString(), e); + this.config.getWellKnownUrl().toString(), e); } LOGGER.debug("Response received from Well Known URI"); From d683bb422bed900d7999e683fc9fc5f6f8790460 Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Fri, 20 Oct 2023 11:36:43 +0100 Subject: [PATCH 05/16] refactor: remove redundant code --- .../factset/sdk/utils/authentication/ConfidentialClient.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java index 0e76cc8..8818fd8 100644 --- a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java +++ b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java @@ -195,9 +195,8 @@ private void requestProviderMetadata() throws AuthServerMetadataContentException if (this.requestOptions == null) stream = wellKnownURL.openStream(); else { HttpURLConnection conn = (HttpURLConnection) wellKnownURL.openConnection(this.requestOptions.getProxy()); - HttpsURLConnection sslConn = null; if (conn instanceof HttpsURLConnection) { - sslConn = (HttpsURLConnection) conn; + HttpsURLConnection sslConn = (HttpsURLConnection) conn; sslConn.setHostnameVerifier(this.requestOptions.getHostnameVerifier()); sslConn.setSSLSocketFactory(this.requestOptions.getSslSocketFactory()); } From 42dd4c6a2aed501690e46391e81a0b2c0ab1ba9e Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Fri, 20 Oct 2023 11:49:20 +0100 Subject: [PATCH 06/16] feat: added proxy and ssl support to token request --- .../sdk/utils/authentication/ConfidentialClient.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java index 8818fd8..09e9186 100644 --- a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java +++ b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java @@ -46,7 +46,7 @@ public class ConfidentialClient implements OAuth2Client { private static final Logger LOGGER = LoggerFactory.getLogger(ConfidentialClient.class); private final Configuration config; private OIDCProviderMetadata providerMetadata; - private RequestOptions requestOptions; + private final RequestOptions requestOptions; private TokenRequestBuilder tokenRequestBuilder; private long jwsIssuedAt; private long accessTokenExpireTime; @@ -236,6 +236,11 @@ private String fetchAccessToken() throws AccessTokenException, SigningJwsExcepti final TokenRequest tokenRequest = this.tokenRequestBuilder.signedJwt(signedJwt).build(); final HTTPRequest httpRequest = tokenRequest.toHTTPRequest(); + if (requestOptions != null) { + httpRequest.setProxy(this.requestOptions.getProxy()); + httpRequest.setHostnameVerifier(this.requestOptions.getHostnameVerifier()); + httpRequest.setSSLSocketFactory(this.requestOptions.getSslSocketFactory()); + } logTokenRequest(httpRequest); final HTTPResponse res = httpRequest.send(); From 3518c9bd46518484af55b6b52481b53d2b7cabb5 Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Fri, 20 Oct 2023 14:46:18 +0100 Subject: [PATCH 07/16] refactor: use default request object instead of null --- .../authentication/ConfidentialClient.java | 28 ++++++++----------- .../ConfidentialClientTest.java | 12 ++++---- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java index 09e9186..5984d03 100644 --- a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java +++ b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java @@ -102,7 +102,7 @@ public ConfidentialClient(final Configuration config) Objects.requireNonNull(config, "Configuration object must not be null"); this.config = config; LOGGER.debug("Finished initialising configuration"); - this.requestOptions = null; + this.requestOptions = new RequestOptions.RequestOptionsBuilder().build(); this.requestProviderMetadata(); } @@ -192,18 +192,15 @@ private void requestProviderMetadata() throws AuthServerMetadataContentException InputStream stream; try { - if (this.requestOptions == null) stream = wellKnownURL.openStream(); - else { - HttpURLConnection conn = (HttpURLConnection) wellKnownURL.openConnection(this.requestOptions.getProxy()); - if (conn instanceof HttpsURLConnection) { - HttpsURLConnection sslConn = (HttpsURLConnection) conn; - sslConn.setHostnameVerifier(this.requestOptions.getHostnameVerifier()); - sslConn.setSSLSocketFactory(this.requestOptions.getSslSocketFactory()); - } - - stream = conn.getInputStream(); + HttpURLConnection conn = (HttpURLConnection) wellKnownURL.openConnection(this.requestOptions.getProxy()); + if (conn instanceof HttpsURLConnection) { + HttpsURLConnection sslConn = (HttpsURLConnection) conn; + sslConn.setHostnameVerifier(this.requestOptions.getHostnameVerifier()); + sslConn.setSSLSocketFactory(this.requestOptions.getSslSocketFactory()); } + stream = conn.getInputStream(); + final String providerInfo = IOUtils.readInputStreamToString(stream); this.providerMetadata = OIDCProviderMetadata.parse(providerInfo); } catch (final ParseException e) { @@ -236,11 +233,10 @@ private String fetchAccessToken() throws AccessTokenException, SigningJwsExcepti final TokenRequest tokenRequest = this.tokenRequestBuilder.signedJwt(signedJwt).build(); final HTTPRequest httpRequest = tokenRequest.toHTTPRequest(); - if (requestOptions != null) { - httpRequest.setProxy(this.requestOptions.getProxy()); - httpRequest.setHostnameVerifier(this.requestOptions.getHostnameVerifier()); - httpRequest.setSSLSocketFactory(this.requestOptions.getSslSocketFactory()); - } + httpRequest.setProxy(this.requestOptions.getProxy()); + httpRequest.setHostnameVerifier(this.requestOptions.getHostnameVerifier()); + httpRequest.setSSLSocketFactory(this.requestOptions.getSslSocketFactory()); + logTokenRequest(httpRequest); final HTTPResponse res = httpRequest.send(); diff --git a/src/test/java/com/factset/sdk/utils/authentication/ConfidentialClientTest.java b/src/test/java/com/factset/sdk/utils/authentication/ConfidentialClientTest.java index 19d4efb..0b16a3f 100644 --- a/src/test/java/com/factset/sdk/utils/authentication/ConfidentialClientTest.java +++ b/src/test/java/com/factset/sdk/utils/authentication/ConfidentialClientTest.java @@ -9,11 +9,9 @@ import org.junit.jupiter.api.Test; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; +import java.net.*; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; @@ -327,7 +325,9 @@ private static URL getUrlMockResponse(String stringFile) throws IOException { final File file = new File(String.valueOf(Paths.get(String.valueOf(pathToResources), stringFile))); URL mockedUrl = mock(URL.class); - when(mockedUrl.openStream()).thenReturn(new FileInputStream(file)); + HttpURLConnection mockedConn = mock(HttpURLConnection.class); + when(mockedUrl.openConnection(any(Proxy.class))).thenReturn(mockedConn); + when(mockedConn.getInputStream()).thenReturn(Files.newInputStream(file.toPath())); return mockedUrl; } @@ -343,7 +343,7 @@ private static Configuration getConfigSpyMockedResponse(String urlResponse, Stri private static Configuration getConfigSpyThrowsIOException(String configFile) throws IOException, ConfigurationException { URL mockedUrl = mock(URL.class); - when(mockedUrl.openStream()).thenThrow(IOException.class); + when(mockedUrl.openConnection(any(Proxy.class))).thenThrow(IOException.class); Configuration configuration = new Configuration(String.valueOf(Paths.get(pathToResources.toString(), configFile))); Configuration configurationSpy = spy(configuration); when(configurationSpy.getWellKnownUrl()).thenReturn(mockedUrl).thenCallRealMethod(); From 701c217fdbe1ec2bc77bc5cd3b8955440dbdb277 Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Mon, 23 Oct 2023 13:28:30 +0100 Subject: [PATCH 08/16] test: proxy and ssl configuration --- .../authentication/ConfidentialClient.java | 19 ++++ .../ConfidentialClientTest.java | 101 ++++++++++++++---- 2 files changed, 99 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java index 5984d03..80c7f64 100644 --- a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java +++ b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java @@ -166,6 +166,25 @@ protected ConfidentialClient(final Configuration config, final TokenRequestBuild this.tokenRequestBuilder = tokReqBuilder.uri(this.providerMetadata.getTokenEndpointURI()); } + /** + * Creates a new ConfidentialClient. When setting up the OAuth 2.0 client, this constructor reaches out to + * FactSet's well-known URI to retrieve metadata about its authorization server. This information along with + * information about the OAuth 2.0 client is stored and used whenever a new access token is fetched. + * + * @param config Configuration object. + * @param tokReqBuilder The TokenRequest builder, used to build custom TokenRequest instances. + * @param requestOptions Object that can configure options like proxy and SSL settings + * @throws AuthServerMetadataContentException If Meta Issuer or Meta Token Endpoint is missing. + * @throws AuthServerMetadataException If reading from URL is unsuccessful. + * @throws NullPointerException Unchecked exception, if config is null. + */ + protected ConfidentialClient(final Configuration config, final TokenRequestBuilder tokReqBuilder, RequestOptions requestOptions) + throws AuthServerMetadataContentException, + AuthServerMetadataException { + this(config, requestOptions); + this.tokenRequestBuilder = tokReqBuilder.uri(this.providerMetadata.getTokenEndpointURI()); + } + /** * Returns an access token that can be used for authentication. If the cache contains a valid access token, * it's returned. Otherwise, a new access token is retrieved from FactSet's authorization server. The access diff --git a/src/test/java/com/factset/sdk/utils/authentication/ConfidentialClientTest.java b/src/test/java/com/factset/sdk/utils/authentication/ConfidentialClientTest.java index 0b16a3f..22f40f2 100644 --- a/src/test/java/com/factset/sdk/utils/authentication/ConfidentialClientTest.java +++ b/src/test/java/com/factset/sdk/utils/authentication/ConfidentialClientTest.java @@ -8,6 +8,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import javax.net.ssl.HttpsURLConnection; import java.io.File; import java.io.IOException; import java.net.*; @@ -149,9 +150,11 @@ void confidentialClientValidPathValidConfigCannotGetInputStreamThrowsAuthServerM } @Test - void confidentialClientValidPathValidConfigMissingIssuerAndTokenEndpointThrowsAuthServerMetadataContentException() { + void confidentialClientValidPathValidConfigMissingIssuerAndTokenEndpointThrowsAuthServerMetadataContentException() throws Exception { + HttpURLConnection mockedConn = mock(HttpURLConnection.class); + URL mockedURL = getUrlMockResponse("emptyJson.txt", mockedConn); assertThrows(AuthServerMetadataContentException.class, - () -> new ConfidentialClient(getConfigSpyMockedResponse("emptyJson.txt", "validConfig.txt"))); + () -> new ConfidentialClient(getConfigSpyMockedResponse(mockedURL, "validConfig.txt"))); } @Test @@ -163,7 +166,8 @@ void confidentialClientValidPathValidConfigCustomWellKnownUriInitialisesWithNoEx "https://test.test.com/.test-test/test-test"); // If this confidential client is instantiated without exceptions, that results in a passing test. - URL mockedURL = getUrlMockResponse("exampleResponseWellKnownUri.txt"); + HttpURLConnection mockedConn = mock(HttpURLConnection.class); + URL mockedURL = getUrlMockResponse("exampleResponseWellKnownUri.txt", mockedConn); Configuration configurationSpy = spy(configuration); when(configurationSpy.getWellKnownUrl()).thenReturn(mockedURL); new ConfidentialClient(configurationSpy); @@ -174,10 +178,27 @@ void confidentialClientValidPathValidConfigCustomWellKnownUriInitialisesWithNoEx void confidentialClientValidConfigInitialisesWithNoException() { assertDoesNotThrow(() -> { // If this confidential client is instantiated without exceptions, that results in a passing test. - new ConfidentialClient(getConfigSpyMockedResponse("exampleResponseWellKnownUri.txt", "validConfig.txt")); + HttpURLConnection mockedConn = mock(HttpURLConnection.class); + URL mockedURL = getUrlMockResponse("exampleResponseWellKnownUri.txt", mockedConn); + new ConfidentialClient(getConfigSpyMockedResponse(mockedURL, "validConfig.txt")); }); } + @Test + void confidentialClientValidConfigInitialisesWithRequestOptions() throws Exception { + Proxy mockProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080)); + RequestOptions reqOptions = RequestOptions.builder().proxy(mockProxy).build(); + + HttpsURLConnection mockedConn = mock(HttpsURLConnection.class); + URL mockedURL = getUrlMockResponse("exampleResponseWellKnownUri.txt", mockedConn); + Configuration config = getConfigSpyMockedResponse(mockedURL, "validConfig.txt"); + new ConfidentialClient(config, reqOptions); + + verify(mockedURL).openConnection(mockProxy); + verify(mockedConn).setHostnameVerifier(reqOptions.getHostnameVerifier()); + verify(mockedConn).setSSLSocketFactory(reqOptions.getSslSocketFactory()); + } + @Test void getAccessTokenCallingWithFailedSigningRaisesSigningJwsException() throws Exception { try { @@ -195,14 +216,18 @@ void getAccessTokenCallingWithFailedSigningRaisesSigningJwsException() throws Ex @Test void getAccessTokenCallingWithErroneousResponseRaisesAccessTokenException() throws Exception { try { + HttpURLConnection mockedConn = mock(HttpURLConnection.class); + URL mockedURL = getUrlMockResponse("exampleResponseWellKnownUri.txt", mockedConn); Configuration configurationMock = ConfidentialClientTest.getConfigSpyMockedResponse( - "exampleResponseWellKnownUri.txt", "validConfig.txt" + mockedURL, "validConfig.txt" ); + HTTPRequest mockedRequest = mock(HTTPRequest.class); TokenRequestBuilder tokenRequestBuilderSpy = ConfidentialClientTest.createTokenRequestBuilderSpy( HTTPResponse.SC_UNAUTHORIZED, "{\"error_description\":\"Unauthorized access.\",\"error\":\"invalid_request\"}", - false + false, + mockedRequest ); ConfidentialClient confidentialClientSpy = spy(new ConfidentialClient(configurationMock, tokenRequestBuilderSpy)); @@ -215,14 +240,18 @@ void getAccessTokenCallingWithErroneousResponseRaisesAccessTokenException() thro @Test void getAccessTokenCalledForTheFirstTimeReturnsANewAccessToken() throws Exception { + HttpURLConnection mockedConn = mock(HttpURLConnection.class); + URL mockedURL = getUrlMockResponse("exampleResponseWellKnownUri.txt", mockedConn); Configuration configurationMock = ConfidentialClientTest.getConfigSpyMockedResponse( - "exampleResponseWellKnownUri.txt", "validConfig.txt" + mockedURL, "validConfig.txt" ); + HTTPRequest mockedRequest = mock(HTTPRequest.class); TokenRequestBuilder tokenRequestBuilderSpy = ConfidentialClientTest.createTokenRequestBuilderSpy( HTTPResponse.SC_OK, "{\"access_token\":\"test token\",\"token_type\":\"Bearer\",\"expires_in\":899}", - true + true, + mockedRequest ); ConfidentialClient confidentialClientSpy = spy(new ConfidentialClient(configurationMock, tokenRequestBuilderSpy)); @@ -231,10 +260,38 @@ void getAccessTokenCalledForTheFirstTimeReturnsANewAccessToken() throws Exceptio assertEquals("test token", accessToken); } + @Test + void getAccessTokenCalledWithRequestOptionsSetsProxyAndSSLSettings() throws Exception { + HttpURLConnection mockedConn = mock(HttpURLConnection.class); + URL mockedURL = getUrlMockResponse("exampleResponseWellKnownUri.txt", mockedConn); + Configuration configurationMock = ConfidentialClientTest.getConfigSpyMockedResponse( + mockedURL, "validConfig.txt" + ); + + HTTPRequest mockedRequest = mock(HTTPRequest.class); + TokenRequestBuilder tokenRequestBuilderSpy = ConfidentialClientTest.createTokenRequestBuilderSpy( + HTTPResponse.SC_OK, + "{\"access_token\":\"test token\",\"token_type\":\"Bearer\",\"expires_in\":899}", + true, + mockedRequest + ); + + Proxy mockProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080)); + RequestOptions reqOptions = RequestOptions.builder().proxy(mockProxy).build(); + ConfidentialClient confidentialClientSpy = spy(new ConfidentialClient(configurationMock, tokenRequestBuilderSpy, reqOptions)); + confidentialClientSpy.getAccessToken(); + + verify(mockedRequest).setProxy(mockProxy); + verify(mockedRequest).setHostnameVerifier(reqOptions.getHostnameVerifier()); + verify(mockedRequest).setSSLSocketFactory(reqOptions.getSslSocketFactory()); + } + @Test void getAccessTokenCalledTwiceBeforeExpirationReturnsSameAccessToken() throws Exception { + HttpURLConnection mockedConn = mock(HttpURLConnection.class); + URL mockedURL = getUrlMockResponse("exampleResponseWellKnownUri.txt", mockedConn); Configuration configurationMock = ConfidentialClientTest.getConfigSpyMockedResponse( - "exampleResponseWellKnownUri.txt", "validConfig.txt" + mockedURL, "validConfig.txt" ); HTTPResponse res = new HTTPResponse(HTTPResponse.SC_OK); @@ -265,8 +322,10 @@ void getAccessTokenCalledTwiceBeforeExpirationReturnsSameAccessToken() throws Ex @Test void getAccessTokenCallingBeforeAndAfterExpirationReturnsDifferentAccessToken() throws Exception { + HttpURLConnection mockedConn = mock(HttpURLConnection.class); + URL mockedURL = getUrlMockResponse("exampleResponseWellKnownUri.txt", mockedConn); Configuration configurationMock = ConfidentialClientTest.getConfigSpyMockedResponse( - "exampleResponseWellKnownUri.txt", "validConfig.txt" + mockedURL, "validConfig.txt" ); HTTPResponse res1 = new HTTPResponse(HTTPResponse.SC_OK); @@ -302,14 +361,18 @@ void getAccessTokenCallingBeforeAndAfterExpirationReturnsDifferentAccessToken() @Test void getAccessTokenCallingWithSendErrorRaisesAccessTokenException() throws Exception { try { + HttpURLConnection mockedConn = mock(HttpURLConnection.class); + URL mockedURL = getUrlMockResponse("exampleResponseWellKnownUri.txt", mockedConn); Configuration configurationMock = ConfidentialClientTest.getConfigSpyMockedResponse( - "exampleResponseWellKnownUri.txt", "validConfig.txt" + mockedURL, "validConfig.txt" ); + HTTPRequest mockedRequest = mock(HTTPRequest.class); TokenRequestBuilder tokenRequestBuilderSpy = ConfidentialClientTest.createTokenRequestBuilderSpy( HTTPResponse.SC_OK, "{\"error_description\":\"Invalid request.\",\"error\":\"invalid_request\"}", - false + false, + mockedRequest ); ConfidentialClient confidentialClientSpy = spy(new ConfidentialClient(configurationMock, tokenRequestBuilderSpy)); @@ -321,19 +384,17 @@ void getAccessTokenCallingWithSendErrorRaisesAccessTokenException() throws Excep } } - private static URL getUrlMockResponse(String stringFile) throws IOException { + private static URL getUrlMockResponse(String stringFile, HttpURLConnection mockedConn) throws IOException { final File file = new File(String.valueOf(Paths.get(String.valueOf(pathToResources), stringFile))); URL mockedUrl = mock(URL.class); - HttpURLConnection mockedConn = mock(HttpURLConnection.class); when(mockedUrl.openConnection(any(Proxy.class))).thenReturn(mockedConn); when(mockedConn.getInputStream()).thenReturn(Files.newInputStream(file.toPath())); return mockedUrl; } - private static Configuration getConfigSpyMockedResponse(String urlResponse, String configFile) throws IOException, ConfigurationException { - URL mockedURL = getUrlMockResponse(urlResponse); + private static Configuration getConfigSpyMockedResponse(URL mockedURL, String configFile) throws ConfigurationException { Configuration configuration = new Configuration(String.valueOf(Paths.get(pathToResources.toString(), configFile))); Configuration configurationSpy = spy(configuration); when(configurationSpy.getWellKnownUrl()).thenReturn(mockedURL); @@ -352,7 +413,7 @@ private static Configuration getConfigSpyThrowsIOException(String configFile) th } private static TokenRequestBuilder createTokenRequestBuilderSpy(int statusCode, String resContent, - boolean requiresHeader) throws URISyntaxException, + boolean requiresHeader, HTTPRequest mockedRequest) throws URISyntaxException, IOException { HTTPResponse res = new HTTPResponse(statusCode); res.setContent(resContent); @@ -367,11 +428,9 @@ private static TokenRequestBuilder createTokenRequestBuilderSpy(int statusCode, TokenRequestBuilder tokenRequestBuilderSpy = spy(new TokenRequestBuilder()); - HTTPRequest httpRequestMock = mock(HTTPRequest.class); - doReturn(tokenRequestMock).when(tokenRequestBuilderSpy).build(); - doReturn(httpRequestMock).when(tokenRequestMock).toHTTPRequest(); - doReturn(res).when(httpRequestMock).send(); + doReturn(mockedRequest).when(tokenRequestMock).toHTTPRequest(); + doReturn(res).when(mockedRequest).send(); return tokenRequestBuilderSpy; } From c6d81fba2bd30fbd1dd1f494ff2eb61153e52e41 Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Mon, 23 Oct 2023 14:11:19 +0100 Subject: [PATCH 09/16] docs: updates package version + snapshot docs --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++-- build.gradle | 2 +- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a6bd0e2..f2fc5e8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Add the below dependency to the project's POM: com.factset.sdk utils - 1.0.1 + 1.1.0-SNAPSHOT ``` @@ -32,10 +32,49 @@ repositories { } dependencies { - implementation "com.factset.sdk:utils:1.0.1" + implementation "com.factset.sdk:utils:1.1.0-SNAPSHOT" } ``` +### Snapshot Releases + +To be able to install snapshot releases of the sdk an additional repository must be added to the maven or gradle config. + +#### Maven Snapshot Repository + +```xml + + + sonatype + sonatype-snapshot + https://oss.sonatype.org/content/repositories/snapshots/ + + true + + + false + + + +``` + +#### Gradle Snapshot Repository + +```groovy +repositories { + mavenCentral() + maven { + url = uri("https://oss.sonatype.org/content/repositories/snapshots/") + mavenContent { + snapshotsOnly() + } + } +} +``` + +Snapshot releases are cached by gradle for some time, for details see: [Gradle Dynamic Versions](https://docs.gradle.org/current/userguide/dynamic_versions.html#sub:declaring_dependency_with_changing_version) + + ## Usage This library contains multiple modules, sample usage of each module is below. diff --git a/build.gradle b/build.gradle index 79409d3..4b7f058 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ apply plugin: 'jacoco' apply plugin: 'maven-publish' group 'com.factset.sdk' -version '1.0.1' +version '1.1.0-SNAPSHOT' dependencies { implementation 'org.slf4j:slf4j-api:1.7.36' From afa70651a1be5531881872b5f0601302ffa9a69a Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Mon, 23 Oct 2023 14:38:54 +0100 Subject: [PATCH 10/16] docs: guide to configure proxy --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index f2fc5e8..362ef15 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,18 @@ public class Console { } ``` +### Configure a Proxy + +The Confidential Client accepts an additional optional parameter called `RequestOptions`. This can be created to specify a proxy for the client to use. Below is an example of how to do this: + +```java +Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080)); +RequestOptions requestOptions = new RequestOptions.RequestOptionsBuilder().proxy(proxy).build(); + +// Pass this into client +ConfidentialClient confidentialClient = new ConfidentialClient("./path/to/config.json", requestOptions); +``` + ## Modules Information about the various utility modules contained in this library can be found below. From a432fa0ce86ceed25afe2aa35113308fb443ed00 Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Tue, 24 Oct 2023 10:46:08 +0100 Subject: [PATCH 11/16] docs: corrected proxy code example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 362ef15..5f7be0a 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ The Confidential Client accepts an additional optional parameter called `Request ```java Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080)); -RequestOptions requestOptions = new RequestOptions.RequestOptionsBuilder().proxy(proxy).build(); +RequestOptions requestOptions = RequestOptions.builder().proxy(proxy).build(); // Pass this into client ConfidentialClient confidentialClient = new ConfidentialClient("./path/to/config.json", requestOptions); From 3f8340045474d9e95ba35e277b5ee9cc7fe61da8 Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Tue, 24 Oct 2023 11:39:00 +0100 Subject: [PATCH 12/16] refactor: simplified + null checks --- .../utils/authentication/ConfidentialClient.java | 9 ++------- .../authentication/ConfidentialClientTest.java | 13 +++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java index 80c7f64..a1324df 100644 --- a/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java +++ b/src/main/java/com/factset/sdk/utils/authentication/ConfidentialClient.java @@ -99,12 +99,7 @@ public ConfidentialClient(final String configPath, RequestOptions requestOptions */ public ConfidentialClient(final Configuration config) throws AuthServerMetadataContentException, AuthServerMetadataException { - Objects.requireNonNull(config, "Configuration object must not be null"); - this.config = config; - LOGGER.debug("Finished initialising configuration"); - this.requestOptions = new RequestOptions.RequestOptionsBuilder().build(); - - this.requestProviderMetadata(); + this(config, RequestOptions.builder().build()); } /** @@ -123,7 +118,7 @@ public ConfidentialClient(final Configuration config, RequestOptions requestOpti Objects.requireNonNull(config, "Configuration object must not be null"); this.config = config; LOGGER.debug("Finished initialising configuration"); - this.requestOptions = requestOptions; + this.requestOptions = requestOptions == null ? RequestOptions.builder().build() : requestOptions; this.requestProviderMetadata(); } diff --git a/src/test/java/com/factset/sdk/utils/authentication/ConfidentialClientTest.java b/src/test/java/com/factset/sdk/utils/authentication/ConfidentialClientTest.java index 22f40f2..474862b 100644 --- a/src/test/java/com/factset/sdk/utils/authentication/ConfidentialClientTest.java +++ b/src/test/java/com/factset/sdk/utils/authentication/ConfidentialClientTest.java @@ -199,6 +199,19 @@ void confidentialClientValidConfigInitialisesWithRequestOptions() throws Excepti verify(mockedConn).setSSLSocketFactory(reqOptions.getSslSocketFactory()); } + @Test + void confidentialClientValidConfigInitialisesWithRequestOptionsAsNull() throws Exception { + HttpsURLConnection mockedConn = mock(HttpsURLConnection.class); + URL mockedURL = getUrlMockResponse("exampleResponseWellKnownUri.txt", mockedConn); + Configuration config = getConfigSpyMockedResponse(mockedURL, "validConfig.txt"); + RequestOptions reqOpts = null; + new ConfidentialClient(config, reqOpts); + + verify(mockedURL).openConnection(Proxy.NO_PROXY); + verify(mockedConn).setHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier()); + verify(mockedConn).setSSLSocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory()); + } + @Test void getAccessTokenCallingWithFailedSigningRaisesSigningJwsException() throws Exception { try { From 5e5a7708bce9ce9b4dbc108b7e3510e5d2322afc Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Tue, 24 Oct 2023 12:02:05 +0100 Subject: [PATCH 13/16] docs: included SSL configuration example --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 5f7be0a..b3f4491 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,25 @@ RequestOptions requestOptions = RequestOptions.builder().proxy(proxy).build(); ConfidentialClient confidentialClient = new ConfidentialClient("./path/to/config.json", requestOptions); ``` +### Custom SSL Certificate + +If you have proxies or firewalls which are using custom TLS certificates, you are able to [modify the Java Runtime Environment keystore](https://docs.plm.automation.siemens.com/content/polarion/20/help/en_US/polarion_windows_installation/manually_updating_third_party_software/import_a_certificate_to_the_java_keystore.html) so that the request library is able to verify the validity of that certificate. + +It is also possible to have a custom SSL configuration with the code example given below. + +```java +SSLContext sslContext = SSLContext.getInstance("SSL"); +sslContext.init(...); + +SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); +HostnameVerifier hostnameVerifier = ((hostname, session) -> ...); + +RequestOptions reqOpt = RequestOptions.builder() + .hostnameVerifier(hostnameVerifier) + .sslSocketFactory(sslSocketFactory) + .build(); +``` + ## Modules Information about the various utility modules contained in this library can be found below. From 90a4c6013ef8a831f8589cce6db09839c692bb55 Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Tue, 24 Oct 2023 13:39:23 +0100 Subject: [PATCH 14/16] docs: modified SSL configuration section --- README.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b3f4491..951f462 100644 --- a/README.md +++ b/README.md @@ -153,16 +153,24 @@ ConfidentialClient confidentialClient = new ConfidentialClient("./path/to/config ### Custom SSL Certificate -If you have proxies or firewalls which are using custom TLS certificates, you are able to [modify the Java Runtime Environment keystore](https://docs.plm.automation.siemens.com/content/polarion/20/help/en_US/polarion_windows_installation/manually_updating_third_party_software/import_a_certificate_to_the_java_keystore.html) so that the request library is able to verify the validity of that certificate. +If you are making requests to a server which is using custom TLS certificates, you are able to verify the validity of the certificate via the `RequestOptions` configuration. -It is also possible to have a custom SSL configuration with the code example given below. +#### Hostname Verifier + +You can pass in a custom hostname verifier to modify the details of the verification with a custom implementation. Otherwise, the `RequestOptions` will use the default one which checks the hostname in the certificate, located in the JRE keystore, and compares it to the hostname of the URL that is being hit by the client. + +#### SSL Socket Factory + +You can pass in a custom SSL Socket Factory and modify the `SSLContext` for a specific user use case. Otherwise, the `RequestOptions` uses a default `SSLSocketFactory` as described [here](https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/HttpsURLConnection.html#getDefaultHostnameVerifier()). + +#### Example ```java SSLContext sslContext = SSLContext.getInstance("SSL"); -sslContext.init(...); +sslContext.init(...); // Configure this based on application's needs SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); -HostnameVerifier hostnameVerifier = ((hostname, session) -> ...); +HostnameVerifier hostnameVerifier = ((hostname, session) -> ...); // Configure this based on application's needs RequestOptions reqOpt = RequestOptions.builder() .hostnameVerifier(hostnameVerifier) From 7d98a3c7c02187f2ca36faade71f5e8b6524ee29 Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Tue, 24 Oct 2023 13:40:04 +0100 Subject: [PATCH 15/16] style: spaced RequestOptions fields --- .../com/factset/sdk/utils/authentication/RequestOptions.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/factset/sdk/utils/authentication/RequestOptions.java b/src/main/java/com/factset/sdk/utils/authentication/RequestOptions.java index e6b01fd..b40865c 100644 --- a/src/main/java/com/factset/sdk/utils/authentication/RequestOptions.java +++ b/src/main/java/com/factset/sdk/utils/authentication/RequestOptions.java @@ -13,8 +13,10 @@ public class RequestOptions { @Builder.Default Proxy proxy = Proxy.NO_PROXY; + @Builder.Default HostnameVerifier hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); + @Builder.Default SSLSocketFactory sslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory(); } From 8c726e58536ac71e6c393c197e9b0e75d2b8e067 Mon Sep 17 00:00:00 2001 From: Granit Dula Date: Tue, 24 Oct 2023 14:13:34 +0100 Subject: [PATCH 16/16] docs: replace SSL for TLS in SSL example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 951f462..b95685b 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ You can pass in a custom SSL Socket Factory and modify the `SSLContext` for a sp #### Example ```java -SSLContext sslContext = SSLContext.getInstance("SSL"); +SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(...); // Configure this based on application's needs SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();