Skip to content

Commit 03b34bb

Browse files
committed
Cloud Compute - Refactored pkce token providers into abstract base class
1 parent b1c5013 commit 03b34bb

5 files changed

Lines changed: 123 additions & 104 deletions

File tree

cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/AuthCodePkceTokenRequestBuilder.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,10 @@ public void handle(HttpExchange exchange) throws IOException {
158158
.post()
159159
.withBody(formData.buildEncodedString())
160160
.withMediaType(MEDIA_TYPE);
161-
LOGGER.info("Retrieving Token...");
162161
try (HttpRequestResponse response = executor.execute()) {
163162
String body = response.getBody();
164163
if (body != null) {
165164
retVal = OAuth2ObjectMapper.mapJsonToObject(body, OAuth2Token.class);
166-
LOGGER.info("Token retrieved.");
167165
}
168166
}
169167
return retVal;
Lines changed: 3 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
package hec.army.usace.hec.cwbi.auth.http.client;
22

33
import java.io.IOException;
4-
import java.net.URI;
54
import java.util.Objects;
65
import java.util.concurrent.CompletionException;
7-
import java.util.function.Consumer;
86

97
import mil.army.usace.hec.cwms.http.client.ApiConnectionInfo;
10-
import mil.army.usace.hec.cwms.http.client.auth.OAuth2Token;
11-
import mil.army.usace.hec.cwms.http.client.auth.OAuth2TokenProvider;
128

139
/**
1410
* Handle generic OIDC auth based on configuration elements in the .well-known/openid-configuration
@@ -17,89 +13,19 @@
1713
* Defaults to using Authorization Code + PKCE.
1814
* Support should be provided to support alternative flows as a user-at-login decision point.
1915
*/
20-
public class OidcAuthTokenProvider implements OAuth2TokenProvider {
16+
public class OidcAuthTokenProvider extends PkceOAuth2TokenProvider {
2117

22-
private final String clientId;
2318
private final ApiConnectionInfo wellKnownUrl;
19+
private final StaticOidcTokenController wellKnowEndpointController;
2420
private ApiConnectionInfo tokenUrl;
2521
private ApiConnectionInfo authUrl;
26-
private final StaticOidcTokenController wellKnowEndpointController;
27-
private OAuth2Token token = null;
28-
// Default to open browser or print to console for usage, but allow overriding for testing and
29-
// other usages.
30-
private Consumer<URI> authCallback = TokenRequestBuilder.BROWSER_OR_CONSOLE_AUTH_CALLBACK;
31-
32-
protected OidcAuthTokenProvider(String clientId) {
33-
this.clientId = Objects.requireNonNull(clientId, "Missing required client id.");
34-
this.wellKnownUrl = null;
35-
this.wellKnowEndpointController = null;
36-
}
3722

3823
public OidcAuthTokenProvider(String clientId, ApiConnectionInfo wellKnownUrl) {
39-
this.clientId = Objects.requireNonNull(clientId, "Missing required client id.");
24+
super(clientId);
4025
this.wellKnownUrl = Objects.requireNonNull(wellKnownUrl, "Missing required well known Url.");
4126
this.wellKnowEndpointController = new StaticOidcTokenController(wellKnownUrl);
4227
}
4328

44-
@Override
45-
public void clear() {
46-
synchronized (this) {
47-
this.token = null;
48-
}
49-
}
50-
51-
@Override
52-
public Consumer<URI> getAuthCallback() {
53-
return authCallback;
54-
}
55-
56-
@Override
57-
public void setAuthCallback(Consumer<URI> authCallback) {
58-
this.authCallback = authCallback;
59-
}
60-
61-
@Override
62-
public OAuth2Token getToken() throws IOException {
63-
synchronized(this) {
64-
if (token == null) {
65-
token = newToken();
66-
}
67-
return token;
68-
}
69-
}
70-
71-
@Override
72-
public OAuth2Token refreshToken() throws IOException {
73-
synchronized (this) {
74-
token = new RefreshTokenRequestBuilder()
75-
.withRefreshToken(token.getRefreshToken())
76-
.withUrl(getTokenUrl())
77-
.withClientId(clientId)
78-
.fetchToken();
79-
return token;
80-
}
81-
}
82-
83-
@Override
84-
public OAuth2Token newToken() throws IOException {
85-
synchronized (this) {
86-
/**
87-
* It may make sense to allow something to override this usage, however that
88-
* *should* be a user setting. So like additional drop down or something in the gui.
89-
* There are various notes about it in different sections for discussion.
90-
*/
91-
token = new AuthCodePkceTokenRequestBuilder()
92-
.withAuthUrl(getAuthUrl())
93-
.withTokenUrl(getTokenUrl())
94-
.withAuthCallback(authCallback)
95-
.buildRequest()
96-
.withClientId(clientId)
97-
.fetchToken();
98-
return token;
99-
}
100-
101-
}
102-
10329
@Override
10430
public ApiConnectionInfo getAuthUrl() {
10531
if(authUrl == null) {
@@ -132,8 +58,4 @@ ApiConnectionInfo getWellKnownUrl() {
13258
return this.wellKnownUrl;
13359
}
13460

135-
String getClientId() {
136-
return this.clientId;
137-
}
138-
13961
}

cwbi-auth-http-client/src/main/java/hec/army/usace/hec/cwbi/auth/http/client/ParameterizedAuthUrlPkceTokenProvider.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Handle OIDC auth using directly provided Authorization and Token URLs.
88
* This is useful when the Identity Provider does not support OIDC discovery (.well-known/openid-configuration).
99
*/
10-
public class ParameterizedAuthUrlPkceTokenProvider extends OidcAuthTokenProvider {
10+
public class ParameterizedAuthUrlPkceTokenProvider extends PkceOAuth2TokenProvider {
1111

1212
private final ApiConnectionInfo authUrl;
1313
private final ApiConnectionInfo tokenUrl;
@@ -27,9 +27,4 @@ public ApiConnectionInfo getAuthUrl() {
2727
public ApiConnectionInfo getTokenUrl() {
2828
return tokenUrl;
2929
}
30-
31-
@Override
32-
protected synchronized void initializeAuthUrls() {
33-
// No-op as URLs are already provided in the constructor.
34-
}
3530
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2026 Hydrologic Engineering Center
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package hec.army.usace.hec.cwbi.auth.http.client;
25+
26+
import java.io.IOException;
27+
import java.net.URI;
28+
import java.util.Objects;
29+
import java.util.function.Consumer;
30+
31+
import mil.army.usace.hec.cwms.http.client.auth.OAuth2Token;
32+
import mil.army.usace.hec.cwms.http.client.auth.OAuth2TokenProvider;
33+
34+
/**
35+
* Base implementation of {@link OAuth2TokenProvider} that provides the token lifecycle behavior
36+
* shared by all CWBI OIDC based token providers: caching the current token, clearing it,
37+
* refreshing it via a refresh token, and retrieving a brand new token using the
38+
* Authorization Code + PKCE flow.
39+
*
40+
* <p>Subclasses differ only in how they resolve the authorization and token endpoint URLs
41+
* (e.g. via OIDC discovery vs. URLs supplied directly by the caller), so they are only
42+
* responsible for implementing {@link #getAuthUrl()} and {@link #getTokenUrl()}. Subclasses
43+
* that require an entirely different token retrieval mechanism (e.g. direct grant) may still
44+
* override {@link #newToken()}.
45+
*/
46+
public abstract class PkceOAuth2TokenProvider implements OAuth2TokenProvider {
47+
48+
private final String clientId;
49+
private OAuth2Token token = null;
50+
// Default to open browser or print to console for usage, but allow overriding for testing and
51+
// other usages.
52+
private Consumer<URI> authCallback = TokenRequestBuilder.BROWSER_OR_CONSOLE_AUTH_CALLBACK;
53+
54+
protected PkceOAuth2TokenProvider(String clientId) {
55+
this.clientId = Objects.requireNonNull(clientId, "Missing required client id.");
56+
}
57+
58+
@Override
59+
public void clear() {
60+
synchronized (this) {
61+
this.token = null;
62+
}
63+
}
64+
65+
@Override
66+
public Consumer<URI> getAuthCallback() {
67+
return authCallback;
68+
}
69+
70+
@Override
71+
public void setAuthCallback(Consumer<URI> authCallback) {
72+
this.authCallback = authCallback;
73+
}
74+
75+
@Override
76+
public OAuth2Token getToken() throws IOException {
77+
synchronized (this) {
78+
if (token == null) {
79+
token = newToken();
80+
}
81+
return token;
82+
}
83+
}
84+
85+
@Override
86+
public OAuth2Token refreshToken() throws IOException {
87+
synchronized (this) {
88+
token = new RefreshTokenRequestBuilder()
89+
.withRefreshToken(token.getRefreshToken())
90+
.withUrl(getTokenUrl())
91+
.withClientId(clientId)
92+
.fetchToken();
93+
return token;
94+
}
95+
}
96+
97+
@Override
98+
public OAuth2Token newToken() throws IOException {
99+
synchronized (this) {
100+
/**
101+
* It may make sense to allow something to override this usage, however that
102+
* *should* be a user setting. So like additional drop down or something in the gui.
103+
* There are various notes about it in different sections for discussion.
104+
*/
105+
token = new AuthCodePkceTokenRequestBuilder()
106+
.withAuthUrl(getAuthUrl())
107+
.withTokenUrl(getTokenUrl())
108+
.withAuthCallback(authCallback)
109+
.buildRequest()
110+
.withClientId(clientId)
111+
.fetchToken();
112+
return token;
113+
}
114+
}
115+
116+
String getClientId() {
117+
return this.clientId;
118+
}
119+
}

cwbi-auth-http-client/src/test/java/hec/army/usace/hec/cwbi/auth/http/client/TestParameterizedAuthUrlPkceTokenProvider.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,4 @@ void testConstructorAndUrls() {
2626
assertEquals(authUrl, provider.getAuthUrl());
2727
assertEquals(tokenUrl, provider.getTokenUrl());
2828
}
29-
30-
@Test
31-
void testInitializeAuthUrlsNoOp() {
32-
String clientId = "test-client";
33-
ApiConnectionInfo authUrl = new ApiConnectionInfoBuilder("http://auth.example.com").build();
34-
ApiConnectionInfo tokenUrl = new ApiConnectionInfoBuilder("http://token.example.com").build();
35-
36-
ParameterizedAuthUrlPkceTokenProvider provider = new ParameterizedAuthUrlPkceTokenProvider(clientId, authUrl, tokenUrl);
37-
38-
// This should not throw even though wellKnowEndpointController is null in OidcAuthTokenProvider
39-
provider.initializeAuthUrls();
40-
41-
assertEquals(authUrl, provider.getAuthUrl());
42-
assertEquals(tokenUrl, provider.getTokenUrl());
43-
}
4429
}

0 commit comments

Comments
 (0)