Skip to content

Commit 5ef73a4

Browse files
authored
Choose next potential scheme if signer not found (#4952)
* Choose next potential scheme if signer not found This commit fixes the generated auth scheme selection mechanism by ensuring that auth scheme is able to provide a signer implementation (i.e. does not throw an exception) before selecting the auth scheme. This is useful in situations where the signer implementation relies on components thay may not be available at runtime, such as the AwsV4aAuthScheme, which relies on CRT which is an optional dependency. Additional changes: - introduce crt-unavailable-tests which is a good candidate for testing behavior of components that use CRT when CRT is not present. - modify DefaultAwsV4aAuthScheme so that errors thrown during static initialization of the singleton holder for the Sigv4a signer are caught and thrown directly instead of resulting in ExceptionInInitializerError * Review comments
1 parent b6401a0 commit 5ef73a4

File tree

12 files changed

+296
-8
lines changed

12 files changed

+296
-8
lines changed

.brazil.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@
101101
"test-utils": { "skipImport": true },
102102
"tests-coverage-reporting": { "skipImport": true },
103103
"third-party": { "skipImport": true },
104-
"third-party-slf4j-api": { "skipImport": true }
104+
"third-party-slf4j-api": { "skipImport": true },
105+
"crt-unavailable-tests": { "skipImport": true }
105106
},
106107

107108
"dependencies": {

buildspecs/release-javadoc.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ phases:
1818
commands:
1919
- python ./scripts/doc_crosslinks/generate_cross_link_data.py --apiDefinitionsBasePath ./services/ --apiDefinitionsRelativeFilePath src/main/resources/codegen-resources/service-2.json --templateFilePath ./scripts/doc_crosslinks/crosslink_redirect.html --outputFilePath ./scripts/crosslink_redirect.html
2020
- mvn install -P quick -T1C
21-
- mvn clean install javadoc:aggregate -B -Ppublic-javadoc -Dcheckstyle.skip -Dspotbugs.skip -DskipTests -Ddoclint=none -pl '!:protocol-tests,!:protocol-tests-core,!:codegen-generated-classes-test,!:sdk-benchmarks,!:s3-benchmarks,!:module-path-tests,!:test-utils,!:http-client-tests,!:tests-coverage-reporting,!:sdk-native-image-test,!:ruleset-testing-core,!:old-client-version-compatibility-test'
21+
- mvn clean install javadoc:aggregate -B -Ppublic-javadoc -Dcheckstyle.skip -Dspotbugs.skip -DskipTests -Ddoclint=none -pl '!:protocol-tests,!:protocol-tests-core,!:codegen-generated-classes-test,!:sdk-benchmarks,!:s3-benchmarks,!:module-path-tests,!:test-utils,!:http-client-tests,!:tests-coverage-reporting,!:sdk-native-image-test,!:ruleset-testing-core,!:old-client-version-compatibility-test,!:crt-unavailable-tests'
2222
- RELEASE_VERSION=`mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec`
2323
-
2424
- aws s3 sync target/site/apidocs/ $DOC_PATH/$RELEASE_VERSION/ --acl="public-read"

buildspecs/release-to-maven.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ phases:
3434
awk 'BEGIN { var=ENVIRON["SDK_SIGNING_GPG_KEYNAME"] } { gsub("\\$SDK_SIGNING_GPG_KEYNAME", var, $0); print }' > \
3535
$SETTINGS_XML
3636
37-
mvn clean deploy -B -s $SETTINGS_XML -Ppublishing -DperformRelease -Dspotbugs.skip -DskipTests -Dcheckstyle.skip -Djapicmp.skip -Ddoclint=none -pl !:protocol-tests,!:protocol-tests-core,!:codegen-generated-classes-test,!:sdk-benchmarks,!:module-path-tests,!:tests-coverage-reporting,!:stability-tests,!:sdk-native-image-test,!:auth-tests,!:s3-benchmarks,!:region-testing,!:old-client-version-compatibility-test -DautoReleaseAfterClose=true -DstagingProgressTimeoutMinutes=30 -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 -Dmaven.wagon.http.retryHandler.requestSentEnabled=true
37+
mvn clean deploy -B -s $SETTINGS_XML -Ppublishing -DperformRelease -Dspotbugs.skip -DskipTests -Dcheckstyle.skip -Djapicmp.skip -Ddoclint=none -pl !:protocol-tests,!:protocol-tests-core,!:codegen-generated-classes-test,!:sdk-benchmarks,!:module-path-tests,!:tests-coverage-reporting,!:stability-tests,!:sdk-native-image-test,!:auth-tests,!:s3-benchmarks,!:region-testing,!:old-client-version-compatibility-test,!:crt-unavailable-tests -DautoReleaseAfterClose=true -DstagingProgressTimeoutMinutes=30 -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 -Dmaven.wagon.http.retryHandler.requestSentEnabled=true
3838
else
3939
echo "This version was already released."
4040
fi

codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import software.amazon.awssdk.endpoints.EndpointProvider;
5353
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
5454
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
55+
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
5556
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
5657
import software.amazon.awssdk.identity.spi.Identity;
5758
import software.amazon.awssdk.identity.spi.IdentityProvider;
@@ -274,6 +275,19 @@ private MethodSpec generateTrySelectAuthScheme() {
274275
.endControlFlow();
275276
}
276277

278+
builder.addStatement("$T signer",
279+
ParameterizedTypeName.get(ClassName.get(HttpSigner.class), TypeVariableName.get("T")));
280+
builder.beginControlFlow("try");
281+
{
282+
builder.addStatement("signer = authScheme.signer()");
283+
builder.nextControlFlow("catch (RuntimeException e)");
284+
builder.addStatement("discardedReasons.add(() -> String.format($S, authOption.schemeId(), e.getMessage()))",
285+
"'%s' signer could not be retrieved: %s")
286+
.addStatement("return null")
287+
.endControlFlow();
288+
}
289+
290+
277291
builder.addStatement("$T.Builder identityRequestBuilder = $T.builder()",
278292
ResolveIdentityRequest.class,
279293
ResolveIdentityRequest.class);
@@ -294,7 +308,7 @@ private MethodSpec generateTrySelectAuthScheme() {
294308
MetricUtils.class)
295309
.endControlFlow();
296310

297-
builder.addStatement("return new $T<>(identity, authScheme.signer(), authOption)", SelectedAuthScheme.class);
311+
builder.addStatement("return new $T<>(identity, signer, authOption)", SelectedAuthScheme.class);
298312
return builder.build();
299313
}
300314

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import software.amazon.awssdk.core.metrics.CoreMetric;
2323
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
2424
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
25+
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
2526
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
2627
import software.amazon.awssdk.identity.spi.Identity;
2728
import software.amazon.awssdk.identity.spi.IdentityProvider;
@@ -100,6 +101,14 @@ private <T extends Identity> SelectedAuthScheme<T> trySelectAuthScheme(AuthSchem
100101
.add(() -> String.format("'%s' does not have an identity provider configured.", authOption.schemeId()));
101102
return null;
102103
}
104+
HttpSigner<T> signer;
105+
try {
106+
signer = authScheme.signer();
107+
} catch (RuntimeException e) {
108+
discardedReasons.add(() -> String.format("'%s' signer could not be retrieved: %s", authOption.schemeId(),
109+
e.getMessage()));
110+
return null;
111+
}
103112
ResolveIdentityRequest.Builder identityRequestBuilder = ResolveIdentityRequest.builder();
104113
authOption.forEachIdentityProperty(identityRequestBuilder::putProperty);
105114
CompletableFuture<? extends T> identity;
@@ -110,7 +119,7 @@ private <T extends Identity> SelectedAuthScheme<T> trySelectAuthScheme(AuthSchem
110119
identity = MetricUtils.reportDuration(() -> identityProvider.resolveIdentity(identityRequestBuilder.build()),
111120
metricCollector, metric);
112121
}
113-
return new SelectedAuthScheme<>(identity, authScheme.signer(), authOption);
122+
return new SelectedAuthScheme<>(identity, signer, authOption);
114123
}
115124

116125
private SdkMetric<Duration> getIdentityMetric(IdentityProvider<?> identityProvider) {

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-with-allowlist-auth-scheme-interceptor.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import software.amazon.awssdk.endpoints.EndpointProvider;
2323
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
2424
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
25+
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
2526
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
2627
import software.amazon.awssdk.identity.spi.Identity;
2728
import software.amazon.awssdk.identity.spi.IdentityProvider;
@@ -117,6 +118,14 @@ private <T extends Identity> SelectedAuthScheme<T> trySelectAuthScheme(AuthSchem
117118
.add(() -> String.format("'%s' does not have an identity provider configured.", authOption.schemeId()));
118119
return null;
119120
}
121+
HttpSigner<T> signer;
122+
try {
123+
signer = authScheme.signer();
124+
} catch (RuntimeException e) {
125+
discardedReasons.add(() -> String.format("'%s' signer could not be retrieved: %s", authOption.schemeId(),
126+
e.getMessage()));
127+
return null;
128+
}
120129
ResolveIdentityRequest.Builder identityRequestBuilder = ResolveIdentityRequest.builder();
121130
authOption.forEachIdentityProperty(identityRequestBuilder::putProperty);
122131
CompletableFuture<? extends T> identity;
@@ -127,7 +136,7 @@ private <T extends Identity> SelectedAuthScheme<T> trySelectAuthScheme(AuthSchem
127136
identity = MetricUtils.reportDuration(() -> identityProvider.resolveIdentity(identityRequestBuilder.build()),
128137
metricCollector, metric);
129138
}
130-
return new SelectedAuthScheme<>(identity, authScheme.signer(), authOption);
139+
return new SelectedAuthScheme<>(identity, signer, authOption);
131140
}
132141

133142
private SdkMetric<Duration> getIdentityMetric(IdentityProvider<?> identityProvider) {

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-endpoint-auth-params-without-allowlist-auth-scheme-interceptor.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import software.amazon.awssdk.endpoints.EndpointProvider;
2323
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
2424
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
25+
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
2526
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
2627
import software.amazon.awssdk.identity.spi.Identity;
2728
import software.amazon.awssdk.identity.spi.IdentityProvider;
@@ -120,6 +121,14 @@ private <T extends Identity> SelectedAuthScheme<T> trySelectAuthScheme(AuthSchem
120121
.add(() -> String.format("'%s' does not have an identity provider configured.", authOption.schemeId()));
121122
return null;
122123
}
124+
HttpSigner<T> signer;
125+
try {
126+
signer = authScheme.signer();
127+
} catch (RuntimeException e) {
128+
discardedReasons.add(() -> String.format("'%s' signer could not be retrieved: %s", authOption.schemeId(),
129+
e.getMessage()));
130+
return null;
131+
}
123132
ResolveIdentityRequest.Builder identityRequestBuilder = ResolveIdentityRequest.builder();
124133
authOption.forEachIdentityProperty(identityRequestBuilder::putProperty);
125134
CompletableFuture<? extends T> identity;
@@ -130,7 +139,7 @@ private <T extends Identity> SelectedAuthScheme<T> trySelectAuthScheme(AuthSchem
130139
identity = MetricUtils.reportDuration(() -> identityProvider.resolveIdentity(identityRequestBuilder.build()),
131140
metricCollector, metric);
132141
}
133-
return new SelectedAuthScheme<>(identity, authScheme.signer(), authOption);
142+
return new SelectedAuthScheme<>(identity, signer, authOption);
134143
}
135144

136145
private SdkMetric<Duration> getIdentityMetric(IdentityProvider<?> identityProvider) {

core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/scheme/DefaultAwsV4aAuthScheme.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,27 @@ public IdentityProvider<AwsCredentialsIdentity> identityProvider(IdentityProvide
5252
*/
5353
@Override
5454
public AwsV4aHttpSigner signer() {
55+
if (SignerSingletonHolder.ERROR != null) {
56+
throw SignerSingletonHolder.ERROR;
57+
}
5558
return SignerSingletonHolder.INSTANCE;
5659
}
5760

5861
private static class SignerSingletonHolder {
59-
private static final AwsV4aHttpSigner INSTANCE = AwsV4aHttpSigner.create();
62+
private static final AwsV4aHttpSigner INSTANCE;
63+
private static final RuntimeException ERROR;
64+
65+
// Attempt to load the Sigv4a signer and cache the error if CRT is not available on the classpath.
66+
static {
67+
AwsV4aHttpSigner instance = null;
68+
RuntimeException error = null;
69+
try {
70+
instance = AwsV4aHttpSigner.create();
71+
} catch (RuntimeException e) {
72+
error = e;
73+
}
74+
INSTANCE = instance;
75+
ERROR = error;
76+
}
6077
}
6178
}

pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
<module>test/ruleset-testing-core</module>
8989
<module>test/old-client-version-compatibility-test</module>
9090
<module>test/bundle-logging-bridge-binding-test</module>
91+
<module>test/crt-unavailable-tests</module>
9192
</modules>
9293
<scm>
9394
<url>${scm.github.url}</url>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.mockito.ArgumentMatchers.any;
20+
import static org.mockito.Mockito.mock;
21+
import static org.mockito.Mockito.spy;
22+
import static org.mockito.Mockito.verify;
23+
import static org.mockito.Mockito.when;
24+
25+
import java.util.Arrays;
26+
import java.util.HashMap;
27+
import java.util.List;
28+
import java.util.Map;
29+
import org.junit.jupiter.api.BeforeEach;
30+
import org.junit.jupiter.api.Test;
31+
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
32+
import software.amazon.awssdk.core.SelectedAuthScheme;
33+
import software.amazon.awssdk.core.interceptor.Context;
34+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
35+
import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute;
36+
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
37+
import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme;
38+
import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme;
39+
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
40+
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
41+
import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity;
42+
import software.amazon.awssdk.identity.spi.IdentityProvider;
43+
import software.amazon.awssdk.identity.spi.IdentityProviders;
44+
import software.amazon.awssdk.services.protocolrestjson.auth.scheme.ProtocolRestJsonAuthSchemeParams;
45+
import software.amazon.awssdk.services.protocolrestjson.auth.scheme.ProtocolRestJsonAuthSchemeProvider;
46+
import software.amazon.awssdk.services.protocolrestjson.auth.scheme.internal.ProtocolRestJsonAuthSchemeInterceptor;
47+
48+
public class AuthSchemeInterceptorTest {
49+
private static final ProtocolRestJsonAuthSchemeInterceptor INTERCEPTOR = new ProtocolRestJsonAuthSchemeInterceptor();
50+
51+
private Context.BeforeExecution mockContext;
52+
53+
@BeforeEach
54+
public void setup() {
55+
mockContext = mock(Context.BeforeExecution.class);
56+
}
57+
58+
@Test
59+
public void resolveAuthScheme_authSchemeSignerThrows_continuesToNextAuthScheme() {
60+
ProtocolRestJsonAuthSchemeProvider mockAuthSchemeProvider = mock(ProtocolRestJsonAuthSchemeProvider.class);
61+
List<AuthSchemeOption> authSchemeOptions = Arrays.asList(
62+
AuthSchemeOption.builder().schemeId(TestAuthScheme.SCHEME_ID).build(),
63+
AuthSchemeOption.builder().schemeId(AwsV4AuthScheme.SCHEME_ID).build()
64+
);
65+
when(mockAuthSchemeProvider.resolveAuthScheme(any(ProtocolRestJsonAuthSchemeParams.class))).thenReturn(authSchemeOptions);
66+
67+
IdentityProviders mockIdentityProviders = mock(IdentityProviders.class);
68+
when(mockIdentityProviders.identityProvider(any(Class.class))).thenReturn(AnonymousCredentialsProvider.create());
69+
70+
Map<String, AuthScheme<?>> authSchemes = new HashMap<>();
71+
authSchemes.put(AwsV4AuthScheme.SCHEME_ID, AwsV4AuthScheme.create());
72+
73+
TestAuthScheme notProvidedAuthScheme = spy(new TestAuthScheme());
74+
authSchemes.put(TestAuthScheme.SCHEME_ID, notProvidedAuthScheme);
75+
76+
ExecutionAttributes attributes = new ExecutionAttributes();
77+
attributes.putAttribute(SdkExecutionAttribute.OPERATION_NAME, "GetFoo");
78+
attributes.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER, mockAuthSchemeProvider);
79+
attributes.putAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS, mockIdentityProviders);
80+
attributes.putAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES, authSchemes);
81+
82+
INTERCEPTOR.beforeExecution(mockContext, attributes);
83+
84+
SelectedAuthScheme<?> selectedAuthScheme = attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME);
85+
86+
verify(notProvidedAuthScheme).signer();
87+
assertThat(selectedAuthScheme.authSchemeOption().schemeId()).isEqualTo(AwsV4AuthScheme.SCHEME_ID);
88+
}
89+
90+
private static class TestAuthScheme implements AuthScheme<AwsCredentialsIdentity> {
91+
public static final String SCHEME_ID = "codegen-test-scheme";
92+
93+
@Override
94+
public String schemeId() {
95+
return SCHEME_ID;
96+
}
97+
98+
@Override
99+
public IdentityProvider<AwsCredentialsIdentity> identityProvider(IdentityProviders providers) {
100+
return providers.identityProvider(AwsCredentialsIdentity.class);
101+
}
102+
103+
@Override
104+
public HttpSigner<AwsCredentialsIdentity> signer() {
105+
throw new RuntimeException("Not on classpath");
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)