diff --git a/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/CwbiAuthSslSocketFactory.java b/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/CwbiAuthSslSocketFactory.java deleted file mode 100644 index cf6da213..00000000 --- a/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/CwbiAuthSslSocketFactory.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Hydrologic Engineering Center - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package hec.army.usace.hec.cwbi.auth.http.client; - -import hec.army.usace.hec.cwbi.auth.http.client.trustmanagers.CwbiAuthTrustManager; -import java.io.IOException; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; - -public final class CwbiAuthSslSocketFactory { - - private CwbiAuthSslSocketFactory() { - throw new AssertionError("Utility class"); - } - - /** - * Builds SSLSocketFactory configured for CWBI Auth and specified KeyManagers. - * @param keyManagers - KeyManager list - * @return SSLSocketFactory - * @throws IOException - thrown if building SSLSocketFactory failed - */ - public static SSLSocketFactory buildSSLSocketFactory(List keyManagers) throws IOException { - try { - SSLContext sc = SSLContext.getInstance("TLS"); - sc.init(keyManagers.toArray(new KeyManager[]{}), - new TrustManager[] {CwbiAuthTrustManager.getTrustManager()}, null); - return sc.getSocketFactory(); - } catch (NoSuchAlgorithmException | KeyManagementException e) { - throw new IOException(e); - } - } -} diff --git a/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/CwbiAuthTokenProvider.java b/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/CwbiAuthTokenProvider.java index 33847549..c95e59ca 100644 --- a/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/CwbiAuthTokenProvider.java +++ b/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/CwbiAuthTokenProvider.java @@ -37,33 +37,31 @@ /** * Suitable only for CWBI Keycloaks direct grant setup. */ -public final class CwbiAuthTokenProvider extends CwbiAuthTokenProviderBase { +public final class CwbiAuthTokenProvider extends OidcAuthTokenProvider { private final SSLSocketFactory sslSocketFactory; /** * Provider for OAuth2Tokens. * - * @param wellKnownUrl - URL we are retrieving configuration from + * @param wellKnownUrl - URL we are retrieving configuration from. Note this should include the SSLSocketData if the accessing the well-known endpoint requires client cert auth * @param clientId - client name * @param sslSocketFactory - ssl socket factory */ - public CwbiAuthTokenProvider(String wellKnownUrl, String clientId, SSLSocketFactory sslSocketFactory) { + public CwbiAuthTokenProvider(ApiConnectionInfo wellKnownUrl, String clientId, SSLSocketFactory sslSocketFactory) { super(clientId, wellKnownUrl); this.sslSocketFactory = Objects.requireNonNull(sslSocketFactory, "Missing required sslSocketFactory"); } - @Override - ApiConnectionInfo getUrl() { - return new ApiConnectionInfoBuilder(this.wellKnownUrl) - .withSslSocketData(new SslSocketData(sslSocketFactory, CwbiAuthTrustManager.getTrustManager())) - .build(); - } - @Override public ApiConnectionInfo getAuthUrl() { // This is specific to CWBI Direct Grant so this replacement as-is is fine - return new ApiConnectionInfoBuilder(this.tokenUrl.getApiRoot().replace("identity", "identityc")) + ApiConnectionInfo tokenUrl = this.getTokenUrl(); + String apiRoot = tokenUrl.getApiRoot(); + if(!apiRoot.contains("identityc")) { + apiRoot = tokenUrl.getApiRoot().replace("identity", "identityc"); + } + return new ApiConnectionInfoBuilder(apiRoot) .withSslSocketData(new SslSocketData(sslSocketFactory, CwbiAuthTrustManager.getTrustManager())) .build(); } @@ -72,7 +70,7 @@ public ApiConnectionInfo getAuthUrl() { public OAuth2Token newToken() throws IOException { return new DirectGrantX509TokenRequestBuilder() .withTokenUrl(getAuthUrl()) - .buildRequest().withClientId(clientId) + .buildRequest().withClientId(getClientId()) .fetchToken(); } } diff --git a/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/CwbiAuthTokenProviderBase.java b/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/CwbiAuthTokenProviderBase.java deleted file mode 100644 index 7e1b5c55..00000000 --- a/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/CwbiAuthTokenProviderBase.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2025 Hydrologic Engineering Center - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package hec.army.usace.hec.cwbi.auth.http.client; - -import java.io.IOException; - -import mil.army.usace.hec.cwms.http.client.ApiConnectionInfo; -import mil.army.usace.hec.cwms.http.client.auth.OAuth2Token; - -abstract class CwbiAuthTokenProviderBase extends OidcAuthTokenProvider { - - protected CwbiAuthTokenProviderBase(String clientId, String wellKnownUrl) { - super(clientId, wellKnownUrl); - } - - abstract ApiConnectionInfo getUrl() throws IOException; - - - @Override - public synchronized OAuth2Token getToken() throws IOException { - if (token == null) { - token = newToken(); - } - return token; - } - - @Override - public synchronized OAuth2Token refreshToken() throws IOException { - OAuth2Token newToken = new RefreshTokenRequestBuilder() - .withRefreshToken(token.getRefreshToken()) - .withUrl(tokenUrl) - .withClientId(clientId) - .fetchToken(); - token = newToken; - return token; - } - - //package scoped for testing - String getClientId() { - return clientId; - } -} diff --git a/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/OidcAuthTokenProvider.java b/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/OidcAuthTokenProvider.java index fc6eb352..2b53f668 100644 --- a/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/OidcAuthTokenProvider.java +++ b/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/OidcAuthTokenProvider.java @@ -7,7 +7,6 @@ import java.util.function.Consumer; import mil.army.usace.hec.cwms.http.client.ApiConnectionInfo; -import mil.army.usace.hec.cwms.http.client.ApiConnectionInfoBuilder; import mil.army.usace.hec.cwms.http.client.auth.OAuth2Token; import mil.army.usace.hec.cwms.http.client.auth.OAuth2TokenProvider; @@ -20,37 +19,20 @@ */ public class OidcAuthTokenProvider implements OAuth2TokenProvider { - protected final String clientId; - protected final String wellKnownUrl; - protected final ApiConnectionInfo tokenUrl; - protected final ApiConnectionInfo authUrl; - protected OAuth2Token token = null; + private final String clientId; + private final ApiConnectionInfo wellKnownUrl; + private ApiConnectionInfo tokenUrl; + private ApiConnectionInfo authUrl; + private final StaticOidcTokenController wellKnowEndpointController; + private OAuth2Token token = null; // Default to open browser or print to console for usage, but allow overriding for testing and // other usages. private Consumer authCallback = TokenRequestBuilder.BROWSER_OR_CONSOLE_AUTH_CALLBACK; - public OidcAuthTokenProvider(String clientId, String wellKnownUrl) { + public OidcAuthTokenProvider(String clientId, ApiConnectionInfo wellKnownUrl) { this.clientId = Objects.requireNonNull(clientId, "Missing required client id."); this.wellKnownUrl = Objects.requireNonNull(wellKnownUrl, "Missing required well known Url."); - - OpenIdTokenController controller = new OpenIdTokenController() { - - @Override - public String retrieveWellKnownEndpoint(ApiConnectionInfo apiConnectionInfo) throws IOException { - return wellKnownUrl; // we already have it. - } - - }; - ApiConnectionInfo info = new ApiConnectionInfoBuilder(wellKnownUrl).build(); - String what = "auth"; - try { - this.authUrl = controller.retrieveAuthUrl(info, null); - what = "token"; - this.tokenUrl = controller.retrieveTokenUrl(info, null); - // TODO: process appropriate extensions to determine things like "kc_idp_hint" - } catch (IOException ex) { - throw new CompletionException("Unable to return " + what + " URL", ex); - } + this.wellKnowEndpointController = new StaticOidcTokenController(wellKnownUrl); } @Override @@ -83,12 +65,11 @@ public OAuth2Token getToken() throws IOException { @Override public OAuth2Token refreshToken() throws IOException { synchronized (this) { - OAuth2Token newToken = new RefreshTokenRequestBuilder() + token = new RefreshTokenRequestBuilder() .withRefreshToken(token.getRefreshToken()) - .withUrl(tokenUrl) + .withUrl(getTokenUrl()) .withClientId(clientId) .fetchToken(); - token = newToken; return token; } } @@ -102,8 +83,8 @@ public OAuth2Token newToken() throws IOException { * There are various notes about it in different sections for discussion. */ token = new AuthCodePkceTokenRequestBuilder() - .withAuthUrl(authUrl) - .withTokenUrl(tokenUrl) + .withAuthUrl(getAuthUrl()) + .withTokenUrl(getTokenUrl()) .withAuthCallback(authCallback) .buildRequest() .withClientId(clientId) @@ -115,12 +96,38 @@ public OAuth2Token newToken() throws IOException { @Override public ApiConnectionInfo getAuthUrl() { + if(authUrl == null) { + initializeAuthUrls(); + } return authUrl; } @Override public ApiConnectionInfo getTokenUrl() { + if(tokenUrl == null) { + initializeAuthUrls(); + } return tokenUrl; } + private synchronized void initializeAuthUrls() { + String what = "auth"; + try { + this.authUrl = this.wellKnowEndpointController.retrieveAuthUrl(wellKnownUrl); + what = "token"; + this.tokenUrl = this.wellKnowEndpointController.retrieveTokenUrl(wellKnownUrl); + // TODO: process appropriate extensions to determine things like "kc_idp_hint" + } catch (IOException ex) { + throw new CompletionException("Unable to return " + what + " URL", ex); + } + } + + ApiConnectionInfo getWellKnownUrl() { + return this.wellKnownUrl; + } + + String getClientId() { + return this.clientId; + } + } diff --git a/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/OpenIdTokenController.java b/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/OpenIdTokenController.java index 3df97b79..2f4b218d 100644 --- a/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/OpenIdTokenController.java +++ b/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/OpenIdTokenController.java @@ -29,7 +29,6 @@ import mil.army.usace.hec.cwms.http.client.ApiConnectionInfoBuilder; import mil.army.usace.hec.cwms.http.client.HttpRequestBuilderImpl; import mil.army.usace.hec.cwms.http.client.HttpRequestResponse; -import mil.army.usace.hec.cwms.http.client.SslSocketData; import mil.army.usace.hec.cwms.http.client.request.HttpRequestExecutor; public abstract class OpenIdTokenController { @@ -42,19 +41,16 @@ public abstract class OpenIdTokenController { /** * Retrieve json text of the .wellknown/openid-configuration - * @param apiConnectionInfo - * @return - * @throws IOException + * @param apiConnectionInfo - connection info to the config endpoint containing the well-known endpoint + * @return ApiConnectionInfo containing the well-known endpoint url + * @throws IOException - throws IOException if there is an issue with the http request to the config endpoint */ - public abstract String retrieveWellKnownEndpoint(ApiConnectionInfo apiConnectionInfo) throws IOException; + public abstract ApiConnectionInfo retrieveWellKnownEndpoint(ApiConnectionInfo apiConnectionInfo) throws IOException; - public final ApiConnectionInfo retrieveTokenUrl(ApiConnectionInfo apiConnectionInfo, SslSocketData sslSocketData) throws IOException { + public final ApiConnectionInfo retrieveTokenUrl(ApiConnectionInfo apiConnectionInfo) throws IOException { if (tokenEndpoint == null) { - String wellKnownEndpoint = retrieveWellKnownEndpoint(apiConnectionInfo); + ApiConnectionInfo wellKnownApiConnectionInfo = retrieveWellKnownEndpoint(apiConnectionInfo); - ApiConnectionInfo wellKnownApiConnectionInfo = new ApiConnectionInfoBuilder(wellKnownEndpoint) - .withSslSocketData(sslSocketData) - .build(); HttpRequestExecutor executor = new HttpRequestBuilderImpl(wellKnownApiConnectionInfo) .get(); try (HttpRequestResponse response = executor.execute()) { @@ -62,17 +58,13 @@ public final ApiConnectionInfo retrieveTokenUrl(ApiConnectionInfo apiConnectionI } } return new ApiConnectionInfoBuilder(tokenEndpoint) - .withSslSocketData(sslSocketData) .build(); } - public final ApiConnectionInfo retrieveAuthUrl(ApiConnectionInfo apiConnectionInfo, SslSocketData sslSocketData) throws IOException { + public final ApiConnectionInfo retrieveAuthUrl(ApiConnectionInfo apiConnectionInfo) throws IOException { if (authEndpoint == null) { - String wellKnownEndpoint = retrieveWellKnownEndpoint(apiConnectionInfo); - ApiConnectionInfo wellKnownApiConnectionInfo = new ApiConnectionInfoBuilder(wellKnownEndpoint) - .withSslSocketData(sslSocketData) - .build(); + ApiConnectionInfo wellKnownApiConnectionInfo = retrieveWellKnownEndpoint(apiConnectionInfo); HttpRequestExecutor executor = new HttpRequestBuilderImpl(wellKnownApiConnectionInfo) .get(); try (HttpRequestResponse response = executor.execute()) { @@ -80,7 +72,6 @@ public final ApiConnectionInfo retrieveAuthUrl(ApiConnectionInfo apiConnectionIn } } return new ApiConnectionInfoBuilder(authEndpoint) - .withSslSocketData(sslSocketData) .build(); } } diff --git a/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/SSLOidcDiscoveryController.java b/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/SSLOidcDiscoveryController.java new file mode 100644 index 00000000..d1ac2a5c --- /dev/null +++ b/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/SSLOidcDiscoveryController.java @@ -0,0 +1,29 @@ +package hec.army.usace.hec.cwbi.auth.http.client; + +import mil.army.usace.hec.cwms.http.client.ApiConnectionInfo; +import mil.army.usace.hec.cwms.http.client.ApiConnectionInfoBuilder; +import mil.army.usace.hec.cwms.http.client.SslSocketData; + +import java.io.IOException; + +/** + * An extension of OpenIdTokenController that allows for SSL socket data to be included in the retrieval of the well-known endpoint. + * This is necessary for cases where the well-known endpoint requires client certificate authentication. + */ +public abstract class SSLOidcDiscoveryController extends OpenIdTokenController { + + private final SslSocketData sslSocketData; + + protected SSLOidcDiscoveryController(SslSocketData sslSocketData) { + this.sslSocketData = sslSocketData; + } + + protected abstract String retrieveWellKnownEndpointUrl(ApiConnectionInfo apiConnectionInfo) throws IOException; + + @Override + public final ApiConnectionInfo retrieveWellKnownEndpoint(ApiConnectionInfo apiConnectionInfo) throws IOException { + return new ApiConnectionInfoBuilder(retrieveWellKnownEndpointUrl(apiConnectionInfo)) + .withSslSocketData(sslSocketData) + .build(); + } +} diff --git a/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/StaticOidcTokenController.java b/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/StaticOidcTokenController.java new file mode 100644 index 00000000..389ec43d --- /dev/null +++ b/cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/StaticOidcTokenController.java @@ -0,0 +1,22 @@ +package hec.army.usace.hec.cwbi.auth.http.client; + +import mil.army.usace.hec.cwms.http.client.ApiConnectionInfo; + +/** + * A StaticOidcTokenController is an implementation of OpenIdTokenController that always returns the same well-known URL. + * This is useful if the well-known URL is already known and does not need to be dynamically retrieved. + * Note that if access to the well-known URL requires authentication, the wellKnownUrl provided to this controller should include the necessary authentication information (e.g. client cert data in the SslSocketData). + */ +class StaticOidcTokenController extends OpenIdTokenController { + + private final ApiConnectionInfo wellKnownUrl; + + StaticOidcTokenController(ApiConnectionInfo wellKnownUrl) { + this.wellKnownUrl = wellKnownUrl; + } + + @Override + public ApiConnectionInfo retrieveWellKnownEndpoint(ApiConnectionInfo apiConnectionInfo) { + return this.wellKnownUrl; + } +} diff --git a/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/MockCwbiAuthTokenProvider.java b/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/MockCwbiAuthTokenProvider.java index 6a53a9bd..a50bf218 100644 --- a/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/MockCwbiAuthTokenProvider.java +++ b/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/MockCwbiAuthTokenProvider.java @@ -23,17 +23,12 @@ */ package hec.army.usace.hec.cwbi.auth.http.client; -import hec.army.usace.hec.cwbi.auth.http.client.trustmanagers.CwbiAuthTrustManager; import java.util.Objects; import javax.net.ssl.SSLSocketFactory; import mil.army.usace.hec.cwms.http.client.ApiConnectionInfo; -import mil.army.usace.hec.cwms.http.client.ApiConnectionInfoBuilder; -import mil.army.usace.hec.cwms.http.client.SslSocketData; -import mil.army.usace.hec.cwms.http.client.auth.OAuth2Token; -public class MockCwbiAuthTokenProvider extends CwbiAuthTokenProviderBase { +public class MockCwbiAuthTokenProvider extends OidcAuthTokenProvider { - private final String url; private final SSLSocketFactory sslSocketFactory; /** @@ -43,22 +38,9 @@ public class MockCwbiAuthTokenProvider extends CwbiAuthTokenProviderBase { * @param clientId - client name * @param sslSocketFactory - ssl socket factory */ - public MockCwbiAuthTokenProvider(String url, String clientId, SSLSocketFactory sslSocketFactory) { + public MockCwbiAuthTokenProvider(ApiConnectionInfo url, String clientId, SSLSocketFactory sslSocketFactory) { super(clientId, url); this.sslSocketFactory = Objects.requireNonNull(sslSocketFactory, "Missing required sslSocketFactory"); - this.url = url; - } - - //used to manually set token for testing - void setOAuth2Token(OAuth2Token token) { - this.token = token; - } - - @Override - ApiConnectionInfo getUrl() { - return new ApiConnectionInfoBuilder(url) - .withSslSocketData(new SslSocketData(sslSocketFactory, CwbiAuthTrustManager.getTrustManager())) - .build(); } //package scoped for testing diff --git a/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestCwbiTokenProvider.java b/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestCwbiTokenProvider.java index c00d516e..7fb7ae9d 100644 --- a/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestCwbiTokenProvider.java +++ b/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestCwbiTokenProvider.java @@ -23,8 +23,8 @@ */ package hec.army.usace.hec.cwbi.auth.http.client; -import static hec.army.usace.hec.cwbi.auth.http.client.trustmanagers.CwbiAuthTrustManager.TOKEN_TEST_URL; -import static hec.army.usace.hec.cwbi.auth.http.client.trustmanagers.CwbiAuthTrustManager.TOKEN_URL; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; import java.util.Collections; import javax.net.ssl.KeyManager; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -37,16 +37,20 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Logger; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +import hec.army.usace.hec.cwbi.auth.http.client.trustmanagers.CwbiAuthTrustManager; import mil.army.usace.hec.cwms.http.client.MockHttpServer; import mil.army.usace.hec.cwms.http.client.ApiConnectionInfo; import mil.army.usace.hec.cwms.http.client.ApiConnectionInfoBuilder; import mil.army.usace.hec.cwms.http.client.auth.OAuth2Token; -import mil.army.usace.hec.cwms.http.client.request.QueryParameters; import okhttp3.HttpUrl; import okhttp3.mockwebserver.Dispatcher; import okhttp3.mockwebserver.MockResponse; @@ -133,11 +137,10 @@ else if (path.endsWith("/token")) { void testBuildTokenProvider() throws IOException { String resource = "oauth2token.json"; launchMockServerWithResource(resource); - SSLSocketFactory sslSocketFactory = CwbiAuthSslSocketFactory.buildSSLSocketFactory( - Collections.singletonList(getTestKeyManager())); - String url = buildConnectionInfo().getApiRoot(); + SSLSocketFactory sslSocketFactory = buildSSLSocketFactory(Collections.singletonList(getTestKeyManager())); + ApiConnectionInfo url = buildConnectionInfo(); CwbiAuthTokenProvider tokenProvider = new CwbiAuthTokenProvider(url, "cumulus", sslSocketFactory); - assertEquals(url, tokenProvider.getUrl().getApiRoot()); + assertEquals(url.getApiRoot(), tokenProvider.getWellKnownUrl().getApiRoot()); assertEquals("cumulus", tokenProvider.getClientId()); } @@ -145,7 +148,7 @@ void testBuildTokenProvider() throws IOException { void testNulls() throws IOException { String resource = "oauth2token.json"; launchMockServerWithResource(resource); - String url = buildConnectionInfo().getApiRoot(); + ApiConnectionInfo url = buildConnectionInfo(); assertThrows(NullPointerException.class, () -> new CwbiAuthTokenProvider(url, "cumulus", null)); assertThrows(NullPointerException.class, () -> new CwbiAuthTokenProvider(url, null, getTestSslSocketFactory())); assertThrows(NullPointerException.class, () -> new CwbiAuthTokenProvider(null, "cumulus", getTestSslSocketFactory())); @@ -160,7 +163,7 @@ private KeyManager getTestKeyManager() { void testGetToken() throws IOException { String resource = "oauth2token.json"; launchMockServerWithResource(resource); - String url = buildConnectionInfo().getApiRoot(); + ApiConnectionInfo url = buildConnectionInfo(); CwbiAuthTokenProvider tokenProvider = new CwbiAuthTokenProvider(url, "cumulus", getTestSslSocketFactory()); OAuth2Token token = tokenProvider.getToken(); assertEquals("MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3", token.getAccessToken()); @@ -174,7 +177,7 @@ void testGetToken() throws IOException { void testClear() throws IOException { String resource = "oauth2token.json"; launchMockServerWithResource(resource); - String url = buildConnectionInfo().getApiRoot(); + ApiConnectionInfo url = buildConnectionInfo(); CwbiAuthTokenProvider tokenProvider = new CwbiAuthTokenProvider(url, "cumulus", getTestSslSocketFactory()); OAuth2Token token1 = tokenProvider.getToken(); OAuth2Token token2 = tokenProvider.getToken(); @@ -187,7 +190,7 @@ void testClear() throws IOException { void testRefreshToken() throws IOException { String resource = "oauth2token.json"; launchMockServerWithResource(resource); - String url = buildConnectionInfo().getApiRoot(); + ApiConnectionInfo url = buildConnectionInfo(); CwbiAuthTokenProvider tokenProvider = new CwbiAuthTokenProvider(url, "cumulus", getTestSslSocketFactory()); OAuth2Token token = tokenProvider.getToken(); assertNotNull(token, "Failed to retrieve initial token."); @@ -204,14 +207,25 @@ void testRefreshToken() throws IOException { void testConstructor() throws IOException { String resource = "oauth2token.json"; launchMockServerWithResource(resource); - String url = buildConnectionInfo().getApiRoot(); + ApiConnectionInfo url = buildConnectionInfo(); SSLSocketFactory sslSocketFactory = getTestSslSocketFactory(); MockCwbiAuthTokenProvider tokenProvider = new MockCwbiAuthTokenProvider(url, "clientId", sslSocketFactory); - assertEquals(url, tokenProvider.getUrl().getApiRoot()); + assertEquals(url.getApiRoot(), tokenProvider.getWellKnownUrl().getApiRoot()); assertEquals("clientId", tokenProvider.getClientId()); assertEquals(sslSocketFactory, tokenProvider.getSslSocketFactory()); } + private static SSLSocketFactory buildSSLSocketFactory(List keyManagers) throws IOException { + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(keyManagers.toArray(new KeyManager[]{}), + new TrustManager[] {CwbiAuthTrustManager.getTrustManager()}, null); + return sc.getSocketFactory(); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new IOException(e); + } + } + private SSLSocketFactory getTestSslSocketFactory() { return new SSLSocketFactory() { @Override diff --git a/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestOidcTokenProvider.java b/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestOidcTokenProvider.java index eaf29b47..0c31c954 100644 --- a/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestOidcTokenProvider.java +++ b/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestOidcTokenProvider.java @@ -123,7 +123,8 @@ else if (path.endsWith("/token")) { }); - String wellKnown = "http://localhost:"+mockAuthServer.getPort()+"/auth/realms/cwbi/.well-known/openid-configuration"; + ApiConnectionInfo wellKnown = new ApiConnectionInfoBuilder("http://localhost:"+mockAuthServer.getPort()+"/auth/realms/cwbi/.well-known/openid-configuration") + .build(); OidcAuthTokenProvider tokenProvider = new OidcAuthTokenProvider("test", wellKnown); tokenProvider.setAuthCallback(u -> { try { diff --git a/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestOpenIdTokenController.java b/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestOpenIdTokenController.java index b0f46296..09e8545c 100644 --- a/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestOpenIdTokenController.java +++ b/cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestOpenIdTokenController.java @@ -46,15 +46,11 @@ void testRetrieveTokenUrl() throws Exception { SSLSocketFactory mockSslSocketFactory = Mockito.mock(SSLSocketFactory.class); SslSocketData sslSocketData = new SslSocketData(mockSslSocketFactory, CwbiAuthTrustManager.getTrustManager()); String baseUrl = String.format("http://localhost:%s", mockWebServer.getPort()); - String wellKnownEndpoint = baseUrl + "/.well-known/openid-configuration"; + ApiConnectionInfo wellKnownEndpoint = new ApiConnectionInfoBuilder(baseUrl + "/.well-known/openid-configuration") + .withSslSocketData(sslSocketData) + .build(); mockWebServer.enqueue(new MockResponse().setBody(readResourceAsString("openIdConfig.json")).setResponseCode(200)); - ApiConnectionInfo tokenUrl = new OpenIdTokenController(){ - - @Override - public String retrieveWellKnownEndpoint(ApiConnectionInfo apiConnectionInfo) { - return wellKnownEndpoint; - } - }.retrieveTokenUrl(new ApiConnectionInfoBuilder(baseUrl).build(), sslSocketData); + ApiConnectionInfo tokenUrl = new StaticOidcTokenController(wellKnownEndpoint).retrieveTokenUrl(new ApiConnectionInfoBuilder(baseUrl).build()); assertEquals("https://api.example.com/auth/realms/cwbi/protocol/openid-connect/token", tokenUrl.getApiRoot()); } } diff --git a/cwms-data-api-client/src/main/java/mil/army/usace/hec/cwms/data/api/client/controllers/CdaOpenIdTokenController.java b/cwms-data-api-client/src/main/java/mil/army/usace/hec/cwms/data/api/client/controllers/CdaOpenIdTokenController.java index 97b2e38b..2f40e90b 100644 --- a/cwms-data-api-client/src/main/java/mil/army/usace/hec/cwms/data/api/client/controllers/CdaOpenIdTokenController.java +++ b/cwms-data-api-client/src/main/java/mil/army/usace/hec/cwms/data/api/client/controllers/CdaOpenIdTokenController.java @@ -23,18 +23,23 @@ */ package mil.army.usace.hec.cwms.data.api.client.controllers; -import hec.army.usace.hec.cwbi.auth.http.client.OpenIdTokenController; +import hec.army.usace.hec.cwbi.auth.http.client.SSLOidcDiscoveryController; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.parser.OpenAPIV3Parser; import java.io.IOException; import mil.army.usace.hec.cwms.http.client.ApiConnectionInfo; +import mil.army.usace.hec.cwms.http.client.SslSocketData; -public final class CdaOpenIdTokenController extends OpenIdTokenController { +public final class CdaOpenIdTokenController extends SSLOidcDiscoveryController { private static final String SWAGGER_DOC_ENDPOINT = "swagger-docs"; + public CdaOpenIdTokenController(SslSocketData sslSocketData) { + super(sslSocketData); + } + @Override - public String retrieveWellKnownEndpoint(ApiConnectionInfo apiConnectionInfo) throws IOException { + protected String retrieveWellKnownEndpointUrl(ApiConnectionInfo apiConnectionInfo) throws IOException { String url = apiConnectionInfo.getApiRoot() + "/" + SWAGGER_DOC_ENDPOINT; OpenAPI openAPI = new OpenAPIV3Parser().read(url); if(openAPI == null) { diff --git a/cwms-data-api-client/src/test/java/mil/army/usace/hec/cwms/data/api/client/controllers/TestCdaOpenIdTokenController.java b/cwms-data-api-client/src/test/java/mil/army/usace/hec/cwms/data/api/client/controllers/TestCdaOpenIdTokenController.java index 49f44427..8e3e06d6 100644 --- a/cwms-data-api-client/src/test/java/mil/army/usace/hec/cwms/data/api/client/controllers/TestCdaOpenIdTokenController.java +++ b/cwms-data-api-client/src/test/java/mil/army/usace/hec/cwms/data/api/client/controllers/TestCdaOpenIdTokenController.java @@ -29,6 +29,7 @@ import java.io.IOException; import javax.net.ssl.SSLSocketFactory; import mil.army.usace.hec.cwms.http.client.ApiConnectionInfo; +import mil.army.usace.hec.cwms.http.client.ApiConnectionInfoBuilder; import mil.army.usace.hec.cwms.http.client.SslSocketData; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -52,7 +53,7 @@ void testRetrieveTokenUrl() throws Exception { mockHttpServer.enqueue(updatedIdpConfig); mockHttpServer.enqueue(readJsonFile(openIdConfig)); SslSocketData sslSocketData = new SslSocketData(mockSslSocketFactory, CwbiAuthTrustManager.getTrustManager()); - ApiConnectionInfo tokenUrl = new CdaOpenIdTokenController().retrieveTokenUrl(buildConnectionInfo(), sslSocketData); + ApiConnectionInfo tokenUrl = new CdaOpenIdTokenController(sslSocketData).retrieveTokenUrl(buildConnectionInfo()); assertEquals("https://api.example.com/auth/realms/cwbi/protocol/openid-connect/token", tokenUrl.getApiRoot()); } @@ -73,6 +74,6 @@ void testRetrieveTokenUrlNoSpec() throws Exception { mockHttpServer.enqueue(updatedIdpConfig); mockHttpServer.enqueue(readJsonFile(openIdConfig)); SslSocketData sslSocketData = new SslSocketData(mockSslSocketFactory, CwbiAuthTrustManager.getTrustManager()); - assertThrows(IOException.class, () -> new CdaOpenIdTokenController().retrieveTokenUrl(buildConnectionInfo(), sslSocketData)); + assertThrows(IOException.class, () -> new CdaOpenIdTokenController(sslSocketData).retrieveTokenUrl(buildConnectionInfo())); } }