From 44348f2b0425ef36bc4d03062a79e70ece51e727 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Mon, 30 Dec 2024 11:53:16 -0800 Subject: [PATCH 1/4] Update AuthScemeParams with RegionSet for Sigv4a auth Scheme --- .../auth/scheme/AuthSchemeParamsSpec.java | 22 +++++ .../poet/auth/scheme/AuthSchemeSpecUtils.java | 4 + .../scheme/DefaultAuthSchemeParamsSpec.java | 28 ++++++ .../awssdk/codegen/utils/AuthUtils.java | 14 +++ .../poet/auth/scheme/AuthSchemeSpecTest.java | 14 ++- ...gv4a-value-auth-scheme-default-params.java | 88 +++++++++++++++++++ ...-auth-sigv4a-value-auth-scheme-params.java | 69 +++++++++++++++ 7 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-params.java create mode 100644 codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-params.java diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeParamsSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeParamsSpec.java index 7b61a4eb70f0..a5a715c356f3 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeParamsSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeParamsSpec.java @@ -31,6 +31,8 @@ import software.amazon.awssdk.codegen.poet.PoetUtils; import software.amazon.awssdk.codegen.poet.rules.EndpointRulesSpecUtils; import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.utils.builder.CopyableBuilder; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -117,7 +119,16 @@ private void addAccessorMethods(TypeSpec.Builder b) { .addJavadoc("Returns the region. The region parameter may be used with the $S auth scheme.", AwsV4AuthScheme.SCHEME_ID) .build()); + } + if (authSchemeSpecUtils.usesSigV4a()) { + b.addMethod(MethodSpec.methodBuilder("regionSet") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .returns(RegionSet.class) + .addJavadoc("Returns the RegionSet. The regionSet parameter may be used with the $S auth " + + "scheme.", + AwsV4aAuthScheme.SCHEME_ID) + .build()); } if (authSchemeSpecUtils.generateEndpointBasedParams()) { @@ -162,6 +173,17 @@ private void addBuilderSetterMethods(TypeSpec.Builder b) { } + if (authSchemeSpecUtils.usesSigV4a()) { + b.addMethod(MethodSpec.methodBuilder("regionSet") + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addParameter(ParameterSpec.builder(RegionSet.class, "regionSet").build()) + .returns(authSchemeSpecUtils.parametersInterfaceBuilderInterfaceName()) + .addJavadoc("Set the RegionSet. The regionSet parameter may be used with the $S auth scheme.", + AwsV4aAuthScheme.SCHEME_ID) + .build()); + + } + if (authSchemeSpecUtils.generateEndpointBasedParams()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java index 5724e2b78f57..c29a1d3bb66d 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java @@ -109,6 +109,10 @@ public boolean usesSigV4() { return AuthUtils.usesAwsAuth(intermediateModel); } + public boolean usesSigV4a() { + return AuthUtils.usesSigv4aAuth(intermediateModel); + } + public boolean useEndpointBasedAuthProvider() { // Endpoint based auth provider is gated using the same setting that enables the use of auth scheme params. One does // not make sense without the other so there's no much point on creating another setting if both have to be at the same diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/DefaultAuthSchemeParamsSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/DefaultAuthSchemeParamsSpec.java index 3592f0dbf5a9..d8e981a3ee6b 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/DefaultAuthSchemeParamsSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/DefaultAuthSchemeParamsSpec.java @@ -29,6 +29,7 @@ import software.amazon.awssdk.codegen.poet.ClassSpec; import software.amazon.awssdk.codegen.poet.PoetUtils; import software.amazon.awssdk.codegen.poet.rules.EndpointRulesSpecUtils; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.utils.Validate; @@ -79,6 +80,10 @@ private MethodSpec constructor() { b.addStatement("this.region = builder.region"); } + if (authSchemeSpecUtils.usesSigV4a()) { + b.addStatement("this.regionSet = builder.regionSet"); + } + if (authSchemeSpecUtils.generateEndpointBasedParams()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { @@ -145,6 +150,9 @@ private void addBuilderConstructors(TypeSpec.Builder b) { if (authSchemeSpecUtils.usesSigV4()) { builderFromInstance.addStatement("this.region = params.region"); } + if (authSchemeSpecUtils.usesSigV4a()) { + builderFromInstance.addStatement("this.regionSet = params.regionSet"); + } if (authSchemeSpecUtils.generateEndpointBasedParams()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { @@ -181,6 +189,19 @@ private void addFieldsAndAccessors(TypeSpec.Builder b) { .build()); } + if (authSchemeSpecUtils.usesSigV4a()) { + b.addField(FieldSpec.builder(RegionSet.class, "regionSet") + .addModifiers(Modifier.PRIVATE, Modifier.FINAL) + .build()); + + b.addMethod(MethodSpec.methodBuilder("regionSet") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(RegionSet.class) + .addStatement("return regionSet") + .build()); + } + if (authSchemeSpecUtils.generateEndpointBasedParams()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { @@ -227,6 +248,13 @@ private void addBuilderFieldsAndSetter(TypeSpec.Builder b) { b.addMethod(builderSetterMethod("region", TypeName.get(Region.class))); } + if (authSchemeSpecUtils.usesSigV4a()) { + b.addField(FieldSpec.builder(RegionSet.class, "regionSet") + .addModifiers(Modifier.PRIVATE) + .build()); + b.addMethod(builderSetterMethod("regionSet", TypeName.get(RegionSet.class))); + } + if (authSchemeSpecUtils.generateEndpointBasedParams()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/utils/AuthUtils.java b/codegen/src/main/java/software/amazon/awssdk/codegen/utils/AuthUtils.java index d21434df30ba..d139e44459ce 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/utils/AuthUtils.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/utils/AuthUtils.java @@ -36,6 +36,16 @@ public static boolean usesBearerAuth(IntermediateModel model) { .anyMatch(authType -> authType == AuthType.BEARER); } + public static boolean usesSigv4aAuth(IntermediateModel model) { + if (isServiceSigv4a(model)) { + return true; + } + return model.getOperations() + .values() + .stream() + .anyMatch(operationModel -> operationModel.getAuth().stream().anyMatch(authType -> authType == AuthType.V4A)); + } + public static boolean usesAwsAuth(IntermediateModel model) { if (isServiceAwsAuthType(model)) { return true; @@ -60,6 +70,10 @@ private static boolean isServiceBearerAuth(IntermediateModel model) { return model.getMetadata().getAuthType() == AuthType.BEARER; } + private static boolean isServiceSigv4a(IntermediateModel model) { + return model.getMetadata().getAuth().stream().anyMatch(authType -> authType == AuthType.V4A); + } + private static boolean isServiceAwsAuthType(IntermediateModel model) { AuthType authType = model.getMetadata().getAuthType(); return isAuthTypeAws(authType); diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java index a4550d72c217..c12984e7bdc5 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java @@ -196,6 +196,18 @@ static List parameters() { .classSpecProvider(ModelBasedAuthSchemeProviderSpec::new) .caseName("ops-auth-sigv4a-value") .outputFileSuffix("default-provider") + .build(), + TestCase.builder() + .modelProvider(ClientTestModels::opsWithSigv4a) + .classSpecProvider(AuthSchemeParamsSpec::new) + .caseName("ops-auth-sigv4a-value") + .outputFileSuffix("params") + .build(), + TestCase.builder() + .modelProvider(ClientTestModels::opsWithSigv4a) + .classSpecProvider(DefaultAuthSchemeParamsSpec::new) + .caseName("ops-auth-sigv4a-value") + .outputFileSuffix("default-params") .build() ); } @@ -210,7 +222,7 @@ static class TestCase { @Override public String toString() { return "TestCase{" + - "caseName='" + caseName + '\'' + + "caseName='" + caseName + "-" + outputFileSuffix + '\'' + '}'; } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-params.java new file mode 100644 index 000000000000..8fc91e2069fe --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-params.java @@ -0,0 +1,88 @@ +package software.amazon.awssdk.services.database.auth.scheme.internal; + +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeParams; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DefaultDatabaseAuthSchemeParams implements DatabaseAuthSchemeParams { + private final String operation; + + private final Region region; + + private final RegionSet regionSet; + + private DefaultDatabaseAuthSchemeParams(Builder builder) { + this.operation = Validate.paramNotNull(builder.operation, "operation"); + this.region = builder.region; + this.regionSet = builder.regionSet; + } + + public static DatabaseAuthSchemeParams.Builder builder() { + return new Builder(); + } + + @Override + public String operation() { + return operation; + } + + @Override + public Region region() { + return region; + } + + @Override + public RegionSet regionSet() { + return regionSet; + } + + @Override + public DatabaseAuthSchemeParams.Builder toBuilder() { + return new Builder(this); + } + + private static final class Builder implements DatabaseAuthSchemeParams.Builder { + private String operation; + + private Region region; + + private RegionSet regionSet; + + Builder() { + } + + Builder(DefaultDatabaseAuthSchemeParams params) { + this.operation = params.operation; + this.region = params.region; + this.regionSet = params.regionSet; + } + + @Override + public Builder operation(String operation) { + this.operation = operation; + return this; + } + + @Override + public Builder region(Region region) { + this.region = region; + return this; + } + + @Override + public Builder regionSet(RegionSet regionSet) { + this.regionSet = regionSet; + return this; + } + + @Override + public DatabaseAuthSchemeParams build() { + return new DefaultDatabaseAuthSchemeParams(this); + } + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-params.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-params.java new file mode 100644 index 000000000000..37a202d5ee3a --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-params.java @@ -0,0 +1,69 @@ +package software.amazon.awssdk.services.database.auth.scheme; + +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.database.auth.scheme.internal.DefaultDatabaseAuthSchemeParams; +import software.amazon.awssdk.utils.builder.CopyableBuilder; +import software.amazon.awssdk.utils.builder.ToCopyableBuilder; + +/** + * The parameters object used to resolve the auth schemes for the Database service. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkPublicApi +public interface DatabaseAuthSchemeParams extends ToCopyableBuilder { + /** + * Get a new builder for creating a {@link DatabaseAuthSchemeParams}. + */ + static Builder builder() { + return DefaultDatabaseAuthSchemeParams.builder(); + } + + /** + * Returns the operation for which to resolve the auth scheme. + */ + String operation(); + + /** + * Returns the region. The region parameter may be used with the "aws.auth#sigv4" auth scheme. + */ + Region region(); + + /** + * Returns the RegionSet. The regionSet parameter may be used with the "aws.auth#sigv4a" auth scheme. + */ + RegionSet regionSet(); + + /** + * Returns a {@link Builder} to customize the parameters. + */ + Builder toBuilder(); + + /** + * A builder for a {@link DatabaseAuthSchemeParams}. + */ + interface Builder extends CopyableBuilder { + /** + * Set the operation for which to resolve the auth scheme. + */ + Builder operation(String operation); + + /** + * Set the region. The region parameter may be used with the "aws.auth#sigv4" auth scheme. + */ + Builder region(Region region); + + /** + * Set the RegionSet. The regionSet parameter may be used with the "aws.auth#sigv4a" auth scheme. + */ + Builder regionSet(RegionSet regionSet); + + /** + * Returns a {@link DatabaseAuthSchemeParams} object that is created from the properties that have been set on + * the builder. + */ + DatabaseAuthSchemeParams build(); + } +} From 0c019d48779089c1560af62075be28b3fa6750eb Mon Sep 17 00:00:00 2001 From: John Viegas Date: Tue, 31 Dec 2024 16:17:10 -0800 Subject: [PATCH 2/4] Update Codegen AuthSchemeInterceptorSpec to update RegionSet for AuthSchemeParams --- .../scheme/AuthSchemeInterceptorSpec.java | 38 +- .../poet/auth/scheme/AuthSchemeSpecTest.java | 6 + ...-sigv4a-value-auth-scheme-interceptor.java | 159 ++++++++ .../scheme/query-auth-scheme-interceptor.java | 5 +- .../ops-with-auth-sigv4a-value/service-2.json | 2 +- .../multiauth/customization.config | 4 + .../multiauth/endpoint-rule-set.json | 375 ++++++++++++++++++ .../multiauth/endpoint-tests.json | 5 + .../multiauth/service-2.json | 47 +++ .../multiauth/Sigv4aMultiAuthTest.java | 143 +++++++ 10 files changed, 771 insertions(+), 13 deletions(-) create mode 100644 codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java create mode 100644 test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/customization.config create mode 100644 test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-rule-set.json create mode 100644 test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-tests.json create mode 100644 test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/service-2.json create mode 100644 test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/multiauth/Sigv4aMultiAuthTest.java diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java index 6ab69cb24a92..7a8b7327a23c 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java @@ -50,6 +50,7 @@ import software.amazon.awssdk.core.internal.util.MetricUtils; import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.endpoints.EndpointProvider; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; 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; @@ -148,20 +149,17 @@ private MethodSpec generateAuthSchemeParams() { if (!authSchemeSpecUtils.useEndpointBasedAuthProvider()) { builder.addStatement("$T operation = executionAttributes.getAttribute($T.OPERATION_NAME)", String.class, SdkExecutionAttribute.class); + builder.addStatement("$T.Builder builder = $T.builder().operation(operation)", + authSchemeSpecUtils.parametersInterfaceName(), + authSchemeSpecUtils.parametersInterfaceName()); + if (authSchemeSpecUtils.usesSigV4()) { builder.addStatement("$T region = executionAttributes.getAttribute($T.AWS_REGION)", Region.class, AwsExecutionAttribute.class); - builder.addStatement("return $T.builder()" - + ".operation(operation)" - + ".region(region)" - + ".build()", - authSchemeSpecUtils.parametersInterfaceName()); - } else { - builder.addStatement("return $T.builder()" - + ".operation(operation)" - + ".build()", - authSchemeSpecUtils.parametersInterfaceName()); + builder.addStatement("builder.region(region)"); } + addRegionSet(builder); + builder.addStatement("return builder.build()"); return builder.build(); } @@ -198,6 +196,7 @@ private MethodSpec generateAuthSchemeParams() { builder.addStatement("(($T)builder).endpointProvider(($T)endpointProvider)", paramsBuilderClass, endpointProviderClass); builder.endControlFlow(); builder.endControlFlow(); + // TODO: Implement addRegionSet() for legacy services that resolve authentication from endpoints in one of next PRs. builder.addStatement("return builder.build()"); return builder.build(); } @@ -449,4 +448,23 @@ private TypeName toTypeName(Object valueType) { } return result; } + + private void addRegionSet(MethodSpec.Builder builder) { + if (authSchemeSpecUtils.usesSigV4a()) { + builder.addStatement( + "$T regionSet = executionAttributes.getOptionalAttribute($T.AWS_SIGV4A_SIGNING_REGION_SET)\n" + + " .filter(regions -> !regions.isEmpty())\n" + + " .map(regions -> $T.create(String.join(\", \", regions)))\n" + + " .orElseGet(() -> {\n" + + " $T fallbackRegion = executionAttributes.getAttribute($T.AWS_REGION);\n" + + " return fallbackRegion != null ? $T.create(fallbackRegion.toString()) : null;\n" + + " });", + RegionSet.class, AwsExecutionAttribute.class, + RegionSet.class, Region.class, AwsExecutionAttribute.class, + RegionSet.class + ); + + builder.addStatement("builder.regionSet(regionSet)"); + } + } } diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java index c12984e7bdc5..dba6bca98c74 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeSpecTest.java @@ -208,6 +208,12 @@ static List parameters() { .classSpecProvider(DefaultAuthSchemeParamsSpec::new) .caseName("ops-auth-sigv4a-value") .outputFileSuffix("default-params") + .build(), + TestCase.builder() + .modelProvider(ClientTestModels::opsWithSigv4a) + .classSpecProvider(AuthSchemeInterceptorSpec::new) + .caseName("ops-auth-sigv4a-value") + .outputFileSuffix("interceptor") .build() ); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java new file mode 100644 index 000000000000..b2117fd2af76 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-interceptor.java @@ -0,0 +1,159 @@ +package software.amazon.awssdk.services.database.auth.scheme.internal; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.AwsExecutionAttribute; +import software.amazon.awssdk.core.SdkRequest; +import software.amazon.awssdk.core.SelectedAuthScheme; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.interceptor.SdkExecutionAttribute; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; +import software.amazon.awssdk.core.internal.util.MetricUtils; +import software.amazon.awssdk.core.metrics.CoreMetric; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +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.identity.spi.AwsCredentialsIdentity; +import software.amazon.awssdk.identity.spi.Identity; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.identity.spi.ResolveIdentityRequest; +import software.amazon.awssdk.identity.spi.TokenIdentity; +import software.amazon.awssdk.metrics.MetricCollector; +import software.amazon.awssdk.metrics.SdkMetric; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeParams; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; +import software.amazon.awssdk.utils.Logger; +import software.amazon.awssdk.utils.Validate; + +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +public final class DatabaseAuthSchemeInterceptor implements ExecutionInterceptor { + private static Logger LOG = Logger.loggerFor(DatabaseAuthSchemeInterceptor.class); + + @Override + public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + List authOptions = resolveAuthOptions(context, executionAttributes); + SelectedAuthScheme selectedAuthScheme = selectAuthScheme(authOptions, executionAttributes); + putSelectedAuthScheme(executionAttributes, selectedAuthScheme); + } + + private List resolveAuthOptions(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + DatabaseAuthSchemeProvider authSchemeProvider = Validate.isInstanceOf(DatabaseAuthSchemeProvider.class, + executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEME_RESOLVER), + "Expected an instance of DatabaseAuthSchemeProvider"); + DatabaseAuthSchemeParams params = authSchemeParams(context.request(), executionAttributes); + return authSchemeProvider.resolveAuthScheme(params); + } + + private SelectedAuthScheme selectAuthScheme(List authOptions, + ExecutionAttributes executionAttributes) { + MetricCollector metricCollector = executionAttributes.getAttribute(SdkExecutionAttribute.API_CALL_METRIC_COLLECTOR); + Map> authSchemes = executionAttributes.getAttribute(SdkInternalExecutionAttribute.AUTH_SCHEMES); + IdentityProviders identityProviders = executionAttributes.getAttribute(SdkInternalExecutionAttribute.IDENTITY_PROVIDERS); + List> discardedReasons = new ArrayList<>(); + for (AuthSchemeOption authOption : authOptions) { + AuthScheme authScheme = authSchemes.get(authOption.schemeId()); + SelectedAuthScheme selectedAuthScheme = trySelectAuthScheme(authOption, authScheme, + identityProviders, discardedReasons, metricCollector, executionAttributes); + if (selectedAuthScheme != null) { + if (!discardedReasons.isEmpty()) { + LOG.debug(() -> String.format("%s auth will be used, discarded: '%s'", authOption.schemeId(), + discardedReasons.stream().map(Supplier::get).collect(Collectors.joining(", ")))); + } + return selectedAuthScheme; + } + } + throw SdkException + .builder() + .message( + "Failed to determine how to authenticate the user: " + + discardedReasons.stream().map(Supplier::get).collect(Collectors.joining(", "))).build(); + } + + private DatabaseAuthSchemeParams authSchemeParams(SdkRequest request, ExecutionAttributes executionAttributes) { + String operation = executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME); + DatabaseAuthSchemeParams.Builder builder = DatabaseAuthSchemeParams.builder().operation(operation); + Region region = executionAttributes.getAttribute(AwsExecutionAttribute.AWS_REGION); + builder.region(region); + RegionSet regionSet = executionAttributes.getOptionalAttribute(AwsExecutionAttribute.AWS_SIGV4A_SIGNING_REGION_SET) + .filter(regions -> !regions.isEmpty()).map(regions -> RegionSet.create(String.join(", ", regions))) + .orElseGet(() -> { + Region fallbackRegion = executionAttributes.getAttribute(AwsExecutionAttribute.AWS_REGION); + return fallbackRegion != null ? RegionSet.create(fallbackRegion.toString()) : null; + }); + ; + builder.regionSet(regionSet); + return builder.build(); + } + + private SelectedAuthScheme trySelectAuthScheme(AuthSchemeOption authOption, AuthScheme authScheme, + IdentityProviders identityProviders, List> discardedReasons, MetricCollector metricCollector, + ExecutionAttributes executionAttributes) { + if (authScheme == null) { + discardedReasons.add(() -> String.format("'%s' is not enabled for this request.", authOption.schemeId())); + return null; + } + IdentityProvider identityProvider = authScheme.identityProvider(identityProviders); + if (identityProvider == null) { + discardedReasons + .add(() -> String.format("'%s' does not have an identity provider configured.", authOption.schemeId())); + return null; + } + HttpSigner signer; + try { + signer = authScheme.signer(); + } catch (RuntimeException e) { + discardedReasons.add(() -> String.format("'%s' signer could not be retrieved: %s", authOption.schemeId(), + e.getMessage())); + return null; + } + ResolveIdentityRequest.Builder identityRequestBuilder = ResolveIdentityRequest.builder(); + authOption.forEachIdentityProperty(identityRequestBuilder::putProperty); + CompletableFuture identity; + SdkMetric metric = getIdentityMetric(identityProvider); + if (metric == null) { + identity = identityProvider.resolveIdentity(identityRequestBuilder.build()); + } else { + identity = MetricUtils.reportDuration(() -> identityProvider.resolveIdentity(identityRequestBuilder.build()), + metricCollector, metric); + } + return new SelectedAuthScheme<>(identity, signer, authOption); + } + + private SdkMetric getIdentityMetric(IdentityProvider identityProvider) { + Class identityType = identityProvider.identityType(); + if (identityType == AwsCredentialsIdentity.class) { + return CoreMetric.CREDENTIALS_FETCH_DURATION; + } + if (identityType == TokenIdentity.class) { + return CoreMetric.TOKEN_FETCH_DURATION; + } + return null; + } + + private void putSelectedAuthScheme(ExecutionAttributes attributes, + SelectedAuthScheme selectedAuthScheme) { + SelectedAuthScheme existingAuthScheme = attributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); + if (existingAuthScheme != null) { + AuthSchemeOption.Builder selectedOption = selectedAuthScheme.authSchemeOption().toBuilder(); + existingAuthScheme.authSchemeOption().forEachIdentityProperty(selectedOption::putIdentityPropertyIfAbsent); + existingAuthScheme.authSchemeOption().forEachSignerProperty(selectedOption::putSignerPropertyIfAbsent); + selectedAuthScheme = new SelectedAuthScheme<>(selectedAuthScheme.identity(), selectedAuthScheme.signer(), + selectedOption.build()); + } + attributes.putAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME, selectedAuthScheme); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java index 48edb00b1855..0b30a534901e 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/query-auth-scheme-interceptor.java @@ -84,10 +84,11 @@ private SelectedAuthScheme selectAuthScheme(List SelectedAuthScheme trySelectAuthScheme(AuthSchemeOption authOption, AuthScheme authScheme, IdentityProviders identityProviders, List> discardedReasons, MetricCollector metricCollector, ExecutionAttributes executionAttributes) { diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/service-2.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/service-2.json index abbff04b72b6..313162ffdd09 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/service-2.json +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/service-2.json @@ -6,7 +6,7 @@ "globalEndpoint": "database-service.amazonaws.com", "protocol": "rest-json", "serviceAbbreviation": "Database Service", - "serviceFullName": "Some Service That Uses AWS Database Protocol", + "serviceFullName": "Some Service That Uses AWS Database Protocol With Sigv4a", "serviceId": "Database Service", "signingName": "database-service", "signatureVersion": "v4", diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/customization.config b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/customization.config new file mode 100644 index 000000000000..28574274a7ef --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/customization.config @@ -0,0 +1,4 @@ +{ + "skipEndpointTestGeneration": true, + "useMultiAuth": true +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-rule-set.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-rule-set.json new file mode 100644 index 000000000000..cf58fb6fe996 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-rule-set.json @@ -0,0 +1,375 @@ +{ + "version": "1.3", + "parameters": { + "Region": { + "builtIn": "AWS::Region", + "required": true, + "documentation": "The AWS region used to dispatch the request.", + "type": "String" + }, + "UseDualStack": { + "builtIn": "AWS::UseDualStack", + "required": true, + "default": false, + "documentation": "When true, use the dual-stack endpoint. If the configured endpoint does not support dual-stack, dispatching the request MAY return an error.", + "type": "Boolean" + }, + "UseFIPS": { + "builtIn": "AWS::UseFIPS", + "required": true, + "default": false, + "documentation": "When true, send this request to the FIPS-compliant regional endpoint. If the configured endpoint does not have a FIPS compliant endpoint, dispatching the request will return an error.", + "type": "Boolean" + }, + "Endpoint": { + "builtIn": "SDK::Endpoint", + "required": false, + "documentation": "Override the endpoint used to send this request", + "type": "String" + }, + "StaticStringParam": { + "type": "String", + "required": false + }, + "OperationContextParam": { + "type": "String", + "required": false + }, + "RegionWithDefault": { + "type": "String", + "required": true, + "default": "us-east-1", + "builtIn": "AWS::Region" + }, + "BooleanClientContextParam": { + "type": "Boolean" + }, + "StringClientContextParam": { + "type": "String" + } + }, + "rules": [ + { + "conditions": [ + { + "fn": "aws.partition", + "argv": [ + { + "ref": "Region" + } + ], + "assign": "PartitionResult" + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "isSet", + "argv": [ + { + "ref": "Endpoint" + } + ] + }, + { + "fn": "parseURL", + "argv": [ + { + "ref": "Endpoint" + } + ], + "assign": "url" + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseFIPS" + }, + true + ] + } + ], + "error": "Invalid Configuration: FIPS and custom endpoint are not supported", + "type": "error" + }, + { + "conditions": [], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseDualStack" + }, + true + ] + } + ], + "error": "Invalid Configuration: Dualstack and custom endpoint are not supported", + "type": "error" + }, + { + "conditions": [], + "endpoint": { + "url": { + "ref": "Endpoint" + }, + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingRegion": "{Region}", + "signingName": "restjson" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] + } + ] + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseFIPS" + }, + true + ] + }, + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseDualStack" + }, + true + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsFIPS" + ] + } + ] + }, + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsDualStack" + ] + } + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [], + "endpoint": { + "url": "https://restjson-fips.{Region}.{PartitionResult#dualStackDnsSuffix}", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingRegion": "{Region}", + "signingName": "restjson" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] + }, + { + "conditions": [], + "error": "FIPS and DualStack are enabled, but this partition does not support one or both", + "type": "error" + } + ] + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseFIPS" + }, + true + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsFIPS" + ] + } + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [], + "type": "tree", + "rules": [ + { + "conditions": [], + "endpoint": { + "url": "https://restjson-fips.{Region}.{PartitionResult#dnsSuffix}", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingRegion": "{Region}", + "signingName": "restjson" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] + } + ] + }, + { + "conditions": [], + "error": "FIPS is enabled but this partition does not support FIPS", + "type": "error" + } + ] + }, + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + { + "ref": "UseDualStack" + }, + true + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [ + { + "fn": "booleanEquals", + "argv": [ + true, + { + "fn": "getAttr", + "argv": [ + { + "ref": "PartitionResult" + }, + "supportsDualStack" + ] + } + ] + } + ], + "type": "tree", + "rules": [ + { + "conditions": [], + "endpoint": { + "url": "https://restjson.{Region}.{PartitionResult#dualStackDnsSuffix}", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingRegion": "{Region}", + "signingName": "restjson" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] + }, + { + "conditions": [], + "error": "DualStack is enabled but this partition does not support DualStack", + "type": "error" + } + ] + }, + { + "conditions": [], + "endpoint": { + "url": "https://restjson.{Region}.{PartitionResult#dnsSuffix}", + "properties": { + "authSchemes": [ + { + "name": "sigv4", + "signingRegion": "{Region}", + "signingName": "restjson" + } + ] + }, + "headers": {} + }, + "type": "endpoint" + } + ] + } + ] +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-tests.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-tests.json new file mode 100644 index 000000000000..f94902ff9d99 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/endpoint-tests.json @@ -0,0 +1,5 @@ +{ + "testCases": [ + ], + "version": "1.0" +} \ No newline at end of file diff --git a/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/service-2.json b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/service-2.json new file mode 100644 index 000000000000..e0be3bee5ca7 --- /dev/null +++ b/test/codegen-generated-classes-test/src/main/resources/codegen-resources/multiauth/service-2.json @@ -0,0 +1,47 @@ +{ + "version":"2.0", + "metadata":{ + "apiVersion":"2016-03-11", + "endpointPrefix":"internalconfig", + "jsonVersion":"1.1", + "protocol":"rest-json", + "serviceAbbreviation":"AwsMultiAuthService", + "serviceFullName":"AWS Multi Auth Service", + "serviceId":"Multiauth", + "signatureVersion":"v4", + "targetPrefix":"MultiAuth", + "timestampFormat":"unixTimestamp", + "uid":"restjson-2016-03-11" + }, + "operations":{ + "sigv4aOperation":{ + "name":"sigv4a", + "http":{ + "method":"POST", + "requestUri":"/2016-03-11/sigv4aoperation" + }, + "input":{"shape":"sigv4aShape"}, + "auth": ["aws.auth#sigv4a"] + }, + "sigv4AndSigv4aOperation":{ + "name":"sigv4a", + "http":{ + "method":"POST", + "requestUri":"/2016-03-11/sigv4andsigv4aoperation" + }, + "input":{"shape":"sigv4aShape"}, + "auth": ["aws.auth#sigv4a", "aws.auth#sigv4"] + } + }, + "shapes": { + "sigv4aShape": { + "type": "structure", + "members": { + "StringMember": { + "shape": "String" + } + } + }, + "String":{"type":"string"} + } +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/multiauth/Sigv4aMultiAuthTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/multiauth/Sigv4aMultiAuthTest.java new file mode 100644 index 000000000000..8aabc1dd39bf --- /dev/null +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/multiauth/Sigv4aMultiAuthTest.java @@ -0,0 +1,143 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.multiauth; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.http.HttpExecuteRequest; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.multiauth.auth.scheme.MultiauthAuthSchemeParams; +import software.amazon.awssdk.services.multiauth.auth.scheme.MultiauthAuthSchemeProvider; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; + +/** + * Unit tests for the Sigv4a multi-auth functionality. + */ +class Sigv4aMultiAuthTest { + + private EnvironmentVariableHelper environmentVariableHelper; + private SdkHttpClient mockHttpClient; + private MultiauthAuthSchemeProvider multiauthAuthSchemeProvider; + + @BeforeEach + void setUp() { + environmentVariableHelper = new EnvironmentVariableHelper(); + multiauthAuthSchemeProvider = mock(MultiauthAuthSchemeProvider.class); + + mockHttpClient = mock(SdkHttpClient.class); + when(mockHttpClient.clientName()).thenReturn("MockHttpClient"); + when(mockHttpClient.prepareRequest(any())).thenThrow(new RuntimeException("expected exception")); + + List authSchemeOptions = Collections.singletonList( + AuthSchemeOption.builder().schemeId(AwsV4AuthScheme.SCHEME_ID).build() + ); + when(multiauthAuthSchemeProvider.resolveAuthScheme(any(MultiauthAuthSchemeParams.class))) + .thenReturn(authSchemeOptions); + } + + @AfterEach + void tearDown() { + environmentVariableHelper.reset(); + } + + @Test + void requestHasRegionSetParamsUpdatedToRegion() { + environmentVariableHelper.set(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET, "us-west-2,us-west-1"); + + MultiauthClient multiauthClient = MultiauthClient.builder() + .httpClient(mockHttpClient) + .authSchemeProvider(multiauthAuthSchemeProvider) + .region(Region.US_WEST_2) + .build(); + + assertThatThrownBy(() -> multiauthClient.sigv4aOperation(r -> r.stringMember(""))) + .hasMessageContaining("expected exception"); + + ArgumentCaptor paramsCaptor = + ArgumentCaptor.forClass(MultiauthAuthSchemeParams.class); + verify(multiauthAuthSchemeProvider).resolveAuthScheme(paramsCaptor.capture()); + + MultiauthAuthSchemeParams resolvedAuthSchemeParams = paramsCaptor.getValue(); + assertThat(resolvedAuthSchemeParams.regionSet()) + .isEqualTo(RegionSet.create(Arrays.asList("us-west-2", "us-west-1"))); + } + + @Test + void requestHasRegionSetSdkSystemSettings() { + MultiauthClient multiauthClient = MultiauthClient.builder() + .httpClient(mockHttpClient) + .authSchemeProvider(multiauthAuthSchemeProvider) + .region(Region.US_WEST_2) + .build(); + + assertThatThrownBy(() -> multiauthClient.sigv4aOperation(r -> r.stringMember(""))) + .hasMessageContaining("expected exception"); + + ArgumentCaptor paramsCaptor = + ArgumentCaptor.forClass(MultiauthAuthSchemeParams.class); + verify(multiauthAuthSchemeProvider).resolveAuthScheme(paramsCaptor.capture()); + + MultiauthAuthSchemeParams resolvedAuthSchemeParams = paramsCaptor.getValue(); + assertThat(resolvedAuthSchemeParams.regionSet()) + .isEqualTo(RegionSet.create(Region.US_WEST_2.toString())); + } + + @Test + void errorWhenSigv4aDoesNotHasFallbackSigv4() { + MultiauthClient multiauthClient = MultiauthClient.builder() + .httpClient(mockHttpClient) + .region(Region.US_WEST_2) + .build(); + + assertThatThrownBy(() -> multiauthClient.sigv4aOperation(r -> r.stringMember(""))) + .hasMessageContaining("You must add a dependency on the 'software.amazon.awssdk:http-auth-aws-crt' " + + "module to enable the CRT-V4a signing feature"); + } + + @Test + void fallBackToSigv4WhenSigv4aIsNotAvailable() { + MultiauthClient multiauthClient = MultiauthClient.builder() + .httpClient(mockHttpClient) + .region(Region.US_WEST_2) + .build(); + + assertThatThrownBy(() -> multiauthClient.sigv4AndSigv4aOperation(r -> r.stringMember(""))) + .hasMessageContaining("expected exception"); + + ArgumentCaptor httpRequestCaptor = ArgumentCaptor.forClass(HttpExecuteRequest.class); + verify(mockHttpClient).prepareRequest(httpRequestCaptor.capture()); + SdkHttpRequest request = httpRequestCaptor.getAllValues().get(0).httpRequest(); + assertThat(request.firstMatchingHeader("Authorization")).isPresent(); + } +} From c7fba53fe03adb0a0655f16f9b7a4f9c1ab85332 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Thu, 2 Jan 2025 10:12:23 -0800 Subject: [PATCH 3/4] updated method name --- .../codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java index 7a8b7327a23c..c7fdbab7ffee 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeInterceptorSpec.java @@ -158,7 +158,7 @@ private MethodSpec generateAuthSchemeParams() { AwsExecutionAttribute.class); builder.addStatement("builder.region(region)"); } - addRegionSet(builder); + generateSigv4aRegionSet(builder); builder.addStatement("return builder.build()"); return builder.build(); } @@ -449,7 +449,7 @@ private TypeName toTypeName(Object valueType) { return result; } - private void addRegionSet(MethodSpec.Builder builder) { + private void generateSigv4aRegionSet(MethodSpec.Builder builder) { if (authSchemeSpecUtils.usesSigV4a()) { builder.addStatement( "$T regionSet = executionAttributes.getOptionalAttribute($T.AWS_SIGV4A_SIGNING_REGION_SET)\n" + From ab6ca7204207a650397b9cb041113c36a5625e77 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Fri, 3 Jan 2025 15:56:56 -0800 Subject: [PATCH 4/4] Adding sigv4aResionSet client builder for services which has Sigv4a in Multi Auth trait --- .../poet/builder/BaseClientBuilderClass.java | 17 ++ .../builder/BaseClientBuilderInterface.java | 24 ++ .../builder/BaseClientBuilderClassTest.java | 6 + .../BaseClientBuilderInterfaceTest.java | 6 + ...ulti-auth-sigv4a-client-builder-class.java | 197 +++++++++++++++ ...-auth-sigv4a-client-builder-interface.java | 45 ++++ .../auth/Sigv4aSigningRegionSetTest.java | 227 +++++++++++------- 7 files changed, 431 insertions(+), 91 deletions(-) create mode 100644 codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-multi-auth-sigv4a-client-builder-class.java create mode 100644 codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-multi-auth-sigv4a-client-builder-interface.java diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java index 25269518b7b2..6aaa594ec131 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClass.java @@ -69,6 +69,7 @@ import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.http.Protocol; import software.amazon.awssdk.http.SdkHttpConfigurationOption; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.identity.spi.IdentityProviders; @@ -191,6 +192,10 @@ public TypeSpec poetSpec() { builder.addMethod(validateClientOptionsMethod()); + if (authSchemeSpecUtils.usesSigV4a()) { + builder.addMethod(sigv4aRegionSetMethod()); + } + return builder.build(); } @@ -721,6 +726,18 @@ private MethodSpec authSchemeProviderMethod() { .build(); } + private MethodSpec sigv4aRegionSetMethod() { + return MethodSpec.methodBuilder("sigv4aRegionSet") + .addModifiers(Modifier.PUBLIC) + .returns(TypeVariableName.get("B")) + .addParameter(RegionSet.class, "sigv4aRegionSet") + .addStatement("clientConfiguration.option($T.AWS_SIGV4A_SIGNING_REGION_SET, sigv4aRegionSet == null ? " + + "$T.emptySet() : sigv4aRegionSet.asSet())", + AwsClientOption.class, Collections.class) + .addStatement("return thisBuilder()") + .build(); + } + private MethodSpec defaultAuthSchemeProviderMethod() { return MethodSpec.methodBuilder("defaultAuthSchemeProvider") .addModifiers(PRIVATE) diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterface.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterface.java index 7cc43e51a587..3c3eeb34c589 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterface.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterface.java @@ -39,7 +39,9 @@ import software.amazon.awssdk.codegen.poet.rules.EndpointParamsKnowledgeIndex; import software.amazon.awssdk.codegen.poet.rules.EndpointRulesSpecUtils; import software.amazon.awssdk.codegen.utils.AuthUtils; +import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.identity.spi.IdentityProvider; import software.amazon.awssdk.identity.spi.TokenIdentity; import software.amazon.awssdk.utils.internal.CodegenNamingUtils; @@ -106,6 +108,10 @@ public TypeSpec poetSpec() { builder.addMethod(tokenIdentityProviderMethod()); } + if (AuthUtils.usesSigv4aAuth(model)) { + builder.addMethod(sigv4aRegionSetMethod()); + } + return builder.build(); } @@ -255,4 +261,22 @@ private boolean hasSdkClientContextParams() { && model.getCustomizationConfig().getCustomClientContextParams() != null && !model.getCustomizationConfig().getCustomClientContextParams().isEmpty(); } + + private MethodSpec sigv4aRegionSetMethod() { + return MethodSpec.methodBuilder("sigv4aRegionSet") + .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) + .addParameter(RegionSet.class, "sigv4aRegionSet") + .addJavadoc("Sets the {@link $T} to be used for operations using Sigv4a signing requests.\n" + + "This is optional; if not provided, the following precedence is used:\n" + + "
    \n" + + "
  1. {@link $T#AWS_SIGV4A_SIGNING_REGION_SET}.
  2. \n" + + "
  3. as sigv4a_signing_region_set in the configuration file.
  4. \n" + + "
  5. The region configured for the client.
  6. \n" + + "
\n", + RegionSet.class, + SdkSystemSetting.class) + .returns(TypeVariableName.get("B")) + .addStatement("throw new $T()", UnsupportedOperationException.class) + .build(); + } } diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java index 253eadc0f59f..d6ba7505c2e8 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderClassTest.java @@ -19,6 +19,7 @@ import static software.amazon.awssdk.codegen.poet.ClientTestModels.composedClientJsonServiceModels; import static software.amazon.awssdk.codegen.poet.ClientTestModels.internalConfigModels; import static software.amazon.awssdk.codegen.poet.ClientTestModels.operationWithNoAuth; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.opsWithSigv4a; import static software.amazon.awssdk.codegen.poet.ClientTestModels.queryServiceModels; import static software.amazon.awssdk.codegen.poet.ClientTestModels.queryServiceModelsEndpointAuthParamsWithAllowList; import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels; @@ -89,6 +90,11 @@ void baseClientBuilderClassWithNoAuthOperation_sra() { validateBaseClientBuilderClassGeneration(operationWithNoAuth(), "test-no-auth-ops-client-builder-class.java", true); } + @Test + void baseClientBuilderClassWithMultiAuthSigv4a() { + validateBaseClientBuilderClassGeneration(opsWithSigv4a(), "test-multi-auth-sigv4a-client-builder-class.java", true); + } + @Test void baseClientBuilderClassWithNoAuthService_sra() { validateBaseClientBuilderClassGeneration(serviceWithNoAuth(), "test-no-auth-service-client-builder-class.java", true); diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterfaceTest.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterfaceTest.java index 1430ba2e779d..b67460a2690e 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterfaceTest.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/builder/BaseClientBuilderInterfaceTest.java @@ -17,6 +17,7 @@ import static software.amazon.awssdk.codegen.poet.ClientTestModels.bearerAuthServiceModels; import static software.amazon.awssdk.codegen.poet.ClientTestModels.composedClientJsonServiceModels; +import static software.amazon.awssdk.codegen.poet.ClientTestModels.opsWithSigv4a; import static software.amazon.awssdk.codegen.poet.ClientTestModels.queryServiceModels; import static software.amazon.awssdk.codegen.poet.ClientTestModels.restJsonServiceModels; import static software.amazon.awssdk.codegen.poet.builder.BuilderClassTestUtils.validateGeneration; @@ -49,6 +50,11 @@ public void syncHasCrossRegionAccessEnabledPropertyBuilderClass() { "test-customcontextparams-sync-client-builder-class.java"); } + @Test + void baseClientBuilderInterfaceWithMultiAuth() { + validateBaseClientBuilderInterfaceGeneration(opsWithSigv4a(), "test-multi-auth-sigv4a-client-builder-interface.java"); + } + private void validateBaseClientBuilderInterfaceGeneration(IntermediateModel model, String expectedClassName) { validateGeneration(BaseClientBuilderInterface::new, model, expectedClassName); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-multi-auth-sigv4a-client-builder-class.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-multi-auth-sigv4a-client-builder-class.java new file mode 100644 index 000000000000..d84e4e4feb74 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/sra/test-multi-auth-sigv4a-client-builder-class.java @@ -0,0 +1,197 @@ +package software.amazon.awssdk.services.database; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.awscore.client.builder.AwsDefaultClientBuilder; +import software.amazon.awssdk.awscore.client.config.AwsClientOption; +import software.amazon.awssdk.awscore.endpoint.AwsClientEndpointProvider; +import software.amazon.awssdk.awscore.retry.AwsRetryStrategy; +import software.amazon.awssdk.core.SdkPlugin; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientConfiguration; +import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4AuthScheme; +import software.amazon.awssdk.http.auth.aws.scheme.AwsV4aAuthScheme; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; +import software.amazon.awssdk.http.auth.spi.scheme.AuthScheme; +import software.amazon.awssdk.identity.spi.IdentityProvider; +import software.amazon.awssdk.identity.spi.IdentityProviders; +import software.amazon.awssdk.regions.ServiceMetadataAdvancedOption; +import software.amazon.awssdk.retries.api.RetryStrategy; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; +import software.amazon.awssdk.services.database.auth.scheme.internal.DatabaseAuthSchemeInterceptor; +import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointProvider; +import software.amazon.awssdk.services.database.endpoints.internal.DatabaseRequestSetEndpointInterceptor; +import software.amazon.awssdk.services.database.endpoints.internal.DatabaseResolveEndpointInterceptor; +import software.amazon.awssdk.services.database.internal.DatabaseServiceClientConfigurationBuilder; +import software.amazon.awssdk.utils.CollectionUtils; + +/** + * Internal base class for {@link DefaultDatabaseClientBuilder} and {@link DefaultDatabaseAsyncClientBuilder}. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkInternalApi +abstract class DefaultDatabaseBaseClientBuilder, C> extends + AwsDefaultClientBuilder { + private final Map> additionalAuthSchemes = new HashMap<>(); + + @Override + protected final String serviceEndpointPrefix() { + return "database-service-endpoint"; + } + + @Override + protected final String serviceName() { + return "Database"; + } + + @Override + protected final SdkClientConfiguration mergeServiceDefaults(SdkClientConfiguration config) { + return config.merge(c -> c.option(SdkClientOption.ENDPOINT_PROVIDER, defaultEndpointProvider()) + .option(SdkClientOption.AUTH_SCHEME_PROVIDER, defaultAuthSchemeProvider()) + .option(SdkClientOption.AUTH_SCHEMES, authSchemes()) + .option(SdkClientOption.CRC32_FROM_COMPRESSED_DATA_ENABLED, false)); + } + + @Override + protected final SdkClientConfiguration finalizeServiceConfiguration(SdkClientConfiguration config) { + List endpointInterceptors = new ArrayList<>(); + endpointInterceptors.add(new DatabaseAuthSchemeInterceptor()); + endpointInterceptors.add(new DatabaseResolveEndpointInterceptor()); + endpointInterceptors.add(new DatabaseRequestSetEndpointInterceptor()); + ClasspathInterceptorChainFactory interceptorFactory = new ClasspathInterceptorChainFactory(); + List interceptors = interceptorFactory + .getInterceptors("software/amazon/awssdk/services/database/execution.interceptors"); + List additionalInterceptors = new ArrayList<>(); + interceptors = CollectionUtils.mergeLists(endpointInterceptors, interceptors); + interceptors = CollectionUtils.mergeLists(interceptors, additionalInterceptors); + interceptors = CollectionUtils.mergeLists(interceptors, config.option(SdkClientOption.EXECUTION_INTERCEPTORS)); + SdkClientConfiguration.Builder builder = config.toBuilder(); + builder.lazyOption(SdkClientOption.IDENTITY_PROVIDERS, c -> { + IdentityProviders.Builder result = IdentityProviders.builder(); + IdentityProvider credentialsIdentityProvider = c.get(AwsClientOption.CREDENTIALS_IDENTITY_PROVIDER); + if (credentialsIdentityProvider != null) { + result.putIdentityProvider(credentialsIdentityProvider); + } + return result.build(); + }); + builder.option(SdkClientOption.EXECUTION_INTERCEPTORS, interceptors); + builder.lazyOptionIfAbsent( + SdkClientOption.CLIENT_ENDPOINT_PROVIDER, + c -> AwsClientEndpointProvider + .builder() + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_DATABASE_SERVICE") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlDatabase") + .serviceProfileProperty("database_service") + .serviceEndpointPrefix(serviceEndpointPrefix()) + .defaultProtocol("https") + .region(c.get(AwsClientOption.AWS_REGION)) + .profileFile(c.get(SdkClientOption.PROFILE_FILE_SUPPLIER)) + .profileName(c.get(SdkClientOption.PROFILE_NAME)) + .putAdvancedOption(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT, + c.get(ServiceMetadataAdvancedOption.DEFAULT_S3_US_EAST_1_REGIONAL_ENDPOINT)) + .dualstackEnabled(c.get(AwsClientOption.DUALSTACK_ENDPOINT_ENABLED)) + .fipsEnabled(c.get(AwsClientOption.FIPS_ENDPOINT_ENABLED)).build()); + return builder.build(); + } + + @Override + protected final String signingName() { + return "database-service"; + } + + private DatabaseEndpointProvider defaultEndpointProvider() { + return DatabaseEndpointProvider.defaultProvider(); + } + + public B authSchemeProvider(DatabaseAuthSchemeProvider authSchemeProvider) { + clientConfiguration.option(SdkClientOption.AUTH_SCHEME_PROVIDER, authSchemeProvider); + return thisBuilder(); + } + + private DatabaseAuthSchemeProvider defaultAuthSchemeProvider() { + return DatabaseAuthSchemeProvider.defaultProvider(); + } + + @Override + public B putAuthScheme(AuthScheme authScheme) { + additionalAuthSchemes.put(authScheme.schemeId(), authScheme); + return thisBuilder(); + } + + private Map> authSchemes() { + Map> schemes = new HashMap<>(3 + this.additionalAuthSchemes.size()); + AwsV4AuthScheme awsV4AuthScheme = AwsV4AuthScheme.create(); + schemes.put(awsV4AuthScheme.schemeId(), awsV4AuthScheme); + AwsV4aAuthScheme awsV4aAuthScheme = AwsV4aAuthScheme.create(); + schemes.put(awsV4aAuthScheme.schemeId(), awsV4aAuthScheme); + NoAuthAuthScheme noAuthAuthScheme = NoAuthAuthScheme.create(); + schemes.put(noAuthAuthScheme.schemeId(), noAuthAuthScheme); + schemes.putAll(this.additionalAuthSchemes); + return schemes; + } + + @Override + protected SdkClientConfiguration invokePlugins(SdkClientConfiguration config) { + List internalPlugins = internalPlugins(config); + List externalPlugins = plugins(); + if (internalPlugins.isEmpty() && externalPlugins.isEmpty()) { + return config; + } + List plugins = CollectionUtils.mergeLists(internalPlugins, externalPlugins); + SdkClientConfiguration.Builder configuration = config.toBuilder(); + DatabaseServiceClientConfigurationBuilder serviceConfigBuilder = new DatabaseServiceClientConfigurationBuilder( + configuration); + for (SdkPlugin plugin : plugins) { + plugin.configureClient(serviceConfigBuilder); + } + updateRetryStrategyClientConfiguration(configuration); + return configuration.build(); + } + + private void updateRetryStrategyClientConfiguration(SdkClientConfiguration.Builder configuration) { + ClientOverrideConfiguration.Builder builder = configuration.asOverrideConfigurationBuilder(); + RetryMode retryMode = builder.retryMode(); + if (retryMode != null) { + configuration.option(SdkClientOption.RETRY_STRATEGY, AwsRetryStrategy.forRetryMode(retryMode)); + } else { + Consumer> configurator = builder.retryStrategyConfigurator(); + if (configurator != null) { + RetryStrategy.Builder defaultBuilder = AwsRetryStrategy.defaultRetryStrategy().toBuilder(); + configurator.accept(defaultBuilder); + configuration.option(SdkClientOption.RETRY_STRATEGY, defaultBuilder.build()); + } else { + RetryStrategy retryStrategy = builder.retryStrategy(); + if (retryStrategy != null) { + configuration.option(SdkClientOption.RETRY_STRATEGY, retryStrategy); + } + } + } + configuration.option(SdkClientOption.CONFIGURED_RETRY_MODE, null); + configuration.option(SdkClientOption.CONFIGURED_RETRY_STRATEGY, null); + configuration.option(SdkClientOption.CONFIGURED_RETRY_CONFIGURATOR, null); + } + + private List internalPlugins(SdkClientConfiguration config) { + return Collections.emptyList(); + } + + protected static void validateClientOptions(SdkClientConfiguration c) { + } + + public B sigv4aRegionSet(RegionSet sigv4aRegionSet) { + clientConfiguration.option(AwsClientOption.AWS_SIGV4A_SIGNING_REGION_SET, + sigv4aRegionSet == null ? Collections.emptySet() : sigv4aRegionSet.asSet()); + return thisBuilder(); + } +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-multi-auth-sigv4a-client-builder-interface.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-multi-auth-sigv4a-client-builder-interface.java new file mode 100644 index 000000000000..6bf84bbde1b4 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/builder/test-multi-auth-sigv4a-client-builder-interface.java @@ -0,0 +1,45 @@ +package software.amazon.awssdk.services.database; + +import software.amazon.awssdk.annotations.Generated; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.awscore.client.builder.AwsClientBuilder; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; +import software.amazon.awssdk.services.database.auth.scheme.DatabaseAuthSchemeProvider; +import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointProvider; + +/** + * This includes configuration specific to Database Service that is supported by both {@link DatabaseClientBuilder} and + * {@link DatabaseAsyncClientBuilder}. + */ +@Generated("software.amazon.awssdk:codegen") +@SdkPublicApi +public interface DatabaseBaseClientBuilder, C> extends AwsClientBuilder { + /** + * Set the {@link DatabaseEndpointProvider} implementation that will be used by the client to determine the endpoint + * for each request. This is optional; if none is provided a default implementation will be used the SDK. + */ + default B endpointProvider(DatabaseEndpointProvider endpointProvider) { + throw new UnsupportedOperationException(); + } + + /** + * Set the {@link DatabaseAuthSchemeProvider} implementation that will be used by the client to resolve the auth + * scheme for each request. This is optional; if none is provided a default implementation will be used the SDK. + */ + default B authSchemeProvider(DatabaseAuthSchemeProvider authSchemeProvider) { + throw new UnsupportedOperationException(); + } + + /** + * Sets the {@link RegionSet} to be used for operations using Sigv4a signing requests. This is optional; if not + * provided, the following precedence is used: + *
    + *
  1. {@link software.amazon.awssdk.core.SdkSystemSetting#AWS_SIGV4A_SIGNING_REGION_SET}.
  2. + *
  3. as sigv4a_signing_region_set in the configuration file.
  4. + *
  5. The region configured for the client.
  6. + *
+ */ + default B sigv4aRegionSet(RegionSet sigv4aRegionSet) { + throw new UnsupportedOperationException(); + } +} diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/auth/Sigv4aSigningRegionSetTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/auth/Sigv4aSigningRegionSetTest.java index 66957d53b6de..24c435b645bc 100644 --- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/auth/Sigv4aSigningRegionSetTest.java +++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/auth/Sigv4aSigningRegionSetTest.java @@ -32,100 +32,140 @@ import software.amazon.awssdk.core.interceptor.Context; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.http.auth.aws.signer.RegionSet; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileProperty; import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.protocolrestjsonwithconfig.ProtocolRestJsonWithConfigClient; -import software.amazon.awssdk.services.protocolrestjsonwithconfig.ProtocolRestJsonWithConfigClientBuilder; +import software.amazon.awssdk.services.multiauth.MultiauthClient; +import software.amazon.awssdk.services.multiauth.MultiauthClientBuilder; import software.amazon.awssdk.testutils.EnvironmentVariableHelper; import software.amazon.awssdk.utils.StringInputStream; class Sigv4aSigningRegionSetTest { - private EnvironmentVariableHelper helper = new EnvironmentVariableHelper(); + private final EnvironmentVariableHelper helper = new EnvironmentVariableHelper(); static Stream testCases() { - - //TODO: ClientBuilder option test cases will be added after we add regionSet option in clientBuilder in new PR. return Stream.of( - Arguments.of(new SuccessCase(null, - null, - null, - Collections.emptySet(), - "No values set anywhere")), - - Arguments.of(new SuccessCase("us-west-2", - null, - null, - Collections.unmodifiableSet(new HashSet<>(Collections.singletonList("us-west-2"))), - "System Property value takes precedence")), - - Arguments.of(new SuccessCase(null, - "us-west-2", - null, - Collections.unmodifiableSet(new HashSet<>(Collections.singletonList("us-west-2"))), - "Environment used when System Property null")), - - Arguments.of(new SuccessCase(null, - null, - "us-west-2", - Collections.unmodifiableSet(new HashSet<>(Collections.singletonList("us-west-2"))), - "Config file used when others null")), - - Arguments.of(new SuccessCase("us-west-2", - "us-east-1", null, - Collections.unmodifiableSet(new HashSet<>(Collections.singletonList("us-west-2"))) - , "System Property overrides Environment")), - - Arguments.of(new SuccessCase("us-west-2", - null, - "us-east-1", - Collections.unmodifiableSet(new HashSet<>(Collections.singletonList("us-west-2"))), - "System Property overrides Config File")), - - Arguments.of(new SuccessCase(null, - "us-west-2", - "us-east-1", - Collections.unmodifiableSet(new HashSet<>(Collections.singletonList("us-west-2"))), - "Environment overrides Config File")), - - Arguments.of(new SuccessCase("us-west-2", - "us-east-1", - "us-north-1", - Collections.unmodifiableSet(new HashSet<>(Collections.singletonList("us-west-2"))), - "SystemProperty highest precedence")), - - Arguments.of(new SuccessCase("*", - "us-west-2", - null, - Collections.unmodifiableSet(new HashSet<>(Collections.singletonList("*"))), - "Wildcard in System Property overrides specific value")), - - Arguments.of(new SuccessCase("us-west-2", - "*", - null, - Collections.unmodifiableSet(new HashSet<>(Collections.singletonList("us-west-2"))), - "Specific Environment overrides wildcard")), - - Arguments.of(new SuccessCase(null, - "*", - "us-west-2", - Collections.unmodifiableSet(new HashSet<>(Collections.singletonList("*"))) - , "Wildcard in Environment overrides Config")), - - Arguments.of(new SuccessCase("us-west-1,us-east-1", - null, "us-west-2", - Collections.unmodifiableSet(new HashSet<>(Arrays.asList("us-west-1", "us-east-1"))), - "Multi-region System Property overrides Config")), - - Arguments.of(new SuccessCase(null, - "us-west-1,us-east-1", - "us-west-2", - Collections.unmodifiableSet(new HashSet<>(Arrays.asList("us-west-1", "us-east-1"))), - "Multi-region Environment overrides Config")) + Arguments.of(new SuccessCase( + null, + null, + null, + null, + setOf(), + "No values set anywhere")), + + Arguments.of(new SuccessCase( + null, + null, + null, + "us-west-2", + setOf("us-west-2"), + "System Property value takes precedence")), + + Arguments.of(new SuccessCase( + null, + "us-west-2", + null, + null, + setOf("us-west-2"), + "Environment used when System Property null")), + + Arguments.of(new SuccessCase( + null, + null, + "us-west-2", + null, + setOf("us-west-2"), + "Config file used when others null")), + + Arguments.of(new SuccessCase( + null, + "us-east-1", + "us-west-2", + "us-west-1", + setOf("us-west-1"), + "System Property overrides Environment")), + + Arguments.of(new SuccessCase( + null, + null, + "us-east-1", + "us-west-2", + setOf("us-west-2"), + "System Property overrides Config File")), + + Arguments.of(new SuccessCase( + null, + "us-west-2", + "us-east-1", + null, + setOf("us-west-2"), + "Environment overrides Config File")), + + Arguments.of(new SuccessCase( + null, + "us-east-1", + "us-west-2", + "us-west-1", + setOf("us-west-1"), + "SystemProperty highest precedence")), + + Arguments.of(new SuccessCase( + null, + null, + null, + "*", + setOf("*"), + "Wildcard in System Property overrides specific value")), + + Arguments.of(new SuccessCase( + null, + "*", + null, + null, + setOf("*"), + "Specific Environment overrides wildcard")), + + Arguments.of(new SuccessCase( + null, + "*", + "us-west-2", + null, + setOf("*"), + "Wildcard in Environment overrides Config")), + + Arguments.of(new SuccessCase( + null, + null, + "us-west-2,us-east-1", + "us-west-1,us-east-2", + setOf("us-west-1", "us-east-2"), + "Multi-region System Property overrides Config")), + + Arguments.of(new SuccessCase( + RegionSet.GLOBAL, + "us-west-2,us-east-1", + "us-west-4", + "us-west-5", + setOf("*"), + "sigv4aRegionSet set to GLOBAL value, takes highest precedence")), + + Arguments.of(new SuccessCase( + RegionSet.create("us-west-3"), + "us-west-2,us-east-1", + "us-west-4", + "us-west-5", + setOf("us-west-3"), + "sigv4aRegionSet set to different value, takes highest precedence")) ); } + + private static Set setOf(String... s) { + return new HashSet<>(Arrays.asList(s)); + } + @AfterEach void tearDown() { System.clearProperty(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET.property()); @@ -136,10 +176,13 @@ void tearDown() { @MethodSource("testCases") void resolvesSigv4aSigningRegionSet(TestCase testCase) { try { - ProtocolRestJsonWithConfigClientBuilder builder = - ProtocolRestJsonWithConfigClient.builder() - .region(Region.US_WEST_2) - .credentialsProvider(AnonymousCredentialsProvider.create()); + MultiauthClientBuilder builder = + MultiauthClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(AnonymousCredentialsProvider.create()); + if (testCase.regionSet != null) { + builder.sigv4aRegionSet(testCase.regionSet); + } if (testCase.systemPropSetting != null) { System.setProperty(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET.property(), testCase.systemPropSetting); } @@ -161,10 +204,10 @@ void resolvesSigv4aSigningRegionSet(TestCase testCase) { .defaultProfileName("default") .addExecutionInterceptor(interceptor)); - ProtocolRestJsonWithConfigClient client = builder.build(); + MultiauthClient client = builder.build(); try { - client.allTypes(); + client.sigv4AndSigv4aOperation(b -> b.stringMember("test").build()); } catch (EndpointCapturingInterceptor.CaptureCompletedException e) { // Expected } @@ -177,17 +220,19 @@ void resolvesSigv4aSigningRegionSet(TestCase testCase) { } public static class TestCase { - private final String systemPropSetting; + private final RegionSet regionSet; private final String envVarSetting; private final String profileSetting; + private final String systemPropSetting; private final Set expectedValues; private final String caseName; - public TestCase(String systemPropSetting, String envVarSetting, String profileSetting, Set expectedValues, + public TestCase(RegionSet regionSet, String envVarSetting, String profileSetting, String systemPropSetting, Set expectedValues, String caseName) { - this.systemPropSetting = systemPropSetting; + this.regionSet = regionSet; this.envVarSetting = envVarSetting; this.profileSetting = profileSetting; + this.systemPropSetting = systemPropSetting; this.expectedValues = expectedValues; this.caseName = caseName; } @@ -199,9 +244,9 @@ public String toString() { } public static class SuccessCase extends TestCase { - public SuccessCase(String systemPropSetting, String envVarSetting, String profileSetting, Set expectedValues, + public SuccessCase(RegionSet regionSet, String envVarSetting, String profileSetting, String systemPropSetting, Set expectedValues, String caseName) { - super(systemPropSetting, envVarSetting, profileSetting, expectedValues, caseName); + super(regionSet, envVarSetting, profileSetting, systemPropSetting, expectedValues, caseName); } }