-
Notifications
You must be signed in to change notification settings - Fork 1k
Preserve interceptor-set signer properties across auth scheme and endpoint resolution #6961
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/master/core-interceptors-migration
Are you sure you want to change the base?
Changes from all commits
6ab9802
bd27de1
6d725f2
258bd85
e092ae8
2d96dc2
1b3d1ed
18e02f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ | |
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.function.Supplier; | ||
| import java.util.stream.Collectors; | ||
|
|
@@ -35,6 +36,7 @@ | |
| import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; | ||
| import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; | ||
| import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; | ||
| import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; | ||
| import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; | ||
| import software.amazon.awssdk.identity.spi.Identity; | ||
| import software.amazon.awssdk.identity.spi.IdentityProvider; | ||
|
|
@@ -125,21 +127,51 @@ public static SelectedAuthScheme<? extends Identity> selectAuthScheme( | |
|
|
||
| /** | ||
| * Merge properties from any pre-existing auth scheme into the selected one. | ||
| * | ||
| * After auth scheme resolution produces a fresh selectedAuthScheme, this method ensures that any signer properties | ||
| * explicitly set by interceptors (e.g., signing region override) take priority over the resolved values. | ||
| */ | ||
| public static <T extends Identity> SelectedAuthScheme<T> mergePreExistingAuthSchemeProperties( | ||
| SelectedAuthScheme<T> selectedAuthScheme, | ||
| ExecutionAttributes executionAttributes) { | ||
|
|
||
| // The "existing" auth scheme is what's currently on SELECTED_AUTH_SCHEME - potentially modified by interceptors. | ||
| SelectedAuthScheme<?> existingAuthScheme = | ||
| executionAttributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); | ||
|
|
||
| if (existingAuthScheme == null) { | ||
| return selectedAuthScheme; | ||
| } | ||
|
|
||
| // Snapshot taken before interceptors ran — used to detect what interceptors changed. | ||
| SelectedAuthScheme<?> authSchemeBeforeInterceptors = | ||
| executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_SNAPSHOT_PRE_INTERCEPTORS); | ||
|
|
||
| // If the auth scheme option is the same object reference before and after | ||
| // interceptors, no interceptor modified it — skip the merge entirely. | ||
| if (authSchemeBeforeInterceptors != null && | ||
| authSchemeBeforeInterceptors.authSchemeOption() == existingAuthScheme.authSchemeOption()) { | ||
| return selectedAuthScheme; | ||
| } | ||
|
|
||
| // Start with the freshly resolved auth scheme as the base. | ||
| AuthSchemeOption.Builder mergedOption = selectedAuthScheme.authSchemeOption().toBuilder(); | ||
|
|
||
| // For each signer property on the interceptor-modified scheme: | ||
| // If the interceptor changed it (differs from pre-interceptor snapshot), apply interceptor override | ||
| // If unchanged (same as before interceptors) only add if not already on the resolved scheme | ||
| existingAuthScheme.authSchemeOption().forEachSignerProperty(new AuthSchemeOption.SignerPropertyConsumer() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add some inline comments explaining each step. This class is too complex to understand...
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comments explaining that. |
||
| @Override | ||
| public <S> void accept(SignerProperty<S> key, S value) { | ||
| if (wasModifiedByInterceptor(authSchemeBeforeInterceptors, key, value)) { | ||
| mergedOption.putSignerProperty(key, value); | ||
| } else { | ||
| mergedOption.putSignerPropertyIfAbsent(key, value); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| existingAuthScheme.authSchemeOption().forEachIdentityProperty(mergedOption::putIdentityPropertyIfAbsent); | ||
| existingAuthScheme.authSchemeOption().forEachSignerProperty(mergedOption::putSignerPropertyIfAbsent); | ||
|
|
||
| return new SelectedAuthScheme<>( | ||
| selectedAuthScheme.identity(), | ||
|
|
@@ -148,6 +180,62 @@ public static <T extends Identity> SelectedAuthScheme<T> mergePreExistingAuthSch | |
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Returns true if the given property value differs from what it was before interceptors ran, | ||
| * meaning an interceptor explicitly changed it. | ||
| */ | ||
| private static <T> boolean wasModifiedByInterceptor(SelectedAuthScheme<?> authSchemeBeforeInterceptors, | ||
| SignerProperty<T> key, T currentValue) { | ||
| T originalValue = authSchemeBeforeInterceptors.authSchemeOption().signerProperty(key); | ||
| return !Objects.equals(originalValue, currentValue); | ||
| } | ||
|
|
||
| /** | ||
| * Re-applies interceptor-modified signer properties onto the current auth scheme. | ||
| * Called after endpoint resolution, which may have overwritten properties that interceptors set. | ||
| */ | ||
| public static void applyInterceptorModifiedProperties(SelectedAuthScheme<?> currentScheme, | ||
| SelectedAuthScheme<?> authSchemeBeforeInterceptors, | ||
| SelectedAuthScheme<?> afterInterceptors, | ||
| ExecutionAttributes attrs) { | ||
| if (afterInterceptors == null) { | ||
| return; | ||
| } | ||
| doApplyInterceptorModifiedProperties(currentScheme, authSchemeBeforeInterceptors, afterInterceptors, attrs); | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| private static <T extends Identity> void doApplyInterceptorModifiedProperties( | ||
| SelectedAuthScheme<T> currentScheme, | ||
| SelectedAuthScheme<?> authSchemeBeforeInterceptors, | ||
| SelectedAuthScheme<?> afterInterceptors, | ||
| ExecutionAttributes attrs) { | ||
|
|
||
| // Start with the current endpoint resolved auth scheme as the base. | ||
| AuthSchemeOption.Builder mergedOption = currentScheme.authSchemeOption().toBuilder(); | ||
| boolean[] changed = {false}; | ||
|
|
||
| // For each property on the post-interceptor scheme, check if the interceptor changed it. | ||
| // If yes, apply it onto the current scheme. | ||
| afterInterceptors.authSchemeOption().forEachSignerProperty(new AuthSchemeOption.SignerPropertyConsumer() { | ||
| @Override | ||
| public <S> void accept(SignerProperty<S> key, S value) { | ||
| if (wasModifiedByInterceptor(authSchemeBeforeInterceptors, key, value)) { | ||
| mergedOption.putSignerProperty(key, value); | ||
| changed[0] = true; | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| // Only update SELECTED_AUTH_SCHEME if at least one property was re-applied. | ||
| if (changed[0]) { | ||
| attrs.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, | ||
| new SelectedAuthScheme<>(currentScheme.identity(), | ||
| currentScheme.signer(), | ||
| mergedOption.build())); | ||
| } | ||
| } | ||
|
|
||
| private static <T extends Identity> SelectedAuthScheme<T> trySelectAuthScheme( | ||
| AuthSchemeOption authOption, | ||
| AuthScheme<T> authScheme, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -72,10 +72,7 @@ public void canSetSignerExecutionAttributes_beforeExecution() { | |
| public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { | ||
| attributeModifications.accept(executionAttributes); | ||
| } | ||
| }, | ||
| AwsSignerExecutionAttribute.SERVICE_SIGNING_NAME, // Endpoint rules override signing name | ||
| AwsSignerExecutionAttribute.SIGNING_REGION, // Endpoint rules override signing region | ||
| AwsSignerExecutionAttribute.SIGNER_DOUBLE_URL_ENCODE); // Endpoint rules override double-url-encode | ||
|
Comment on lines
-76
to
-78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did we remove those three lines?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously (before our changes), these three properties were already not working in beforeExecution, endpoint rules would overwrite them. That's why they were excluded from the test. But other stages modifyRequest, modifyHttpRequest used to work because they ran after endpoint rules. Now with our changes it works correctly for beforeExecution as well, so removed those exclusions.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, could it break users who rely on the buggy behavior?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, I don't think so. If the user is explicitly setting those attributes in interceptors, previously we didn't use them, we used the values resolved by endpoint rule. But now with our fix, we will use the values. If they are explicitly setting they are intending to use them.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, I was thinking about the case where an attribute was set to a wrong value by accident, no one has noticed it because the application is working (SDK not honoring it) and it'll start to fail once we start to honor it
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should let it prevent us from fixing it. Just need to document it somewhere... probably changelog entry?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Hmm yeah right, I guess that would be rare. But I'll make sure to document it in changelog. |
||
| }); | ||
| } | ||
|
|
||
| @Test | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have we benchmarked this? I'm a little concerned about the performance impact - I'm wondering if theres some way we could at least short circuit this when no interceptors have run. Or can we do an identity check on the authSchemeOption (like beforeInterceptors.authSchemeOption() == selectedAuthScheme.authSchemeOption() ) -since they are immutable, if you change them, it requires building a new object, so could be a quick way to see if any mutation has happened?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! Added the check to skips the merge when no interceptors have run.