Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,9 @@ A URI that indicates the target service or resource where the client intends to

### `rest.auth.oauth2.token-exchange.audience`

The logical name of the target service where the client intends to use the requested security token. This serves a purpose similar to the resource parameter but with the client providing a logical name for the target service. Optional.
The logical name of the target service where the client intends to use the requested security token. This serves a purpose similar to the resource parameter but with the client providing a logical name for the target service.

Optional. Can be a single value or a comma-separated list of values.

## Client Assertion Settings

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class ConfigUtils {

Expand Down Expand Up @@ -60,14 +59,6 @@ public static boolean requiresUserInteraction(GrantType grantType) {
|| grantType.equals(GrantType.DEVICE_CODE);
}

public static List<String> parseCommaSeparatedList(String text) {
if (text == null || text.isBlank()) {
return List.of();
}
String[] parts = text.trim().split(",");
return Stream.of(parts).map(String::trim).collect(Collectors.toList());
}

public static Map<String, String> prefixedMap(Map<String, String> properties, String prefix) {
return properties.entrySet().stream()
.map(e -> Map.entry(prefix + '.' + e.getKey(), e.getValue()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.nio.file.Path;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
Expand Down Expand Up @@ -128,7 +129,7 @@ default HttpClient newHttpClient() {
* default}.
*/
@WithName(SSL_PROTOCOLS)
Optional<String> getSslProtocols();
Optional<List<String>> getSslProtocols();

/**
* A comma-separated list of SSL cipher suites to use for HTTPS requests. Optional, defaults to
Expand All @@ -138,7 +139,7 @@ default HttpClient newHttpClient() {
* default}.
*/
@WithName(SSL_CIPHER_SUITES)
Optional<String> getSslCipherSuites();
Optional<List<String>> getSslCipherSuites();

/**
* Whether to enable SSL hostname verification for HTTPS requests.
Expand Down Expand Up @@ -251,8 +252,10 @@ default Map<String, String> asMap() {
getProxyPort().ifPresent(p -> properties.put(PREFIX + '.' + PROXY_PORT, String.valueOf(p)));
getProxyUsername().ifPresent(u -> properties.put(PREFIX + '.' + PROXY_USERNAME, u));
getProxyPassword().ifPresent(p -> properties.put(PREFIX + '.' + PROXY_PASSWORD, p));
getSslProtocols().ifPresent(p -> properties.put(PREFIX + '.' + SSL_PROTOCOLS, p));
getSslCipherSuites().ifPresent(c -> properties.put(PREFIX + '.' + SSL_CIPHER_SUITES, c));
getSslProtocols()
.ifPresent(p -> properties.put(PREFIX + '.' + SSL_PROTOCOLS, String.join(",", p)));
getSslCipherSuites()
.ifPresent(c -> properties.put(PREFIX + '.' + SSL_CIPHER_SUITES, String.join(",", c)));
properties.put(
PREFIX + '.' + SSL_HOSTNAME_VERIFICATION_ENABLED,
String.valueOf(isSslHostnameVerificationEnabled()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
import io.smallrye.config.WithName;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* Configuration properties for the <a href="https://datatracker.ietf.org/doc/html/rfc8693">Token
Expand Down Expand Up @@ -173,10 +175,12 @@ public interface TokenExchangeConfig {
/**
* The logical name of the target service where the client intends to use the requested security
* token. This serves a purpose similar to the resource parameter but with the client providing a
* logical name for the target service. Optional.
* logical name for the target service.
*
* <p>Optional. Can be a single value or a comma-separated list of values.
*/
@WithName(AUDIENCE)
Optional<Audience> getAudience();
Optional<List<Audience>> getAudience();

default void validate() {
ConfigValidator validator = new ConfigValidator();
Expand Down Expand Up @@ -206,7 +210,12 @@ default Map<String, String> asMap() {
properties.put(
PREFIX + '.' + REQUESTED_TOKEN_TYPE, getRequestedTokenType().getURI().toString());
getResource().ifPresent(r -> properties.put(PREFIX + '.' + RESOURCE, r.toString()));
getAudience().ifPresent(a -> properties.put(PREFIX + '.' + AUDIENCE, a.getValue()));
getAudience()
.ifPresent(
a ->
properties.put(
PREFIX + '.' + AUDIENCE,
a.stream().map(Audience::getValue).collect(Collectors.joining(","))));
getSubjectTokenConfig()
.forEach((k, v) -> properties.put(PREFIX + '.' + SUBJECT_TOKEN + '.' + k, v));
getActorTokenConfig()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;

import com.dremio.iceberg.authmgr.oauth2.config.AuthorizationCodeConfig;
import com.dremio.iceberg.authmgr.oauth2.config.ConfigUtils;
import com.dremio.iceberg.authmgr.oauth2.config.HttpConfig;
import com.dremio.iceberg.authmgr.tools.immutables.AuthManagerImmutable;
import com.google.errorprone.annotations.FormatMethod;
Expand Down Expand Up @@ -368,12 +367,10 @@ private void configureSslParams(SSLContext sslContext, HttpsParameters params) {
HttpConfig httpConfig = getConfig().getHttpConfig();
httpConfig
.getSslProtocols()
.map(ConfigUtils::parseCommaSeparatedList)
.map(list -> list.toArray(new String[0]))
.ifPresent(sslParameters::setProtocols);
httpConfig
.getSslCipherSuites()
.map(ConfigUtils::parseCommaSeparatedList)
.map(list -> list.toArray(new String[0]))
.ifPresent(sslParameters::setCipherSuites);
params.setSSLParameters(sslParameters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,6 @@ private static TokenExchangeGrant newTokenExchangeGrant(
? TokenTypeURI.ACCESS_TOKEN
: actorToken.getIssuedTokenType(),
tokenExchangeConfig.getRequestedTokenType(),
tokenExchangeConfig.getAudience().map(List::of).orElse(List.of()));
tokenExchangeConfig.getAudience().orElseGet(List::of));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package com.dremio.iceberg.authmgr.oauth2.http;

import com.dremio.iceberg.authmgr.oauth2.config.ConfigUtils;
import com.dremio.iceberg.authmgr.oauth2.config.HttpConfig;
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
import com.nimbusds.oauth2.sdk.http.HTTPResponse;
Expand Down Expand Up @@ -204,12 +203,10 @@ public static HttpClientBuilder createApacheClientBuilder(HttpConfig config) {
sslContext,
config
.getSslProtocols()
.map(ConfigUtils::parseCommaSeparatedList)
.map(list -> list.toArray(new String[0]))
.orElseGet(HttpsSupport::getSystemProtocols),
config
.getSslCipherSuites()
.map(ConfigUtils::parseCommaSeparatedList)
.map(list -> list.toArray(new String[0]))
.orElseGet(HttpsSupport::getSystemCipherSuits),
SSLBufferMode.STATIC,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;

class ConfigUtilsTest {

Expand Down Expand Up @@ -75,12 +74,6 @@ static Stream<Arguments> requiresUserInteraction() {
Arguments.of(GrantType.TOKEN_EXCHANGE, false));
}

@ParameterizedTest
@ValueSource(strings = {"a,b,c", "a, b, c", " a , b , c "})
void parseCommaSeparatedList(String text) {
assertThat(ConfigUtils.parseCommaSeparatedList(text)).containsExactly("a", "b", "c");
}

@Test
void prefixedMap() {
assertThat(ConfigUtils.prefixedMap(Map.of("a", "1", "b", "2"), "prefix"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import com.dremio.iceberg.authmgr.oauth2.config.validator.ConfigValidator;
import com.google.common.collect.ImmutableMap;
import com.nimbusds.oauth2.sdk.id.Audience;
import io.smallrye.config.SmallRyeConfig;
import io.smallrye.config.SmallRyeConfigBuilder;
import io.smallrye.config.common.MapBackedConfigSource;
Expand Down Expand Up @@ -107,4 +108,58 @@ void testAsMap() {
TokenExchangeConfig config = smallRyeConfig.getConfigMapping(TokenExchangeConfig.class, PREFIX);
assertThat(config.asMap()).isEqualTo(properties);
}

@Test
void testAudienceEmpty() {
Map<String, String> properties =
ImmutableMap.<String, String>builder()
.put(PREFIX + '.' + SUBJECT_TOKEN, "subject-token")
.build();
SmallRyeConfig smallRyeConfig =
new SmallRyeConfigBuilder()
.withMapping(TokenExchangeConfig.class, PREFIX)
.withSources(new MapBackedConfigSource("catalog-properties", properties, 1000) {})
.build();
TokenExchangeConfig config = smallRyeConfig.getConfigMapping(TokenExchangeConfig.class, PREFIX);
assertThat(config.getAudience()).isEmpty();
}

@Test
void testSingleAudience() {
Map<String, String> properties =
ImmutableMap.<String, String>builder()
.put(PREFIX + '.' + SUBJECT_TOKEN, "subject-token")
.put(PREFIX + '.' + AUDIENCE, "https://example.com/resource")
.build();
SmallRyeConfig smallRyeConfig =
new SmallRyeConfigBuilder()
.withMapping(TokenExchangeConfig.class, PREFIX)
.withSources(new MapBackedConfigSource("catalog-properties", properties, 1000) {})
.build();
TokenExchangeConfig config = smallRyeConfig.getConfigMapping(TokenExchangeConfig.class, PREFIX);
assertThat(config.getAudience())
.contains(List.of(new Audience("https://example.com/resource")));
}

@Test
void testMultipleAudiences() {
Map<String, String> properties =
ImmutableMap.<String, String>builder()
.put(PREFIX + '.' + SUBJECT_TOKEN, "subject-token")
.put(
PREFIX + '.' + AUDIENCE,
"https://example.com/resource1,https://example.com/resource2")
.build();
SmallRyeConfig smallRyeConfig =
new SmallRyeConfigBuilder()
.withMapping(TokenExchangeConfig.class, PREFIX)
.withSources(new MapBackedConfigSource("catalog-properties", properties, 1000) {})
.build();
TokenExchangeConfig config = smallRyeConfig.getConfigMapping(TokenExchangeConfig.class, PREFIX);
assertThat(config.getAudience())
.contains(
List.of(
new Audience("https://example.com/resource1"),
new Audience("https://example.com/resource2")));
}
}