From ec1c996c0ecf1e1ca7256210fd0e02caa75bd226 Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Thu, 12 Feb 2026 09:22:48 -0800 Subject: [PATCH 1/5] CWMS-2386 - Adds ability to attach SslSocketData to the wellKnownUrl for cases where accessing the wellKnownUrl requires auth. Refactors web calls to not be done inside OidcAuthTokenProvider constructor. Removed CwbiAuthTokenProviderBase as its no longer needed. Improved encapsulation in OidcAuthTokenProvider. --- .../http/client/CwbiAuthTokenProvider.java | 17 ++--- .../client/CwbiAuthTokenProviderBase.java | 63 ----------------- .../http/client/OidcAuthTokenProvider.java | 69 ++++++++++--------- .../http/client/OpenIdTokenController.java | 25 +++---- .../client/SSLOidcDiscoveryController.java | 29 ++++++++ .../client/StaticOidcTokenController.java | 22 ++++++ .../client/MockCwbiAuthTokenProvider.java | 42 ++++++++--- .../http/client/TestCwbiTokenProvider.java | 19 +++-- .../http/client/TestOidcTokenProvider.java | 3 +- .../client/TestOpenIdTokenController.java | 12 ++-- .../controllers/CdaOpenIdTokenController.java | 11 ++- .../TestCdaOpenIdTokenController.java | 5 +- 12 files changed, 161 insertions(+), 156 deletions(-) delete mode 100644 cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/CwbiAuthTokenProviderBase.java create mode 100644 cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/SSLOidcDiscoveryController.java create mode 100644 cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/StaticOidcTokenController.java 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..0fd848ab 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,26 @@ /** * 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")) + return new ApiConnectionInfoBuilder(this.getTokenUrl().getApiRoot().replace("identity", "identityc")) .withSslSocketData(new SslSocketData(sslSocketFactory, CwbiAuthTrustManager.getTrustManager())) .build(); } @@ -72,7 +65,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..7403b5c8 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 @@ -31,10 +31,11 @@ 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; + private final ApiConnectionInfo rawUrl; + private final String clientId; /** * Provider for OAuth2Tokens. @@ -43,24 +44,49 @@ 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.clientId = Objects.requireNonNull(clientId, "Missing required client id."); + this.rawUrl = Objects.requireNonNull(url, "Missing required url"); this.sslSocketFactory = Objects.requireNonNull(sslSocketFactory, "Missing required sslSocketFactory"); - this.url = url; } - //used to manually set token for testing + //used to manually set token for testing (not used currently, but keep API) void setOAuth2Token(OAuth2Token token) { - this.token = token; + // No direct access to parent token; simulate by overriding newToken in tests if needed. + throw new UnsupportedOperationException("Manual token injection not supported"); } - @Override + // Expose the provided URL with SSL settings (package scope for tests) ApiConnectionInfo getUrl() { - return new ApiConnectionInfoBuilder(url) + return new ApiConnectionInfoBuilder(rawUrl.getApiRoot()) .withSslSocketData(new SslSocketData(sslSocketFactory, CwbiAuthTrustManager.getTrustManager())) .build(); } + // Provide clientId for assertions (package scope for tests) + String getClientId() { + return clientId; + } + + @Override + public ApiConnectionInfo getTokenUrl() { + // Use test SSL data when token URL is requested + return getUrl(); + } + + @Override + public ApiConnectionInfo getAuthUrl() { + // For mock, just return same as token URL + return getUrl(); + } + + @Override + public OAuth2Token newToken() { + // Not used in tests that construct MockCwbiAuthTokenProvider; throw if invoked unexpectedly + throw new UnsupportedOperationException("Mock provider does not fetch real tokens"); + } + //package scoped for testing SSLSocketFactory getSslSocketFactory() { return sslSocketFactory; 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..ea968151 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,6 @@ */ 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.util.Collections; import javax.net.ssl.KeyManager; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -46,7 +44,6 @@ 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; @@ -135,9 +132,9 @@ void testBuildTokenProvider() throws IOException { launchMockServerWithResource(resource); SSLSocketFactory sslSocketFactory = CwbiAuthSslSocketFactory.buildSSLSocketFactory( Collections.singletonList(getTestKeyManager())); - String url = buildConnectionInfo().getApiRoot(); + 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 +142,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 +157,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 +171,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 +184,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,10 +201,10 @@ 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.getUrl().getApiRoot()); assertEquals("clientId", tokenProvider.getClientId()); assertEquals(sslSocketFactory, tokenProvider.getSslSocketFactory()); } 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..c2bb4d10 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).withSslSocketData(sslSocketData).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())); } } From 74d3d63fa05aca89324585f6e3fd6e77caeccca2 Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Thu, 12 Feb 2026 09:29:01 -0800 Subject: [PATCH 2/5] CWMS-2386 - Removed CwbiAuthSslSocketFactory as this will be provided at or near the application level. --- .../http/client/CwbiAuthSslSocketFactory.java | 58 ------------------- .../http/client/TestCwbiTokenProvider.java | 21 ++++++- 2 files changed, 19 insertions(+), 60 deletions(-) delete mode 100644 cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/CwbiAuthSslSocketFactory.java 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/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 ea968151..0ca43a7a 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,6 +23,8 @@ */ package hec.army.usace.hec.cwbi.auth.http.client; +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; @@ -35,11 +37,16 @@ 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; @@ -130,8 +137,7 @@ else if (path.endsWith("/token")) { void testBuildTokenProvider() throws IOException { String resource = "oauth2token.json"; launchMockServerWithResource(resource); - SSLSocketFactory sslSocketFactory = CwbiAuthSslSocketFactory.buildSSLSocketFactory( - Collections.singletonList(getTestKeyManager())); + SSLSocketFactory sslSocketFactory = buildSSLSocketFactory(Collections.singletonList(getTestKeyManager())); ApiConnectionInfo url = buildConnectionInfo(); CwbiAuthTokenProvider tokenProvider = new CwbiAuthTokenProvider(url, "cumulus", sslSocketFactory); assertEquals(url.getApiRoot(), tokenProvider.getWellKnownUrl().getApiRoot()); @@ -209,6 +215,17 @@ void testConstructor() throws IOException { 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 From a22bfc8f53f8e4c73f5c3d691f4e42897f9c0d8e Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Thu, 12 Feb 2026 09:56:57 -0800 Subject: [PATCH 3/5] CWMS-2386 - cleaned up tests --- .../client/MockCwbiAuthTokenProvider.java | 45 ++----------------- .../client/TestOpenIdTokenController.java | 2 +- 2 files changed, 5 insertions(+), 42 deletions(-) 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 7403b5c8..8be86fbb 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,19 +23,14 @@ */ 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 OidcAuthTokenProvider { private final SSLSocketFactory sslSocketFactory; - private final ApiConnectionInfo rawUrl; - private final String clientId; + private final ApiConnectionInfo url; /** * Provider for OAuth2Tokens. @@ -46,45 +41,13 @@ public class MockCwbiAuthTokenProvider extends OidcAuthTokenProvider { */ public MockCwbiAuthTokenProvider(ApiConnectionInfo url, String clientId, SSLSocketFactory sslSocketFactory) { super(clientId, url); - this.clientId = Objects.requireNonNull(clientId, "Missing required client id."); - this.rawUrl = Objects.requireNonNull(url, "Missing required url"); + this.url = Objects.requireNonNull(url, "Missing required url"); this.sslSocketFactory = Objects.requireNonNull(sslSocketFactory, "Missing required sslSocketFactory"); } - //used to manually set token for testing (not used currently, but keep API) - void setOAuth2Token(OAuth2Token token) { - // No direct access to parent token; simulate by overriding newToken in tests if needed. - throw new UnsupportedOperationException("Manual token injection not supported"); - } - - // Expose the provided URL with SSL settings (package scope for tests) + //package scoped for testing ApiConnectionInfo getUrl() { - return new ApiConnectionInfoBuilder(rawUrl.getApiRoot()) - .withSslSocketData(new SslSocketData(sslSocketFactory, CwbiAuthTrustManager.getTrustManager())) - .build(); - } - - // Provide clientId for assertions (package scope for tests) - String getClientId() { - return clientId; - } - - @Override - public ApiConnectionInfo getTokenUrl() { - // Use test SSL data when token URL is requested - return getUrl(); - } - - @Override - public ApiConnectionInfo getAuthUrl() { - // For mock, just return same as token URL - return getUrl(); - } - - @Override - public OAuth2Token newToken() { - // Not used in tests that construct MockCwbiAuthTokenProvider; throw if invoked unexpectedly - throw new UnsupportedOperationException("Mock provider does not fetch real tokens"); + return url; } //package scoped for testing 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 c2bb4d10..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 @@ -50,7 +50,7 @@ void testRetrieveTokenUrl() throws Exception { .withSslSocketData(sslSocketData) .build(); mockWebServer.enqueue(new MockResponse().setBody(readResourceAsString("openIdConfig.json")).setResponseCode(200)); - ApiConnectionInfo tokenUrl = new StaticOidcTokenController(wellKnownEndpoint).retrieveTokenUrl(new ApiConnectionInfoBuilder(baseUrl).withSslSocketData(sslSocketData).build()); + ApiConnectionInfo tokenUrl = new StaticOidcTokenController(wellKnownEndpoint).retrieveTokenUrl(new ApiConnectionInfoBuilder(baseUrl).build()); assertEquals("https://api.example.com/auth/realms/cwbi/protocol/openid-connect/token", tokenUrl.getApiRoot()); } } From f070c9784650954b378048a6a179a7117ccab871 Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Thu, 12 Feb 2026 10:02:13 -0800 Subject: [PATCH 4/5] CWMS-2386 - removed url accessor from mock since the parent has an accessor --- .../cwbi/auth/http/client/MockCwbiAuthTokenProvider.java | 7 ------- .../hec/cwbi/auth/http/client/TestCwbiTokenProvider.java | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) 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 8be86fbb..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 @@ -30,7 +30,6 @@ public class MockCwbiAuthTokenProvider extends OidcAuthTokenProvider { private final SSLSocketFactory sslSocketFactory; - private final ApiConnectionInfo url; /** * Provider for OAuth2Tokens. @@ -41,15 +40,9 @@ public class MockCwbiAuthTokenProvider extends OidcAuthTokenProvider { */ public MockCwbiAuthTokenProvider(ApiConnectionInfo url, String clientId, SSLSocketFactory sslSocketFactory) { super(clientId, url); - this.url = Objects.requireNonNull(url, "Missing required url"); this.sslSocketFactory = Objects.requireNonNull(sslSocketFactory, "Missing required sslSocketFactory"); } - //package scoped for testing - ApiConnectionInfo getUrl() { - return url; - } - //package scoped for testing SSLSocketFactory getSslSocketFactory() { return sslSocketFactory; 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 0ca43a7a..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 @@ -210,7 +210,7 @@ void testConstructor() throws IOException { ApiConnectionInfo url = buildConnectionInfo(); SSLSocketFactory sslSocketFactory = getTestSslSocketFactory(); MockCwbiAuthTokenProvider tokenProvider = new MockCwbiAuthTokenProvider(url, "clientId", sslSocketFactory); - assertEquals(url.getApiRoot(), tokenProvider.getUrl().getApiRoot()); + assertEquals(url.getApiRoot(), tokenProvider.getWellKnownUrl().getApiRoot()); assertEquals("clientId", tokenProvider.getClientId()); assertEquals(sslSocketFactory, tokenProvider.getSslSocketFactory()); } From 31db1b70a4741b74bf215da37c136779213a9f86 Mon Sep 17 00:00:00 2001 From: Bryson Spilman Date: Fri, 13 Feb 2026 10:11:17 -0800 Subject: [PATCH 5/5] CWMS-2386 - Fixes issue with string replacement with identityc --- .../hec/cwbi/auth/http/client/CwbiAuthTokenProvider.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 0fd848ab..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 @@ -56,7 +56,12 @@ public CwbiAuthTokenProvider(ApiConnectionInfo wellKnownUrl, String clientId, SS @Override public ApiConnectionInfo getAuthUrl() { // This is specific to CWBI Direct Grant so this replacement as-is is fine - return new ApiConnectionInfoBuilder(this.getTokenUrl().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(); }