Skip to content

Commit 4155a7b

Browse files
authored
Preserve interceptor-set signer properties across auth scheme and endpoint resolution (#6961)
* Preserve interceptor-set signer properties across auth scheme and endpoint resolution * Fix benchmark imports after merging from master * Fix test failures after master merge * Update codegen fixture files * Address review comments
1 parent 080d00c commit 4155a7b

12 files changed

Lines changed: 182 additions & 28 deletions

File tree

codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/ClientClassUtils.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,16 @@ static MethodSpec resolveAuthSchemeOptionsMethod(AuthSchemeSpecUtils authSchemeS
371371

372372
ClassName providerInterface = authSchemeSpecUtils.providerInterfaceName();
373373

374-
builder.addStatement("$T authSchemeProvider = $T.isInstanceOf($T.class, "
374+
// Check for request-level authSchemeProvider override
375+
builder.addStatement("$T requestAuthSchemeProvider = request.overrideConfiguration()"
376+
+ ".flatMap(c -> c.authSchemeProvider())"
377+
+ ".map(p -> $T.isInstanceOf($T.class, p, $S))"
378+
+ ".orElse(null)",
379+
providerInterface, Validate.class, providerInterface,
380+
"Expected an instance of " + authSchemeSpecUtils.providerInterfaceName().simpleName());
381+
builder.addStatement("$T authSchemeProvider = requestAuthSchemeProvider != null "
382+
+ "? requestAuthSchemeProvider "
383+
+ ": $T.isInstanceOf($T.class, "
375384
+ "clientConfiguration.option($T.AUTH_SCHEME_PROVIDER), $S)",
376385
providerInterface, Validate.class, providerInterface, SdkClientOption.class,
377386
"Expected an instance of " + authSchemeSpecUtils.providerInterfaceName().simpleName());

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-custom-context-params-async-client-class.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,14 @@ private static List<MetricPublisher> resolveMetricPublishers(SdkClientConfigurat
197197

198198
private List<AuthSchemeOption> resolveAuthSchemeOptions(SdkRequest request, String operationName,
199199
SdkClientConfiguration clientConfiguration) {
200-
FooBarAuthSchemeProvider authSchemeProvider = Validate.isInstanceOf(FooBarAuthSchemeProvider.class,
201-
clientConfiguration.option(SdkClientOption.AUTH_SCHEME_PROVIDER),
202-
"Expected an instance of FooBarAuthSchemeProvider");
200+
FooBarAuthSchemeProvider requestAuthSchemeProvider = request
201+
.overrideConfiguration()
202+
.flatMap(c -> c.authSchemeProvider())
203+
.map(p -> Validate.isInstanceOf(FooBarAuthSchemeProvider.class, p,
204+
"Expected an instance of FooBarAuthSchemeProvider")).orElse(null);
205+
FooBarAuthSchemeProvider authSchemeProvider = requestAuthSchemeProvider != null ? requestAuthSchemeProvider : Validate
206+
.isInstanceOf(FooBarAuthSchemeProvider.class, clientConfiguration.option(SdkClientOption.AUTH_SCHEME_PROVIDER),
207+
"Expected an instance of FooBarAuthSchemeProvider");
203208
FooBarAuthSchemeParams.Builder paramsBuilder = FooBarAuthSchemeParams.builder().operation(operationName);
204209
paramsBuilder.region(clientConfiguration.option(AwsClientOption.AWS_REGION));
205210
List<AuthSchemeOption> options = authSchemeProvider.resolveAuthScheme(paramsBuilder.build());

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/test-query-client-class.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,9 +1095,14 @@ private static List<MetricPublisher> resolveMetricPublishers(SdkClientConfigurat
10951095

10961096
private List<AuthSchemeOption> resolveAuthSchemeOptions(SdkRequest request, String operationName,
10971097
SdkClientConfiguration clientConfiguration) {
1098-
QueryAuthSchemeProvider authSchemeProvider = Validate.isInstanceOf(QueryAuthSchemeProvider.class,
1099-
clientConfiguration.option(SdkClientOption.AUTH_SCHEME_PROVIDER),
1100-
"Expected an instance of QueryAuthSchemeProvider");
1098+
QueryAuthSchemeProvider requestAuthSchemeProvider = request
1099+
.overrideConfiguration()
1100+
.flatMap(c -> c.authSchemeProvider())
1101+
.map(p -> Validate.isInstanceOf(QueryAuthSchemeProvider.class, p,
1102+
"Expected an instance of QueryAuthSchemeProvider")).orElse(null);
1103+
QueryAuthSchemeProvider authSchemeProvider = requestAuthSchemeProvider != null ? requestAuthSchemeProvider : Validate
1104+
.isInstanceOf(QueryAuthSchemeProvider.class, clientConfiguration.option(SdkClientOption.AUTH_SCHEME_PROVIDER),
1105+
"Expected an instance of QueryAuthSchemeProvider");
11011106
QueryAuthSchemeParams.Builder paramsBuilder = QueryAuthSchemeParams.builder().operation(operationName);
11021107
paramsBuilder.region(clientConfiguration.option(AwsClientOption.AWS_REGION));
11031108
List<AuthSchemeOption> options = authSchemeProvider.resolveAuthScheme(paramsBuilder.build());

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ private AwsExecutionContextBuilder() {
165165
ExecutionInterceptorChain executionInterceptorChain =
166166
new ExecutionInterceptorChain(clientConfig.option(SdkClientOption.EXECUTION_INTERCEPTORS));
167167

168+
executionAttributes.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_SNAPSHOT_PRE_INTERCEPTORS,
169+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME));
170+
168171
InterceptorContext interceptorContext = InterceptorContext.builder()
169172
.request(originalRequest)
170173
.asyncRequestBody(executionParams.getAsyncRequestBody())

core/sdk-core/src/main/java/software/amazon/awssdk/core/http/auth/AuthSchemeResolver.java

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.ArrayList;
2020
import java.util.List;
2121
import java.util.Map;
22+
import java.util.Objects;
2223
import java.util.concurrent.CompletableFuture;
2324
import java.util.function.Supplier;
2425
import java.util.stream.Collectors;
@@ -35,6 +36,7 @@
3536
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
3637
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
3738
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
39+
import software.amazon.awssdk.http.auth.spi.signer.SignerProperty;
3840
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
3941
import software.amazon.awssdk.identity.spi.Identity;
4042
import software.amazon.awssdk.identity.spi.IdentityProvider;
@@ -125,21 +127,59 @@ public static SelectedAuthScheme<? extends Identity> selectAuthScheme(
125127

126128
/**
127129
* Merge properties from any pre-existing auth scheme into the selected one.
130+
*
131+
* After auth scheme resolution produces a fresh selectedAuthScheme, this method ensures that any signer properties
132+
* explicitly set by interceptors (e.g., signing region override) take priority over the resolved values.
128133
*/
129134
public static <T extends Identity> SelectedAuthScheme<T> mergePreExistingAuthSchemeProperties(
130135
SelectedAuthScheme<T> selectedAuthScheme,
131136
ExecutionAttributes executionAttributes) {
132137

138+
// The "existing" auth scheme is what's currently on SELECTED_AUTH_SCHEME - potentially modified by interceptors.
133139
SelectedAuthScheme<?> existingAuthScheme =
134140
executionAttributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME);
135141

136142
if (existingAuthScheme == null) {
137143
return selectedAuthScheme;
138144
}
139145

146+
// Snapshot taken before interceptors ran — used to detect what interceptors changed.
147+
SelectedAuthScheme<?> authSchemeBeforeInterceptors =
148+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_SNAPSHOT_PRE_INTERCEPTORS);
149+
150+
// If no interceptor modified the auth scheme option, skip the diff logic. Still merge existing properties with
151+
// putIfAbsent so that properties from the initial placeholder (e.g., REGION_NAME) carry over to the
152+
// freshly resolved scheme.
153+
if (authSchemeBeforeInterceptors != null &&
154+
authSchemeBeforeInterceptors.authSchemeOption() == existingAuthScheme.authSchemeOption()) {
155+
AuthSchemeOption.Builder mergedOption = selectedAuthScheme.authSchemeOption().toBuilder();
156+
existingAuthScheme.authSchemeOption().forEachSignerProperty(mergedOption::putSignerPropertyIfAbsent);
157+
existingAuthScheme.authSchemeOption().forEachIdentityProperty(mergedOption::putIdentityPropertyIfAbsent);
158+
return new SelectedAuthScheme<>(
159+
selectedAuthScheme.identity(),
160+
selectedAuthScheme.signer(),
161+
mergedOption.build()
162+
);
163+
}
164+
165+
// Start with the freshly resolved auth scheme as the base.
140166
AuthSchemeOption.Builder mergedOption = selectedAuthScheme.authSchemeOption().toBuilder();
167+
168+
// For each signer property on the interceptor-modified scheme:
169+
// If the interceptor changed it (differs from pre-interceptor snapshot), apply interceptor override
170+
// If unchanged (same as before interceptors) only add if not already on the resolved scheme
171+
existingAuthScheme.authSchemeOption().forEachSignerProperty(new AuthSchemeOption.SignerPropertyConsumer() {
172+
@Override
173+
public <S> void accept(SignerProperty<S> key, S value) {
174+
if (wasModifiedByInterceptor(authSchemeBeforeInterceptors, key, value)) {
175+
mergedOption.putSignerProperty(key, value);
176+
} else {
177+
mergedOption.putSignerPropertyIfAbsent(key, value);
178+
}
179+
}
180+
});
181+
141182
existingAuthScheme.authSchemeOption().forEachIdentityProperty(mergedOption::putIdentityPropertyIfAbsent);
142-
existingAuthScheme.authSchemeOption().forEachSignerProperty(mergedOption::putSignerPropertyIfAbsent);
143183

144184
return new SelectedAuthScheme<>(
145185
selectedAuthScheme.identity(),
@@ -148,6 +188,65 @@ public static <T extends Identity> SelectedAuthScheme<T> mergePreExistingAuthSch
148188
);
149189
}
150190

191+
/**
192+
* Returns true if the given property value differs from what it was before interceptors ran,
193+
* meaning an interceptor explicitly changed it.
194+
*/
195+
private static <T> boolean wasModifiedByInterceptor(SelectedAuthScheme<?> authSchemeBeforeInterceptors,
196+
SignerProperty<T> key, T currentValue) {
197+
if (authSchemeBeforeInterceptors == null) {
198+
return false;
199+
}
200+
T originalValue = authSchemeBeforeInterceptors.authSchemeOption().signerProperty(key);
201+
return !Objects.equals(originalValue, currentValue);
202+
}
203+
204+
/**
205+
* Re-applies interceptor-modified signer properties onto the current auth scheme.
206+
* Called after endpoint resolution, which may have overwritten properties that interceptors set.
207+
*/
208+
public static void applyInterceptorModifiedProperties(SelectedAuthScheme<?> currentScheme,
209+
SelectedAuthScheme<?> authSchemeBeforeInterceptors,
210+
SelectedAuthScheme<?> afterInterceptors,
211+
ExecutionAttributes attrs) {
212+
if (afterInterceptors == null) {
213+
return;
214+
}
215+
doApplyInterceptorModifiedProperties(currentScheme, authSchemeBeforeInterceptors, afterInterceptors, attrs);
216+
}
217+
218+
@SuppressWarnings("unchecked")
219+
private static <T extends Identity> void doApplyInterceptorModifiedProperties(
220+
SelectedAuthScheme<T> currentScheme,
221+
SelectedAuthScheme<?> authSchemeBeforeInterceptors,
222+
SelectedAuthScheme<?> afterInterceptors,
223+
ExecutionAttributes attrs) {
224+
225+
// Start with the current endpoint resolved auth scheme as the base.
226+
AuthSchemeOption.Builder mergedOption = currentScheme.authSchemeOption().toBuilder();
227+
boolean[] changed = {false};
228+
229+
// For each property on the post-interceptor scheme, check if the interceptor changed it.
230+
// If yes, apply it onto the current scheme.
231+
afterInterceptors.authSchemeOption().forEachSignerProperty(new AuthSchemeOption.SignerPropertyConsumer() {
232+
@Override
233+
public <S> void accept(SignerProperty<S> key, S value) {
234+
if (wasModifiedByInterceptor(authSchemeBeforeInterceptors, key, value)) {
235+
mergedOption.putSignerProperty(key, value);
236+
changed[0] = true;
237+
}
238+
}
239+
});
240+
241+
// Only update SELECTED_AUTH_SCHEME if at least one property was re-applied.
242+
if (changed[0]) {
243+
attrs.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME,
244+
new SelectedAuthScheme<>(currentScheme.identity(),
245+
currentScheme.signer(),
246+
mergedOption.build()));
247+
}
248+
}
249+
151250
private static <T extends Identity> SelectedAuthScheme<T> trySelectAuthScheme(
152251
AuthSchemeOption authOption,
153252
AuthScheme<T> authScheme,

core/sdk-core/src/main/java/software/amazon/awssdk/core/interceptor/SdkInternalExecutionAttribute.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,22 @@ public final class SdkInternalExecutionAttribute extends SdkExecutionAttribute {
212212
public static final ExecutionAttribute<SelectedAuthScheme<?>> SELECTED_AUTH_SCHEME =
213213
new ExecutionAttribute<>("SelectedAuthScheme");
214214

215+
/**
216+
* Snapshot of {@link #SELECTED_AUTH_SCHEME} taken before execution interceptors run.
217+
* Used by {@code AuthSchemeResolver#mergePreExistingAuthSchemeProperties} to detect which signer properties
218+
* were explicitly modified by interceptors (and should therefore override the freshly-resolved values).
219+
*/
220+
public static final ExecutionAttribute<SelectedAuthScheme<?>> AUTH_SCHEME_SNAPSHOT_PRE_INTERCEPTORS =
221+
new ExecutionAttribute<>("AuthSchemeSnapshotPreInterceptors");
222+
223+
/**
224+
* Snapshot of {@link #SELECTED_AUTH_SCHEME} taken after interceptors run but before auth scheme resolution.
225+
* Together with {@link #AUTH_SCHEME_SNAPSHOT_PRE_INTERCEPTORS}, this allows detecting which signer properties
226+
* were explicitly modified by interceptors so they can be re-applied after endpoint resolution.
227+
*/
228+
public static final ExecutionAttribute<SelectedAuthScheme<?>> AUTH_SCHEME_SNAPSHOT_POST_INTERCEPTORS =
229+
new ExecutionAttribute<>("AuthSchemeSnapshotPostInterceptors");
230+
215231
/**
216232
* The supported compression algorithms for an operation, and whether the operation is streaming or not.
217233
*/

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AuthSchemeResolutionStage.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, Re
7777
SelectedAuthScheme<? extends Identity> selectedAuthScheme =
7878
AuthSchemeResolver.selectAuthScheme(authOptions, authSchemes, identityProviders, metricCollector);
7979

80+
executionAttributes.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_SNAPSHOT_POST_INTERCEPTORS,
81+
executionAttributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME));
82+
8083
selectedAuthScheme = AuthSchemeResolver.mergePreExistingAuthSchemeProperties(selectedAuthScheme, executionAttributes);
8184

8285
executionAttributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, selectedAuthScheme);

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/EndpointResolutionStage.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import software.amazon.awssdk.annotations.SdkInternalApi;
2323
import software.amazon.awssdk.core.ClientEndpointProvider;
2424
import software.amazon.awssdk.core.SdkRequest;
25+
import software.amazon.awssdk.core.SelectedAuthScheme;
26+
import software.amazon.awssdk.core.http.auth.AuthSchemeResolver;
2527
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
2628
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
2729
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
@@ -70,6 +72,8 @@ public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, Re
7072
Endpoint endpoint = resolver.resolve(sdkRequest, attrs);
7173
Duration resolveEndpointDuration = Duration.ofNanos(System.nanoTime() - resolveEndpointStart);
7274

75+
reapplyInterceptorModifiedAuthProperties(attrs);
76+
7377
MetricCollector metricCollector = attrs.getAttribute(SdkExecutionAttribute.API_CALL_METRIC_COLLECTOR);
7478
if (metricCollector != null) {
7579
metricCollector.reportMetric(CoreMetric.ENDPOINT_RESOLVE_DURATION, resolveEndpointDuration);
@@ -140,4 +144,21 @@ private static String combinePath(String clientEndpointPath, String requestPath,
140144
String requestPathWithClientPathRemoved = StringUtils.replaceOnce(requestPath, clientEndpointPath, "");
141145
return SdkHttpUtils.appendUri(resolvedUriPath, requestPathWithClientPathRemoved);
142146
}
147+
148+
private static void reapplyInterceptorModifiedAuthProperties(ExecutionAttributes attrs) {
149+
SelectedAuthScheme<?> currentScheme = attrs.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME);
150+
if (currentScheme == null) {
151+
return;
152+
}
153+
SelectedAuthScheme<?> beforeInterceptors =
154+
attrs.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_SNAPSHOT_PRE_INTERCEPTORS);
155+
156+
SelectedAuthScheme<?> afterInterceptors =
157+
attrs.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_SNAPSHOT_POST_INTERCEPTORS);
158+
if (afterInterceptors == null) {
159+
return;
160+
}
161+
162+
AuthSchemeResolver.applyInterceptorModifiedProperties(currentScheme, beforeInterceptors, afterInterceptors, attrs);
163+
}
143164
}

services/s3/src/test/java/software/amazon/awssdk/services/s3/ExecutionAttributeBackwardsCompatibilityTest.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,7 @@ public void canSetSignerExecutionAttributes_beforeExecution() {
7272
public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) {
7373
attributeModifications.accept(executionAttributes);
7474
}
75-
},
76-
AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, // Endpoint rules override signing name
77-
AwsSignerExecutionAttribute.SIGNING_REGION, // Endpoint rules override signing region
78-
AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE); // Endpoint rules override double-url-encode
75+
});
7976
}
8077

8178
@Test

0 commit comments

Comments
 (0)