Skip to content

Commit 802d471

Browse files
feat(oauth): Support multiple values in the “aud” claim for JWT client assertions (#185)
Co-authored-by: Alexandre Dutra <adutra@apache.org>
1 parent 5fbddba commit 802d471

4 files changed

Lines changed: 54 additions & 10 deletions

File tree

docs/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ The subject of the client assertion JWT. Optional. The default is the client ID.
403403

404404
### `rest.auth.oauth2.client-assertion.jwt.audience`
405405

406-
The audience of the client assertion JWT. Optional. The default is the token endpoint.
406+
The audience of the client assertion JWT. Optional. The default is the token endpoint. Can be a single audience or a comma-separated list of audiences.
407407

408408
### `rest.auth.oauth2.client-assertion.jwt.token-lifespan`
409409

oauth2/core/src/main/java/com/dremio/iceberg/authmgr/oauth2/config/ClientAssertionConfig.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,12 @@ public interface ClientAssertionConfig {
6666
@WithName(SUBJECT)
6767
Optional<Subject> getSubject();
6868

69-
/** The audience of the client assertion JWT. Optional. The default is the token endpoint. */
69+
/**
70+
* The audience of the client assertion JWT. Optional. The default is the token endpoint. Can be a
71+
* single audience or a comma-separated list of audiences.
72+
*/
7073
@WithName(AUDIENCE)
71-
Optional<Audience> getAudience();
74+
Optional<List<Audience>> getAudience();
7275

7376
/** The expiration time of the client assertion JWT. Optional. The default is 5 minutes. */
7477
@WithName(TOKEN_LIFESPAN)
@@ -166,10 +169,15 @@ default void validate() {
166169
}
167170

168171
default Map<String, String> asMap() {
169-
Map<String, String> properties = new HashMap<>();
172+
Map<String, String> properties = new HashMap<String, String>();
170173
getIssuer().ifPresent(i -> properties.put(PREFIX + '.' + ISSUER, i.getValue()));
171174
getSubject().ifPresent(s -> properties.put(PREFIX + '.' + SUBJECT, s.getValue()));
172-
getAudience().ifPresent(a -> properties.put(PREFIX + '.' + AUDIENCE, a.getValue()));
175+
getAudience()
176+
.ifPresent(
177+
a ->
178+
properties.put(
179+
PREFIX + '.' + AUDIENCE,
180+
a.stream().map(Audience::getValue).collect(Collectors.joining(","))));
173181
properties.put(PREFIX + '.' + TOKEN_LIFESPAN, getTokenLifespan().toString());
174182
getAlgorithm().ifPresent(a -> properties.put(PREFIX + '.' + ALGORITHM, a.getName()));
175183
getKeyId().ifPresent(k -> properties.put(PREFIX + '.' + KEY_ID, k));

oauth2/core/src/main/java/com/dremio/iceberg/authmgr/oauth2/flow/AbstractFlow.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -231,18 +231,19 @@ private JWTAssertionDetails createJwtAssertionDetails(URI tokenEndpoint) {
231231
getConfig().getClientAssertionConfig().getSubject().isPresent()
232232
? getConfig().getClientAssertionConfig().getSubject().get()
233233
: new Subject(getConfig().getBasicConfig().getClientId().orElseThrow().getValue());
234-
Audience audience =
235-
getConfig().getClientAssertionConfig().getAudience().isPresent()
236-
? getConfig().getClientAssertionConfig().getAudience().get()
237-
: new Audience(tokenEndpoint);
234+
List<Audience> audiences =
235+
getConfig()
236+
.getClientAssertionConfig()
237+
.getAudience()
238+
.orElseGet(() -> List.of(new Audience(tokenEndpoint)));
238239
Instant issuedAt = getRuntime().getClock().instant();
239240
Instant expiration = issuedAt.plus(getConfig().getClientAssertionConfig().getTokenLifespan());
240241
@SuppressWarnings({"rawtypes", "unchecked"})
241242
Map<String, Object> extraClaims = (Map) getConfig().getClientAssertionConfig().getExtraClaims();
242243
return new JWTAssertionDetails(
243244
issuer,
244245
subject,
245-
List.of(audience),
246+
audiences,
246247
Date.from(expiration),
247248
Date.from(issuedAt),
248249
Date.from(issuedAt),

oauth2/core/src/test/java/com/dremio/iceberg/authmgr/oauth2/config/ClientAssertionConfigTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
2121

2222
import com.dremio.iceberg.authmgr.oauth2.config.validator.ConfigValidator;
23+
import com.nimbusds.oauth2.sdk.id.Audience;
2324
import io.smallrye.config.SmallRyeConfig;
2425
import io.smallrye.config.SmallRyeConfigBuilder;
2526
import io.smallrye.config.common.MapBackedConfigSource;
@@ -149,4 +150,38 @@ void testKeyIdPresent() {
149150
smallRyeConfig.getConfigMapping(ClientAssertionConfig.class, PREFIX);
150151
assertThat(config.getKeyId()).hasValue("my-key-123");
151152
}
153+
154+
@Test
155+
void testAudienceSingleValue() {
156+
Map<String, String> properties =
157+
Map.of(PREFIX + '.' + ClientAssertionConfig.AUDIENCE, "https://example.com");
158+
SmallRyeConfig smallRyeConfig =
159+
new SmallRyeConfigBuilder()
160+
.withMapping(ClientAssertionConfig.class, PREFIX)
161+
.withSources(new MapBackedConfigSource("catalog-properties", properties, 1000) {})
162+
.build();
163+
ClientAssertionConfig config =
164+
smallRyeConfig.getConfigMapping(ClientAssertionConfig.class, PREFIX);
165+
assertThat(config.getAudience()).contains(List.of(new Audience("https://example.com")));
166+
}
167+
168+
@Test
169+
void testAudienceMultipleValues() {
170+
Map<String, String> properties =
171+
Map.of(
172+
PREFIX + '.' + ClientAssertionConfig.AUDIENCE,
173+
"https://auth1.example.com,https://auth2.example.com");
174+
SmallRyeConfig smallRyeConfig =
175+
new SmallRyeConfigBuilder()
176+
.withMapping(ClientAssertionConfig.class, PREFIX)
177+
.withSources(new MapBackedConfigSource("catalog-properties", properties, 1000) {})
178+
.build();
179+
ClientAssertionConfig config =
180+
smallRyeConfig.getConfigMapping(ClientAssertionConfig.class, PREFIX);
181+
assertThat(config.getAudience())
182+
.contains(
183+
List.of(
184+
new Audience("https://auth1.example.com"),
185+
new Audience("https://auth2.example.com")));
186+
}
152187
}

0 commit comments

Comments
 (0)