Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ private AwsExecutionContextBuilder() {
ExecutionInterceptorChain executionInterceptorChain =
new ExecutionInterceptorChain(clientConfig.option(SdkClientOption.EXECUTION_INTERCEPTORS));

executionAttributes.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_BEFORE_INTERCEPTORS,
executionAttributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME));

InterceptorContext interceptorContext = InterceptorContext.builder()
.request(originalRequest)
.asyncRequestBody(executionParams.getAsyncRequestBody())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -137,9 +139,28 @@ public static <T extends Identity> SelectedAuthScheme<T> mergePreExistingAuthSch
return selectedAuthScheme;
}

SelectedAuthScheme<?> beforeInterceptors =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we rename it to authSchemeBeforeInterceptors?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_BEFORE_INTERCEPTORS);

if (beforeInterceptors != null &&
beforeInterceptors.authSchemeOption() == existingAuthScheme.authSchemeOption()) {
return selectedAuthScheme;
}

AuthSchemeOption.Builder mergedOption = selectedAuthScheme.authSchemeOption().toBuilder();

existingAuthScheme.authSchemeOption().forEachSignerProperty(new AuthSchemeOption.SignerPropertyConsumer() {

Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
Contributor Author

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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(beforeInterceptors, 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(),
Expand All @@ -148,6 +169,57 @@ public static <T extends Identity> SelectedAuthScheme<T> mergePreExistingAuthSch
);
}

private static <T> boolean wasModifiedByInterceptor(SelectedAuthScheme<?> beforeInterceptors,
SignerProperty<T> key, T currentValue) {
if (beforeInterceptors == null) {
return true;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: why we return true if there are no auth schemes before interceptors?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, actually beforeInterceptors can never be null, its always set in AwsExecutionContextBuilder, so whatever we return doesn't matter, maybe its better to remove the check since it never happen. Will remove it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the check

T originalValue = beforeInterceptors.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<?> beforeInterceptors,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: formatting is a bit off here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed it

SelectedAuthScheme<?> afterInterceptors,
ExecutionAttributes attrs) {
if (afterInterceptors == null) {
return;
}
doApplyInterceptorModifiedProperties(currentScheme, beforeInterceptors, afterInterceptors, attrs);
}

@SuppressWarnings("unchecked")
private static <T extends Identity> void doApplyInterceptorModifiedProperties(
SelectedAuthScheme<T> currentScheme,
SelectedAuthScheme<?> beforeInterceptors,
SelectedAuthScheme<?> afterInterceptors,
ExecutionAttributes attrs) {

AuthSchemeOption.Builder mergedOption = currentScheme.authSchemeOption().toBuilder();
boolean[] changed = {false};

afterInterceptors.authSchemeOption().forEachSignerProperty(new AuthSchemeOption.SignerPropertyConsumer() {
@Override
public <S> void accept(SignerProperty<S> key, S value) {
if (wasModifiedByInterceptor(beforeInterceptors, key, value)) {
mergedOption.putSignerProperty(key, value);
changed[0] = true;
}
}
});

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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,22 @@ public final class SdkInternalExecutionAttribute extends SdkExecutionAttribute {
public static final ExecutionAttribute<SelectedAuthScheme<?>> SELECTED_AUTH_SCHEME =
new ExecutionAttribute<>("SelectedAuthScheme");

/**
* Snapshot of {@link #SELECTED_AUTH_SCHEME} taken before execution interceptors run.
* Used by {@code AuthSchemeResolver#mergePreExistingAuthSchemeProperties} to detect which signer properties
* were explicitly modified by interceptors (and should therefore override the freshly-resolved values).
*/
public static final ExecutionAttribute<SelectedAuthScheme<?>> AUTH_SCHEME_BEFORE_INTERCEPTORS =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggesting renaming it to AUTH_SCHEME_SNAPSHOT_PRE_INTERCEPTORS and AUTH_SCHEME_SNAPSHOT_POST_INTERCEPTORS

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed the fields.

new ExecutionAttribute<>("AuthSchemeBeforeInterceptors");

/**
* Snapshot of {@link #SELECTED_AUTH_SCHEME} taken after interceptors run but before auth scheme resolution.
* Together with {@link #AUTH_SCHEME_BEFORE_INTERCEPTORS}, this allows detecting which signer properties
* were explicitly modified by interceptors so they can be re-applied after endpoint resolution.
*/
public static final ExecutionAttribute<SelectedAuthScheme<?>> AUTH_SCHEME_AFTER_INTERCEPTORS =
new ExecutionAttribute<>("AuthSchemeAfterInterceptors");

/**
* The supported compression algorithms for an operation, and whether the operation is streaming or not.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, Re
SelectedAuthScheme<? extends Identity> selectedAuthScheme =
AuthSchemeResolver.selectAuthScheme(authOptions, authSchemes, identityProviders, metricCollector);

executionAttributes.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_AFTER_INTERCEPTORS,
executionAttributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME));

selectedAuthScheme = AuthSchemeResolver.mergePreExistingAuthSchemeProperties(selectedAuthScheme, executionAttributes);

executionAttributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, selectedAuthScheme);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.core.ClientEndpointProvider;
import software.amazon.awssdk.core.SdkRequest;
import software.amazon.awssdk.core.SelectedAuthScheme;
import software.amazon.awssdk.core.http.auth.AuthSchemeResolver;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
Expand Down Expand Up @@ -70,6 +72,8 @@ public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, Re
Endpoint endpoint = resolver.resolve(sdkRequest, attrs);
Duration resolveEndpointDuration = Duration.ofNanos(System.nanoTime() - resolveEndpointStart);

reapplyInterceptorModifiedAuthProperties(attrs);

MetricCollector metricCollector = attrs.getAttribute(SdkExecutionAttribute.API_CALL_METRIC_COLLECTOR);
if (metricCollector != null) {
metricCollector.reportMetric(CoreMetric.ENDPOINT_RESOLVE_DURATION, resolveEndpointDuration);
Expand Down Expand Up @@ -140,4 +144,21 @@ private static String combinePath(String clientEndpointPath, String requestPath,
String requestPathWithClientPathRemoved = StringUtils.replaceOnce(requestPath, clientEndpointPath, "");
return SdkHttpUtils.appendUri(resolvedUriPath, requestPathWithClientPathRemoved);
}

private static void reapplyInterceptorModifiedAuthProperties(ExecutionAttributes attrs) {
SelectedAuthScheme<?> currentScheme = attrs.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME);
if (currentScheme == null) {
return;
}
SelectedAuthScheme<?> beforeInterceptors =
attrs.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_BEFORE_INTERCEPTORS);

SelectedAuthScheme<?> afterInterceptors =
attrs.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_AFTER_INTERCEPTORS);
if (afterInterceptors == null) {
return;
}

AuthSchemeResolver.applyInterceptorModifiedProperties(currentScheme, beforeInterceptors, afterInterceptors, attrs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we remove those three lines?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, could it break users who rely on the buggy behavior?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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

Hmm yeah right, I guess that would be rare. But I'll make sure to document it in changelog.

});
}

@Test
Expand Down
Loading