Skip to content

Commit 959c394

Browse files
alextwoodsdavidh44
andauthored
Add support for overriding authSchemeProvider per request (#6892)
* Add support for overriding authSchemeProvider per request * Update docs * Add functional test * Rename and make more functional --------- Co-authored-by: David Ho <70000000+davidh44@users.noreply.github.com>
1 parent 8ff786e commit 959c394

6 files changed

Lines changed: 469 additions & 4 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Add support for overriding authSchemeProvider per request."
6+
}

core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilder.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,9 @@ private static void putAuthSchemeResolutionAttributes(ExecutionAttributes execut
269269
SdkClientConfiguration clientConfig,
270270
SdkRequest originalRequest) {
271271

272-
// TODO(request-override auth scheme feature): When request-level auth scheme provider is added, use the request-level
273-
// auth scheme provider if the customer specified an override, otherwise fall back to the one on the client.
274-
AuthSchemeProvider authSchemeProvider = clientConfig.option(SdkClientOption.AUTH_SCHEME_PROVIDER);
272+
// Use the request-level auth scheme provider if the customer specified an override, otherwise fall back to the one
273+
// on the client.
274+
AuthSchemeProvider authSchemeProvider = resolveAuthSchemeProvider(originalRequest, clientConfig);
275275

276276
// Use auth schemes that the user specified at the request level with
277277
// preference over those on the client.
@@ -360,6 +360,19 @@ private static EndpointProvider resolveEndpointProvider(SdkRequest request,
360360
.orElse(clientConfig.option(SdkClientOption.ENDPOINT_PROVIDER));
361361
}
362362

363+
/**
364+
* Resolves the auth scheme provider, with the request override configuration taking precedence over the provided client
365+
* configuration.
366+
*
367+
* @return The auth scheme provider that will be used by the SDK to resolve auth schemes.
368+
*/
369+
private static AuthSchemeProvider resolveAuthSchemeProvider(SdkRequest request,
370+
SdkClientConfiguration clientConfig) {
371+
return request.overrideConfiguration()
372+
.flatMap(RequestOverrideConfiguration::authSchemeProvider)
373+
.orElse(clientConfig.option(SdkClientOption.AUTH_SCHEME_PROVIDER));
374+
}
375+
363376
private static <InputT extends SdkRequest, OutputT extends SdkResponse> BusinessMetricCollection
364377
resolveUserAgentBusinessMetrics(SdkClientConfiguration clientConfig,
365378
ClientExecutionParams<InputT, OutputT> executionParams) {

core/aws-core/src/test/java/software/amazon/awssdk/awscore/internal/AwsExecutionContextBuilderTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme;
7373
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
7474
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
75+
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider;
7576
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
7677
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
7778
import software.amazon.awssdk.identity.spi.IdentityProvider;
@@ -557,6 +558,51 @@ public void invokeInterceptorsAndCreateExecutionContext_withExplicitHttpClientFa
557558
assertThat(businessMetrics.recordedMetrics()).contains("AL");
558559
}
559560

561+
@Test
562+
public void invokeInterceptorsAndCreateExecutionContext_authSchemeProviderRequestOverride_usesRequestOverride() {
563+
AuthSchemeProvider clientAuthSchemeProvider = mock(AuthSchemeProvider.class);
564+
AuthSchemeProvider requestAuthSchemeProvider = mock(AuthSchemeProvider.class);
565+
566+
SdkClientConfiguration clientConfig = testClientConfiguration()
567+
.option(SdkClientOption.AUTH_SCHEME_PROVIDER, clientAuthSchemeProvider)
568+
.build();
569+
570+
Optional overrideConfiguration =
571+
Optional.of(AwsRequestOverrideConfiguration.builder()
572+
.authSchemeProvider(requestAuthSchemeProvider)
573+
.build());
574+
when(sdkRequest.overrideConfiguration()).thenReturn(overrideConfiguration);
575+
576+
ClientExecutionParams<SdkRequest, SdkResponse> executionParams = clientExecutionParams();
577+
578+
ExecutionContext executionContext =
579+
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams, clientConfig);
580+
581+
AuthSchemeProvider actualProvider =
582+
executionContext.executionAttributes().getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER);
583+
584+
assertThat(actualProvider).isSameAs(requestAuthSchemeProvider);
585+
}
586+
587+
@Test
588+
public void invokeInterceptorsAndCreateExecutionContext_noAuthSchemeProviderRequestOverride_usesClientProvider() {
589+
AuthSchemeProvider clientAuthSchemeProvider = mock(AuthSchemeProvider.class);
590+
591+
SdkClientConfiguration clientConfig = testClientConfiguration()
592+
.option(SdkClientOption.AUTH_SCHEME_PROVIDER, clientAuthSchemeProvider)
593+
.build();
594+
595+
ClientExecutionParams<SdkRequest, SdkResponse> executionParams = clientExecutionParams();
596+
597+
ExecutionContext executionContext =
598+
AwsExecutionContextBuilder.invokeInterceptorsAndCreateExecutionContext(executionParams, clientConfig);
599+
600+
AuthSchemeProvider actualProvider =
601+
executionContext.executionAttributes().getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER);
602+
603+
assertThat(actualProvider).isSameAs(clientAuthSchemeProvider);
604+
}
605+
560606
private ClientExecutionParams<SdkRequest, SdkResponse> clientExecutionParams() {
561607
return clientExecutionParams(sdkRequest);
562608
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/RequestOverrideConfiguration.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
3434
import software.amazon.awssdk.core.signer.Signer;
3535
import software.amazon.awssdk.endpoints.EndpointProvider;
36+
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider;
3637
import software.amazon.awssdk.metrics.MetricPublisher;
3738
import software.amazon.awssdk.utils.CollectionUtils;
3839
import software.amazon.awssdk.utils.Validate;
@@ -53,6 +54,7 @@ public abstract class RequestOverrideConfiguration {
5354
private final List<MetricPublisher> metricPublishers;
5455
private final ExecutionAttributes executionAttributes;
5556
private final EndpointProvider endpointProvider;
57+
private final AuthSchemeProvider authSchemeProvider;
5658
private final CompressionConfiguration compressionConfiguration;
5759
private final List<SdkPlugin> plugins;
5860

@@ -66,6 +68,7 @@ protected RequestOverrideConfiguration(Builder<?> builder) {
6668
this.metricPublishers = Collections.unmodifiableList(new ArrayList<>(builder.metricPublishers()));
6769
this.executionAttributes = ExecutionAttributes.unmodifiableExecutionAttributes(builder.executionAttributes());
6870
this.endpointProvider = builder.endpointProvider();
71+
this.authSchemeProvider = builder.authSchemeProvider();
6972
this.compressionConfiguration = builder.compressionConfiguration();
7073
this.plugins = Collections.unmodifiableList(new ArrayList<>(builder.plugins()));
7174
}
@@ -177,6 +180,14 @@ public Optional<EndpointProvider> endpointProvider() {
177180
return Optional.ofNullable(endpointProvider);
178181
}
179182

183+
/**
184+
* Returns the auth scheme provider for resolving the auth scheme for this request. This supersedes the
185+
* auth scheme provider set on the client.
186+
*/
187+
public Optional<AuthSchemeProvider> authSchemeProvider() {
188+
return Optional.ofNullable(authSchemeProvider);
189+
}
190+
180191
/**
181192
* Returns the compression configuration object, if present, which includes options to enable/disable compression and set
182193
* the minimum compression threshold. This compression config object supersedes the compression config object set on the
@@ -204,6 +215,7 @@ public boolean equals(Object o) {
204215
Objects.equals(metricPublishers, that.metricPublishers) &&
205216
Objects.equals(executionAttributes, that.executionAttributes) &&
206217
Objects.equals(endpointProvider, that.endpointProvider) &&
218+
Objects.equals(authSchemeProvider, that.authSchemeProvider) &&
207219
Objects.equals(compressionConfiguration, that.compressionConfiguration) &&
208220
Objects.equals(plugins, that.plugins);
209221
}
@@ -220,6 +232,7 @@ public int hashCode() {
220232
hashCode = 31 * hashCode + Objects.hashCode(metricPublishers);
221233
hashCode = 31 * hashCode + Objects.hashCode(executionAttributes);
222234
hashCode = 31 * hashCode + Objects.hashCode(endpointProvider);
235+
hashCode = 31 * hashCode + Objects.hashCode(authSchemeProvider);
223236
hashCode = 31 * hashCode + Objects.hashCode(compressionConfiguration);
224237
hashCode = 31 * hashCode + Objects.hashCode(plugins);
225238
return hashCode;
@@ -456,13 +469,57 @@ default B putRawQueryParameter(String name, String value) {
456469
* over the endpointProvider set on the client while resolving the endpoint for the requests.
457470
* If this value is null, then the client level endpointProvider is used for resolving the endpoint.
458471
*
459-
* @param endpointProvider Endpoint Provider that will override the resolving the endpoint for the request.
472+
* @param endpointProvider Endpoint Provider that will override client's configured or default EndpointProvider
473+
* for the request.
460474
* @return This object for method chaining
461475
*/
462476
B endpointProvider(EndpointProvider endpointProvider);
463477

464478
EndpointProvider endpointProvider();
465479

480+
/**
481+
* Sets the auth scheme provider to use for resolving the auth scheme of the request. This auth scheme provider gets
482+
* priority over the auth scheme provider set on the client while resolving the auth scheme for the requests.
483+
* If this value is null, then the client level auth scheme provider is used for resolving the auth scheme.
484+
*
485+
* <p>Example — overriding the signing region for a single request:
486+
* {@snippet :
487+
* // Create a custom auth scheme provider that overrides the signing region.
488+
* // This wraps the default provider and modifies the resolved auth scheme options.
489+
* S3AuthSchemeProvider defaultProvider = S3AuthSchemeProvider.defaultProvider();
490+
*
491+
* S3AuthSchemeProvider customRegionProvider = params -> {
492+
* List<AuthSchemeOption> options = defaultProvider.resolveAuthScheme(params);
493+
* return options.stream()
494+
* .map(option -> option.toBuilder()
495+
* .putSignerProperty(AwsV4HttpSigner.REGION_NAME, "eu-west-1")
496+
* .build())
497+
* .collect(Collectors.toList());
498+
* };
499+
*
500+
* S3Client s3 = S3Client.builder()
501+
* .region(Region.US_EAST_1)
502+
* .build();
503+
*
504+
* // Override the auth scheme provider for a single request
505+
* GetObjectResponse response = s3.getObject(
506+
* GetObjectRequest.builder()
507+
* .bucket("my-bucket")
508+
* .key("my-key")
509+
* .overrideConfiguration(c -> c.authSchemeProvider(customRegionProvider))
510+
* .build(),
511+
* ResponseTransformer.toBytes()
512+
* );
513+
* }
514+
*
515+
* @param authSchemeProvider Auth scheme provider that will override the client's configured or default
516+
* AuthSchemeProvider for the request.
517+
* @return This object for method chaining
518+
*/
519+
B authSchemeProvider(AuthSchemeProvider authSchemeProvider);
520+
521+
AuthSchemeProvider authSchemeProvider();
522+
466523
/**
467524
* Sets the {@link CompressionConfiguration} for this request. The order of precedence, from highest to lowest,
468525
* for this setting is: 1) Per request configuration 2) Client configuration 3) Environment variables 4) Profile setting.
@@ -546,6 +603,7 @@ protected abstract static class BuilderImpl<B extends Builder> implements Builde
546603
private List<MetricPublisher> metricPublishers = new ArrayList<>();
547604
private ExecutionAttributes.Builder executionAttributesBuilder = ExecutionAttributes.builder();
548605
private EndpointProvider endpointProvider;
606+
private AuthSchemeProvider authSchemeProvider;
549607
private CompressionConfiguration compressionConfiguration;
550608
private List<SdkPlugin> plugins = new ArrayList<>();
551609

@@ -563,6 +621,7 @@ protected BuilderImpl(RequestOverrideConfiguration sdkRequestOverrideConfig) {
563621
metricPublishers(sdkRequestOverrideConfig.metricPublishers());
564622
executionAttributes(sdkRequestOverrideConfig.executionAttributes());
565623
endpointProvider(sdkRequestOverrideConfig.endpointProvider);
624+
authSchemeProvider(sdkRequestOverrideConfig.authSchemeProvider);
566625
compressionConfiguration(sdkRequestOverrideConfig.compressionConfiguration);
567626
plugins(sdkRequestOverrideConfig.plugins);
568627
}
@@ -734,6 +793,21 @@ public EndpointProvider endpointProvider() {
734793
return endpointProvider;
735794
}
736795

796+
@Override
797+
public B authSchemeProvider(AuthSchemeProvider authSchemeProvider) {
798+
this.authSchemeProvider = authSchemeProvider;
799+
return (B) this;
800+
}
801+
802+
public void setAuthSchemeProvider(AuthSchemeProvider authSchemeProvider) {
803+
authSchemeProvider(authSchemeProvider);
804+
}
805+
806+
@Override
807+
public AuthSchemeProvider authSchemeProvider() {
808+
return authSchemeProvider;
809+
}
810+
737811
@Override
738812
public B compressionConfiguration(CompressionConfiguration compressionConfiguration) {
739813
this.compressionConfiguration = compressionConfiguration;

core/sdk-core/src/test/java/software/amazon/awssdk/core/RequestOverrideConfigurationTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
3535
import software.amazon.awssdk.core.signer.Signer;
3636
import software.amazon.awssdk.http.SdkHttpFullRequest;
37+
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeProvider;
3738
import software.amazon.awssdk.metrics.MetricPublisher;
3839
import software.amazon.awssdk.utils.ImmutableMap;
3940

@@ -75,6 +76,7 @@ public void toBuilder_maximal() {
7576
.apiCallTimeout(Duration.ofSeconds(1))
7677
.apiCallAttemptTimeout(Duration.ofSeconds(1))
7778
.signer(new NoOpSigner())
79+
.authSchemeProvider(mock(AuthSchemeProvider.class))
7880
.executionAttributes(ExecutionAttributes.builder().put(testAttribute, expectedValue).build())
7981
.addMetricPublisher(mock(MetricPublisher.class))
8082
.build();
@@ -330,6 +332,46 @@ public void testConfigurationEquals() {
330332
assertThat(request1Override).isNotEqualTo(null);
331333
}
332334

335+
@Test
336+
public void authSchemeProvider_setOnBuilder_isPresent() {
337+
AuthSchemeProvider provider = mock(AuthSchemeProvider.class);
338+
RequestOverrideConfiguration configuration = SdkRequestOverrideConfiguration.builder()
339+
.authSchemeProvider(provider)
340+
.build();
341+
342+
assertThat(configuration.authSchemeProvider()).isPresent();
343+
assertThat(configuration.authSchemeProvider().get()).isSameAs(provider);
344+
}
345+
346+
@Test
347+
public void authSchemeProvider_notSet_isEmpty() {
348+
RequestOverrideConfiguration configuration = SdkRequestOverrideConfiguration.builder()
349+
.build();
350+
351+
assertThat(configuration.authSchemeProvider()).isEmpty();
352+
}
353+
354+
@Test
355+
public void authSchemeProvider_setToNull_isEmpty() {
356+
RequestOverrideConfiguration configuration = SdkRequestOverrideConfiguration.builder()
357+
.authSchemeProvider(null)
358+
.build();
359+
360+
assertThat(configuration.authSchemeProvider()).isEmpty();
361+
}
362+
363+
@Test
364+
public void authSchemeProvider_toBuilder_roundTrips() {
365+
AuthSchemeProvider provider = mock(AuthSchemeProvider.class);
366+
RequestOverrideConfiguration configuration = SdkRequestOverrideConfiguration.builder()
367+
.authSchemeProvider(provider)
368+
.build();
369+
370+
RequestOverrideConfiguration rebuilt = configuration.toBuilder().build();
371+
assertThat(rebuilt.authSchemeProvider()).isPresent();
372+
assertThat(rebuilt.authSchemeProvider().get()).isSameAs(provider);
373+
}
374+
333375
private static class NoOpSigner implements Signer {
334376

335377
@Override

0 commit comments

Comments
 (0)