From 44348f2b0425ef36bc4d03062a79e70ece51e727 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Mon, 30 Dec 2024 11:53:16 -0800 Subject: [PATCH 01/14] 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 02/14] 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 03/14] 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 04/14] 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); } } From b837482c04f12531775b09a7ef9f3266a5ffdf4f Mon Sep 17 00:00:00 2001 From: John Viegas Date: Tue, 7 Jan 2025 12:20:55 -0800 Subject: [PATCH 05/14] Adding Codegen support for unsignedPayload model trait for multi-auth auth schemes --- .../amazon/awssdk/codegen/AddOperations.java | 1 + .../model/intermediate/OperationModel.java | 11 ++ .../codegen/model/service/Operation.java | 9 ++ .../codegen/poet/auth/scheme/AuthOption.java | 100 ++++++++++++ .../scheme/AuthSchemeCodegenMetadataExt.java | 70 ++++++-- .../scheme/ModelAuthSchemeKnowledgeIndex.java | 55 +++++-- .../ops-with-auth-sigv4a-value/service-2.json | 150 +++++++++++++++++- 7 files changed, 362 insertions(+), 34 deletions(-) create mode 100644 codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthOption.java diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java b/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java index 5fc680eccb48..5576c6487802 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java @@ -182,6 +182,7 @@ public Map constructOperations() { operationModel.setStaticContextParams(op.getStaticContextParams()); operationModel.setOperationContextParams(op.getOperationContextParams()); operationModel.setAuth(getAuthFromOperation(op)); + operationModel.setUnsignedPayload(op.isUnsignedPayload()); Input input = op.getInput(); if (input != null) { diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java index 76a46d697abf..72ddf2a88b21 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java @@ -41,6 +41,9 @@ public class OperationModel extends DocumentationModel { private String deprecatedMessage; + private boolean unsignedPayload; + + private VariableModel input; private ReturnTypeModel returnType; @@ -111,10 +114,18 @@ public boolean isDeprecated() { return deprecated; } + public boolean isUnsignedPayload() { + return unsignedPayload; + } + public void setDeprecated(boolean deprecated) { this.deprecated = deprecated; } + public void setUnsignedPayload(boolean unsignedPayload) { + this.unsignedPayload = unsignedPayload; + } + public String getDeprecatedMessage() { return deprecatedMessage; } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Operation.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Operation.java index bf74350bd925..47a678977fca 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Operation.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Operation.java @@ -26,6 +26,7 @@ public class Operation { private String name; private boolean deprecated; + private boolean unsignedPayload; private String deprecatedMessage; @@ -78,10 +79,18 @@ public boolean isDeprecated() { return deprecated; } + public boolean isUnsignedPayload() { + return unsignedPayload; + } + public void setDeprecated(boolean deprecated) { this.deprecated = deprecated; } + public void setUnsignedPayload(boolean unsignedPayload) { + this.unsignedPayload = unsignedPayload; + } + public String getDeprecatedMessage() { return deprecatedMessage; } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthOption.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthOption.java new file mode 100644 index 000000000000..bd8968948ca2 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthOption.java @@ -0,0 +1,100 @@ +/* + * 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.codegen.poet.auth.scheme; + +import java.util.Objects; +import software.amazon.awssdk.codegen.model.service.AuthType; + +/** + * Represents an authentication option, encapsulating attributes such as the authentication type + * and whether the payload should be unsigned. This class provides a clean and immutable way + * to model these attributes as separate traits, as specified in the service models. + * + *

The primary purpose of this class is to hold authentication-related attributes, such as: + *

    + *
  • authType: Specifies the type of authentication to be used (e.g., SigV4, SigV4A etc).
  • + *
  • unsignedPayload: Indicates whether the payload should be unsigned.
  • + *
+ */ +public final class AuthOption { + + private final AuthType authType; + private final boolean unsignedPayload; + + private AuthOption(AuthType authType, boolean unsignedPayload) { + this.authType = Objects.requireNonNull(authType, "authType must not be null"); + this.unsignedPayload = unsignedPayload; + } + + public static Builder builder() { + return new Builder(); + } + + public AuthType authType() { + return authType; + } + + public boolean isUnsignedPayload() { + return unsignedPayload; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AuthOption that = (AuthOption) o; + return unsignedPayload == that.unsignedPayload && authType == that.authType; + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode = 31 * hashCode + Objects.hashCode(authType); + hashCode = 31 * hashCode + (unsignedPayload ? 1 : 0); + return hashCode; + } + + @Override + public String toString() { + return "AuthOption{" + + "authType=" + authType + + ", unsignedPayload=" + unsignedPayload + + '}'; + } + + public static class Builder { + private AuthType authType; + private boolean unsignedPayload = false; + + public Builder authType(AuthType authType) { + this.authType = authType; + return this; + } + + public Builder unsignedPayload(boolean unsignedPayload) { + this.unsignedPayload = unsignedPayload; + return this; + } + + public AuthOption build() { + return new AuthOption(authType, unsignedPayload); + } + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java index c7c7b6168bb7..53c2932f3da1 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java @@ -31,6 +31,7 @@ import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; /** @@ -91,24 +92,34 @@ private AuthSchemeCodegenMetadataExt() { /** * Creates a new auth scheme codegen metadata instance using the defaults for the given {@link AuthType} defaults. */ - public static AuthSchemeCodegenMetadata fromAuthType(AuthType type) { - switch (type) { + public static AuthSchemeCodegenMetadata fromAuthType(AuthOption authOption) { + switch (authOption.authType()) { case BEARER: return BEARER; case NONE: return NO_AUTH; case V4A: - return SIGV4A; + return getSigv4aAuthSchemeBuilder(authOption).build(); default: - String authTypeName = type.value(); - SigV4SignerDefaults defaults = AuthTypeToSigV4Default.authTypeToDefaults().get(authTypeName); - if (defaults == null) { - throw new IllegalArgumentException("Unknown auth type: " + type); - } - return fromConstants(defaults); + return resolveAuthSchemeForType(authOption); } } + private static AuthSchemeCodegenMetadata resolveAuthSchemeForType(AuthOption authOption) { + String authTypeName = authOption.authType().value(); + SigV4SignerDefaults defaults = AuthTypeToSigV4Default.authTypeToDefaults().get(authTypeName); + + if (defaults == null) { + throw new IllegalArgumentException("Unknown auth option: " + authOption + " with type " + authTypeName); + } + if (authOption.isUnsignedPayload()) { + defaults = defaults.toBuilder() + .payloadSigningEnabled(false) + .build(); + } + return fromConstants(defaults); + } + /** * Transforms a {@link SigV4SignerDefaults} instance to an {@link AuthSchemeCodegenMetadata} instance. */ @@ -171,25 +182,54 @@ public static CodeBlock codegenSignerPropertiesIfAbsent( private static List propertiesFromConstants(SigV4SignerDefaults constants) { List properties = new ArrayList<>(); if (constants.payloadSigningEnabled() != null) { - properties.add(from("PAYLOAD_SIGNING_ENABLED", constants::payloadSigningEnabled)); + properties.add(from("PAYLOAD_SIGNING_ENABLED", constants::payloadSigningEnabled, AwsV4HttpSigner.class)); } if (constants.doubleUrlEncode() != null) { - properties.add(from("DOUBLE_URL_ENCODE", constants::doubleUrlEncode)); + properties.add(from("DOUBLE_URL_ENCODE", constants::doubleUrlEncode, AwsV4HttpSigner.class)); } if (constants.normalizePath() != null) { - properties.add(from("NORMALIZE_PATH", constants::normalizePath)); + properties.add(from("NORMALIZE_PATH", constants::normalizePath, AwsV4HttpSigner.class)); } if (constants.chunkEncodingEnabled() != null) { - properties.add(from("CHUNK_ENCODING_ENABLED", constants::chunkEncodingEnabled)); + properties.add(from("CHUNK_ENCODING_ENABLED", constants::chunkEncodingEnabled, AwsV4HttpSigner.class)); } return properties; } - private static SignerPropertyValueProvider from(String name, Supplier valueSupplier) { + private static SignerPropertyValueProvider from(String name, + Supplier valueSupplier, + Class containingClass) { return SignerPropertyValueProvider.builder() - .containingClass(AwsV4HttpSigner.class) + .containingClass(containingClass) .fieldName(name) .constantValueSupplier(valueSupplier) .build(); } + + private static Builder getSigv4aAuthSchemeBuilder(AuthOption authOption) { + Builder sigv4aBuilder = builder() + .schemeId(AwsV4aAuthScheme.SCHEME_ID) + .authSchemeClass(AwsV4aAuthScheme.class); + + addCommonSigv4aProperties(sigv4aBuilder); + + if (authOption.isUnsignedPayload()) { + sigv4aBuilder.addProperty(from("PAYLOAD_SIGNING_ENABLED", () -> false, AwsV4aHttpSigner.class)); + } + return sigv4aBuilder; + } + + private static void addCommonSigv4aProperties(Builder builder) { + builder.addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4aHttpSigner.class) + .fieldName("SERVICE_SIGNING_NAME") + .valueEmitter((spec, utils) -> spec.add("$S", utils.signingName())) + .build()) + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4aHttpSigner.class) + .fieldName("REGION_SET") + .valueEmitter((spec, utils) -> spec.add("$L", "params.regionSet()")) + .build()); + } + } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java index 2b0a39145e43..954ecb5b97b6 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java @@ -72,9 +72,9 @@ public Map, List> operationsToMetadata() * {@link Collections#emptyList()}. */ private Map, List> operationsToModeledMetadata() { - Map, List> operationsToAuthType = operationsToAuthType(); + Map, List> operationsToAuthOption = operationsToAuthOptions(); Map, List> operationsToMetadata = new LinkedHashMap<>(); - operationsToAuthType.forEach((k, v) -> operationsToMetadata.put(k, authTypeToCodegenMetadata(v))); + operationsToAuthOption.forEach((k, v) -> operationsToMetadata.put(k, authOptionToCodegenMetadata(v))); return operationsToMetadata; } @@ -82,16 +82,19 @@ private Map, List> operationsToModeledMe * Returns a map from list of operations to the list of auth-types modeled for those operations. The values are taken directly * from the model {@link OperationModel#getAuth()} method. */ - private Map, List> operationsToAuthType() { - Map, List> authSchemesToOperations = + private Map, List> operationsToAuthOptions() { + // Create a map from lists of AuthOption to lists of operation names + Map, List> authOptionToOperations = intermediateModel.getOperations() .entrySet() .stream() .filter(kvp -> !kvp.getValue().getAuth().isEmpty()) - .collect(Collectors.groupingBy(kvp -> kvp.getValue().getAuth(), - Collectors.mapping(Map.Entry::getKey, Collectors.toList()))); + .collect(Collectors.groupingBy( + kvp -> toAuthOptions(kvp.getValue()), + Collectors.mapping(Map.Entry::getKey, Collectors.toList()) + )); - Map, List> operationsToAuthType = authSchemesToOperations + Map, List> operationsToAuthOption = authOptionToOperations .entrySet() .stream() .sorted(Comparator.comparing(kvp -> kvp.getValue().get(0))) @@ -99,17 +102,28 @@ private Map, List> operationsToAuthType() { Map.Entry::getKey, (a, b) -> b, LinkedHashMap::new)); - List serviceDefaults = serviceDefaultAuthTypes(); + List serviceDefaults = serviceDefaultAuthOption(); + + // Handle operations with defaults + List operationsWithDefaults = authOptionToOperations.remove(serviceDefaults); + if (operationsWithDefaults != null) { + operationsToAuthOption.remove(operationsWithDefaults); + } + operationsToAuthOption.put(Collections.emptyList(), serviceDefaults); + return operationsToAuthOption; + } - // Get the list of operations that share the same auth schemes as the system defaults and remove it from the result. We - // will take care of all of these in the fallback `default` case. - List operationsWithDefaults = authSchemesToOperations.remove(serviceDefaults); - operationsToAuthType.remove(operationsWithDefaults); - operationsToAuthType.put(Collections.emptyList(), serviceDefaults); - return operationsToAuthType; + private List toAuthOptions(OperationModel operation) { + return operation.getAuth().stream() + .map(authType -> AuthOption.builder() + .authType(authType) + .unsignedPayload(operation.isUnsignedPayload()) + .build()) + .collect(Collectors.toList()); } + /** * Similar to {@link #operationsToModeledMetadata()} computes a map from operations to codegen metadata objects. The service * default list of codegen metadata is keyed with {@link Collections#emptyList()}. @@ -153,7 +167,18 @@ private List serviceDefaultAuthTypes() { return Collections.singletonList(intermediateModel.getMetadata().getAuthType()); } - private List authTypeToCodegenMetadata(List authTypes) { + /** + * Returns the list of modeled top-level auth-options. + */ + private List serviceDefaultAuthOption() { + List modeled = intermediateModel.getMetadata().getAuth(); + if (!modeled.isEmpty()) { + return modeled.stream().map(r -> AuthOption.builder().authType(r).build()).collect(Collectors.toList()); + } + return Collections.singletonList(AuthOption.builder().authType(intermediateModel.getMetadata().getAuthType()).build()); + } + + private List authOptionToCodegenMetadata(List authTypes) { return authTypes.stream().map(AuthSchemeCodegenMetadataExt::fromAuthType).collect(Collectors.toList()); } 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 313162ffdd09..1d273a0cf1b1 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 @@ -32,8 +32,7 @@ "shape": "InvalidInputException" } ], - "documentation": "

Performs a get row operation no output

" - }, + "documentation": "

Performs a GET request to get-row with default payload signing.

" }, "PutRow": { "name": "PutRow", "auth": ["v4"], @@ -52,7 +51,7 @@ "shape": "InvalidInputException" } ], - "documentation": "

Performs a get row operation no output

" + "documentation": "

Performs a PUT request to put-row with auth as sigv4.

" }, "DeleteRow": { "name": "DeleteRow", @@ -72,7 +71,150 @@ "shape": "InvalidInputException" } ], - "documentation": "

Performs a get row operation no output

" + "documentation": "

Performs a DELETE request to delete-row with auth as sigv4.

" + }, + "opWithSigv4aSignedPayload": { + "name": "OpWithSigv4aSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4a"], + "documentation": "

Performs a GET request to get-row with signed payload and auth as sigv4a.

" + }, + "opWithSigv4aUnSignedPayload": { + "name": "opWithSigv4aUnSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4a"], + "unsignedPayload": true, + "documentation": "

Performs a GET request to get-row with unsigned payload and auth as sigv4a.

" + }, + "opWithSigv4SignedPayload": { + "name": "OpWithSigv4SignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4"], + "documentation": "

Performs a GET request to get-row with signed payload and auth as sigv4.

" + }, + "opWithSigv4UnSignedPayload": { + "name": "opWithSigv4UnSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4"], + "unsignedPayload": true, + "documentation": "

Performs a GET request to get-row with unsigned payload and auth as sigv4.

" + }, + "opsWithSigv4andSigv4aSignedPayload": { + "name": "opsWithSigv4andSigv4aSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4","aws.auth#sigv4a"], + "documentation": "

Performs a GET request to get-row with signed payload and auth as sigv4 and sigv4a.

" + }, + "secondOpsWithSigv4andSigv4aSignedPayload": { + "name": "secondOpsWithSigv4andSigv4aSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4","aws.auth#sigv4a"], + "documentation": "

Performs a GET request to get-row with signed payload and auth as sigv4 and sigv4a.

" + }, + "opWithSigv4AndSigv4aUnSignedPayload": { + "name": "opWithSigv4AndSigv4aUnSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4","aws.auth#sigv4a"], + "unsignedPayload": true, + "documentation": "

Performs a GET request to get-row with unsigned payload and auth as sigv4 and sigv4a.

" } }, "shapes": { From 22ecdf6bab12d8a270b7a4a91696ce82f5b67840 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Tue, 7 Jan 2025 12:20:55 -0800 Subject: [PATCH 06/14] Adding Codegen support for unsignedPayload model trait for multi-auth auth schemes --- .../amazon/awssdk/codegen/AddOperations.java | 1 + .../model/intermediate/OperationModel.java | 10 ++ .../codegen/model/service/Operation.java | 14 +- .../scheme/AuthSchemeCodegenMetadataExt.java | 70 ++++++-- .../codegen/poet/auth/scheme/AuthTrait.java | 100 ++++++++++++ .../scheme/ModelAuthSchemeKnowledgeIndex.java | 55 +++++-- .../ops-with-auth-sigv4a-value/service-2.json | 150 +++++++++++++++++- 7 files changed, 364 insertions(+), 36 deletions(-) create mode 100644 codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTrait.java diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java b/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java index 5fc680eccb48..5576c6487802 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/AddOperations.java @@ -182,6 +182,7 @@ public Map constructOperations() { operationModel.setStaticContextParams(op.getStaticContextParams()); operationModel.setOperationContextParams(op.getOperationContextParams()); operationModel.setAuth(getAuthFromOperation(op)); + operationModel.setUnsignedPayload(op.isUnsignedPayload()); Input input = op.getInput(); if (input != null) { diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java index 76a46d697abf..a2a060c7a915 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/intermediate/OperationModel.java @@ -87,6 +87,8 @@ public class OperationModel extends DocumentationModel { @JsonIgnore private Map operationContextParams; + private boolean unsignedPayload; + public String getOperationName() { return operationName; } @@ -369,4 +371,12 @@ public Map getOperationContextParams() { public void setOperationContextParams(Map operationContextParams) { this.operationContextParams = operationContextParams; } + + public boolean isUnsignedPayload() { + return unsignedPayload; + } + + public void setUnsignedPayload(boolean unsignedPayload) { + this.unsignedPayload = unsignedPayload; + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Operation.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Operation.java index bf74350bd925..efa1da4de367 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Operation.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/service/Operation.java @@ -61,6 +61,8 @@ public class Operation { private Map operationContextParams; + private boolean unsignedPayload; + public String getName() { return name; } @@ -78,8 +80,8 @@ public boolean isDeprecated() { return deprecated; } - public void setDeprecated(boolean deprecated) { - this.deprecated = deprecated; + public void setUnsignedPayload(boolean unsignedPayload) { + this.unsignedPayload = unsignedPayload; } public String getDeprecatedMessage() { @@ -227,4 +229,12 @@ public Map getOperationContextParams() { public void setOperationContextParams(Map operationContextParams) { this.operationContextParams = operationContextParams; } + + public boolean isUnsignedPayload() { + return unsignedPayload; + } + + public void setDeprecated(boolean deprecated) { + this.deprecated = deprecated; + } } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java index c7c7b6168bb7..7e31c61c793a 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthSchemeCodegenMetadataExt.java @@ -31,6 +31,7 @@ import software.amazon.awssdk.http.auth.scheme.BearerAuthScheme; import software.amazon.awssdk.http.auth.scheme.NoAuthAuthScheme; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.http.auth.spi.signer.HttpSigner; import software.amazon.awssdk.http.auth.spi.signer.SignerProperty; /** @@ -91,24 +92,34 @@ private AuthSchemeCodegenMetadataExt() { /** * Creates a new auth scheme codegen metadata instance using the defaults for the given {@link AuthType} defaults. */ - public static AuthSchemeCodegenMetadata fromAuthType(AuthType type) { - switch (type) { + public static AuthSchemeCodegenMetadata fromAuthType(AuthTrait authTrait) { + switch (authTrait.authType()) { case BEARER: return BEARER; case NONE: return NO_AUTH; case V4A: - return SIGV4A; + return getSigv4aAuthSchemeBuilder(authTrait).build(); default: - String authTypeName = type.value(); - SigV4SignerDefaults defaults = AuthTypeToSigV4Default.authTypeToDefaults().get(authTypeName); - if (defaults == null) { - throw new IllegalArgumentException("Unknown auth type: " + type); - } - return fromConstants(defaults); + return resolveAuthSchemeForType(authTrait); } } + private static AuthSchemeCodegenMetadata resolveAuthSchemeForType(AuthTrait authTrait) { + String authTypeName = authTrait.authType().value(); + SigV4SignerDefaults defaults = AuthTypeToSigV4Default.authTypeToDefaults().get(authTypeName); + + if (defaults == null) { + throw new IllegalArgumentException("Unknown auth option: " + authTrait + " with type " + authTypeName); + } + if (authTrait.isUnsignedPayload()) { + defaults = defaults.toBuilder() + .payloadSigningEnabled(false) + .build(); + } + return fromConstants(defaults); + } + /** * Transforms a {@link SigV4SignerDefaults} instance to an {@link AuthSchemeCodegenMetadata} instance. */ @@ -171,25 +182,54 @@ public static CodeBlock codegenSignerPropertiesIfAbsent( private static List propertiesFromConstants(SigV4SignerDefaults constants) { List properties = new ArrayList<>(); if (constants.payloadSigningEnabled() != null) { - properties.add(from("PAYLOAD_SIGNING_ENABLED", constants::payloadSigningEnabled)); + properties.add(from("PAYLOAD_SIGNING_ENABLED", constants::payloadSigningEnabled, AwsV4HttpSigner.class)); } if (constants.doubleUrlEncode() != null) { - properties.add(from("DOUBLE_URL_ENCODE", constants::doubleUrlEncode)); + properties.add(from("DOUBLE_URL_ENCODE", constants::doubleUrlEncode, AwsV4HttpSigner.class)); } if (constants.normalizePath() != null) { - properties.add(from("NORMALIZE_PATH", constants::normalizePath)); + properties.add(from("NORMALIZE_PATH", constants::normalizePath, AwsV4HttpSigner.class)); } if (constants.chunkEncodingEnabled() != null) { - properties.add(from("CHUNK_ENCODING_ENABLED", constants::chunkEncodingEnabled)); + properties.add(from("CHUNK_ENCODING_ENABLED", constants::chunkEncodingEnabled, AwsV4HttpSigner.class)); } return properties; } - private static SignerPropertyValueProvider from(String name, Supplier valueSupplier) { + private static SignerPropertyValueProvider from(String name, + Supplier valueSupplier, + Class containingClass) { return SignerPropertyValueProvider.builder() - .containingClass(AwsV4HttpSigner.class) + .containingClass(containingClass) .fieldName(name) .constantValueSupplier(valueSupplier) .build(); } + + private static Builder getSigv4aAuthSchemeBuilder(AuthTrait authTrait) { + Builder sigv4aBuilder = builder() + .schemeId(AwsV4aAuthScheme.SCHEME_ID) + .authSchemeClass(AwsV4aAuthScheme.class); + + addCommonSigv4aProperties(sigv4aBuilder); + + if (authTrait.isUnsignedPayload()) { + sigv4aBuilder.addProperty(from("PAYLOAD_SIGNING_ENABLED", () -> false, AwsV4aHttpSigner.class)); + } + return sigv4aBuilder; + } + + private static void addCommonSigv4aProperties(Builder builder) { + builder.addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4aHttpSigner.class) + .fieldName("SERVICE_SIGNING_NAME") + .valueEmitter((spec, utils) -> spec.add("$S", utils.signingName())) + .build()) + .addProperty(SignerPropertyValueProvider.builder() + .containingClass(AwsV4aHttpSigner.class) + .fieldName("REGION_SET") + .valueEmitter((spec, utils) -> spec.add("$L", "params.regionSet()")) + .build()); + } + } diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTrait.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTrait.java new file mode 100644 index 000000000000..dee3d4f35f55 --- /dev/null +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/AuthTrait.java @@ -0,0 +1,100 @@ +/* + * 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.codegen.poet.auth.scheme; + +import java.util.Objects; +import software.amazon.awssdk.codegen.model.service.AuthType; + +/** + * Represents an authentication option, encapsulating attributes such as the authentication type + * and whether the payload should be unsigned. This class provides a clean and immutable way + * to model these attributes as separate traits, as specified in the service models. + * + *

The primary purpose of this class is to hold authentication-related attributes, such as: + *

    + *
  • authType: Specifies the type of authentication to be used (e.g., SigV4, SigV4A etc).
  • + *
  • unsignedPayload: Indicates whether the payload should be unsigned.
  • + *
+ */ +public final class AuthTrait { + + private final AuthType authType; + private final boolean unsignedPayload; + + private AuthTrait(AuthType authType, boolean unsignedPayload) { + this.authType = Objects.requireNonNull(authType, "authType must not be null"); + this.unsignedPayload = unsignedPayload; + } + + public static Builder builder() { + return new Builder(); + } + + public AuthType authType() { + return authType; + } + + public boolean isUnsignedPayload() { + return unsignedPayload; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AuthTrait that = (AuthTrait) o; + return unsignedPayload == that.unsignedPayload && authType == that.authType; + } + + @Override + public int hashCode() { + int hashCode = 1; + hashCode = 31 * hashCode + Objects.hashCode(authType); + hashCode = 31 * hashCode + (unsignedPayload ? 1 : 0); + return hashCode; + } + + @Override + public String toString() { + return "AuthTrait{" + + "authType=" + authType + + ", unsignedPayload=" + unsignedPayload + + '}'; + } + + public static class Builder { + private AuthType authType; + private boolean unsignedPayload = false; + + public Builder authType(AuthType authType) { + this.authType = authType; + return this; + } + + public Builder unsignedPayload(boolean unsignedPayload) { + this.unsignedPayload = unsignedPayload; + return this; + } + + public AuthTrait build() { + return new AuthTrait(authType, unsignedPayload); + } + } +} diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java index 2b0a39145e43..9911050bf22a 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java @@ -72,9 +72,9 @@ public Map, List> operationsToMetadata() * {@link Collections#emptyList()}. */ private Map, List> operationsToModeledMetadata() { - Map, List> operationsToAuthType = operationsToAuthType(); + Map, List> operationsToAuthOption = operationsToAuthOptions(); Map, List> operationsToMetadata = new LinkedHashMap<>(); - operationsToAuthType.forEach((k, v) -> operationsToMetadata.put(k, authTypeToCodegenMetadata(v))); + operationsToAuthOption.forEach((k, v) -> operationsToMetadata.put(k, authOptionToCodegenMetadata(v))); return operationsToMetadata; } @@ -82,16 +82,19 @@ private Map, List> operationsToModeledMe * Returns a map from list of operations to the list of auth-types modeled for those operations. The values are taken directly * from the model {@link OperationModel#getAuth()} method. */ - private Map, List> operationsToAuthType() { - Map, List> authSchemesToOperations = + private Map, List> operationsToAuthOptions() { + // Create a map from lists of AuthTrait to lists of operation names + Map, List> authOptionToOperations = intermediateModel.getOperations() .entrySet() .stream() .filter(kvp -> !kvp.getValue().getAuth().isEmpty()) - .collect(Collectors.groupingBy(kvp -> kvp.getValue().getAuth(), - Collectors.mapping(Map.Entry::getKey, Collectors.toList()))); + .collect(Collectors.groupingBy( + kvp -> toAuthOptions(kvp.getValue()), + Collectors.mapping(Map.Entry::getKey, Collectors.toList()) + )); - Map, List> operationsToAuthType = authSchemesToOperations + Map, List> operationsToAuthOption = authOptionToOperations .entrySet() .stream() .sorted(Comparator.comparing(kvp -> kvp.getValue().get(0))) @@ -99,17 +102,28 @@ private Map, List> operationsToAuthType() { Map.Entry::getKey, (a, b) -> b, LinkedHashMap::new)); - List serviceDefaults = serviceDefaultAuthTypes(); + List serviceDefaults = serviceDefaultAuthOption(); + + // Handle operations with defaults + List operationsWithDefaults = authOptionToOperations.remove(serviceDefaults); + if (operationsWithDefaults != null) { + operationsToAuthOption.remove(operationsWithDefaults); + } + operationsToAuthOption.put(Collections.emptyList(), serviceDefaults); + return operationsToAuthOption; + } - // Get the list of operations that share the same auth schemes as the system defaults and remove it from the result. We - // will take care of all of these in the fallback `default` case. - List operationsWithDefaults = authSchemesToOperations.remove(serviceDefaults); - operationsToAuthType.remove(operationsWithDefaults); - operationsToAuthType.put(Collections.emptyList(), serviceDefaults); - return operationsToAuthType; + private List toAuthOptions(OperationModel operation) { + return operation.getAuth().stream() + .map(authType -> AuthTrait.builder() + .authType(authType) + .unsignedPayload(operation.isUnsignedPayload()) + .build()) + .collect(Collectors.toList()); } + /** * Similar to {@link #operationsToModeledMetadata()} computes a map from operations to codegen metadata objects. The service * default list of codegen metadata is keyed with {@link Collections#emptyList()}. @@ -153,7 +167,18 @@ private List serviceDefaultAuthTypes() { return Collections.singletonList(intermediateModel.getMetadata().getAuthType()); } - private List authTypeToCodegenMetadata(List authTypes) { + /** + * Returns the list of modeled top-level auth-options. + */ + private List serviceDefaultAuthOption() { + List modeled = intermediateModel.getMetadata().getAuth(); + if (!modeled.isEmpty()) { + return modeled.stream().map(r -> AuthTrait.builder().authType(r).build()).collect(Collectors.toList()); + } + return Collections.singletonList(AuthTrait.builder().authType(intermediateModel.getMetadata().getAuthType()).build()); + } + + private List authOptionToCodegenMetadata(List authTypes) { return authTypes.stream().map(AuthSchemeCodegenMetadataExt::fromAuthType).collect(Collectors.toList()); } 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 313162ffdd09..1d273a0cf1b1 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 @@ -32,8 +32,7 @@ "shape": "InvalidInputException" } ], - "documentation": "

Performs a get row operation no output

" - }, + "documentation": "

Performs a GET request to get-row with default payload signing.

" }, "PutRow": { "name": "PutRow", "auth": ["v4"], @@ -52,7 +51,7 @@ "shape": "InvalidInputException" } ], - "documentation": "

Performs a get row operation no output

" + "documentation": "

Performs a PUT request to put-row with auth as sigv4.

" }, "DeleteRow": { "name": "DeleteRow", @@ -72,7 +71,150 @@ "shape": "InvalidInputException" } ], - "documentation": "

Performs a get row operation no output

" + "documentation": "

Performs a DELETE request to delete-row with auth as sigv4.

" + }, + "opWithSigv4aSignedPayload": { + "name": "OpWithSigv4aSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4a"], + "documentation": "

Performs a GET request to get-row with signed payload and auth as sigv4a.

" + }, + "opWithSigv4aUnSignedPayload": { + "name": "opWithSigv4aUnSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4a"], + "unsignedPayload": true, + "documentation": "

Performs a GET request to get-row with unsigned payload and auth as sigv4a.

" + }, + "opWithSigv4SignedPayload": { + "name": "OpWithSigv4SignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4"], + "documentation": "

Performs a GET request to get-row with signed payload and auth as sigv4.

" + }, + "opWithSigv4UnSignedPayload": { + "name": "opWithSigv4UnSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4"], + "unsignedPayload": true, + "documentation": "

Performs a GET request to get-row with unsigned payload and auth as sigv4.

" + }, + "opsWithSigv4andSigv4aSignedPayload": { + "name": "opsWithSigv4andSigv4aSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4","aws.auth#sigv4a"], + "documentation": "

Performs a GET request to get-row with signed payload and auth as sigv4 and sigv4a.

" + }, + "secondOpsWithSigv4andSigv4aSignedPayload": { + "name": "secondOpsWithSigv4andSigv4aSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4","aws.auth#sigv4a"], + "documentation": "

Performs a GET request to get-row with signed payload and auth as sigv4 and sigv4a.

" + }, + "opWithSigv4AndSigv4aUnSignedPayload": { + "name": "opWithSigv4AndSigv4aUnSignedPayload", + "http": { + "method": "GET", + "requestUri": "/get-row/" + }, + "input": { + "shape": "GetRowRequest" + }, + "output": { + "shape": "GetRowResponse" + }, + "errors": [ + { + "shape": "InvalidInputException" + } + ], + "auth": ["aws.auth#sigv4","aws.auth#sigv4a"], + "unsignedPayload": true, + "documentation": "

Performs a GET request to get-row with unsigned payload and auth as sigv4 and sigv4a.

" } }, "shapes": { From bb3beca121adb862a2cff31fddfedf8044ea5c02 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Tue, 7 Jan 2025 12:54:38 -0800 Subject: [PATCH 07/14] Adding Codegen support for unsignedPayload model trait for multi-auth auth schemes --- ...4a-value-auth-scheme-default-provider.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-provider.java b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-provider.java index 8b66e935f3a9..8d2f50135504 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-provider.java +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/auth/scheme/ops-auth-sigv4a-value-auth-scheme-default-provider.java @@ -29,10 +29,47 @@ public List resolveAuthScheme(DatabaseAuthSchemeParams params) switch (params.operation()) { case "DeleteRow": case "PutRow": + case "opWithSigv4SignedPayload": options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "database-service") .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()).build()); break; + case "opWithSigv4AndSigv4aUnSignedPayload": + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()) + .putSignerProperty(AwsV4HttpSigner.PAYLOAD_SIGNING_ENABLED, false).build()); + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, params.regionSet()) + .putSignerProperty(AwsV4aHttpSigner.PAYLOAD_SIGNING_ENABLED, false).build()); + break; + case "opWithSigv4UnSignedPayload": + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()) + .putSignerProperty(AwsV4HttpSigner.PAYLOAD_SIGNING_ENABLED, false).build()); + break; + case "opWithSigv4aSignedPayload": + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, params.regionSet()).build()); + break; + case "opWithSigv4aUnSignedPayload": + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, params.regionSet()) + .putSignerProperty(AwsV4aHttpSigner.PAYLOAD_SIGNING_ENABLED, false).build()); + break; + case "opsWithSigv4andSigv4aSignedPayload": + case "secondOpsWithSigv4andSigv4aSignedPayload": + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4") + .putSignerProperty(AwsV4HttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4HttpSigner.REGION_NAME, params.region().id()).build()); + options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4a") + .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "database-service") + .putSignerProperty(AwsV4aHttpSigner.REGION_SET, params.regionSet()).build()); + break; default: options.add(AuthSchemeOption.builder().schemeId("aws.auth#sigv4a") .putSignerProperty(AwsV4aHttpSigner.SERVICE_SIGNING_NAME, "database-service") From 1825f54709c6a2a3ae1a42c5d485cc009dff73a6 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Wed, 8 Jan 2025 12:31:02 -0800 Subject: [PATCH 08/14] Handled comments --- .../scheme/ModelAuthSchemeKnowledgeIndex.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java index 9911050bf22a..8b904666121e 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java @@ -79,22 +79,25 @@ private Map, List> operationsToModeledMe } /** - * Returns a map from list of operations to the list of auth-types modeled for those operations. The values are taken directly - * from the model {@link OperationModel#getAuth()} method. + * Returns a map from a list of operations to the list of auth-types modeled for those operations. + * The {@link AuthTrait} values are taken directly from the {@link OperationModel} */ private Map, List> operationsToAuthOptions() { - // Create a map from lists of AuthTrait to lists of operation names - Map, List> authOptionToOperations = + // Group operations by their shared AuthTraits. + // The map's keys are AuthTrait lists, and the values are lists of operation names. + Map, List> authTraitToOperations = intermediateModel.getOperations() .entrySet() .stream() .filter(kvp -> !kvp.getValue().getAuth().isEmpty()) .collect(Collectors.groupingBy( - kvp -> toAuthOptions(kvp.getValue()), + kvp -> toAuthTrait(kvp.getValue()), Collectors.mapping(Map.Entry::getKey, Collectors.toList()) )); - Map, List> operationsToAuthOption = authOptionToOperations + // Convert the map to have operation names as keys and AuthTrait options as values, + // sorted by the first operation name in each group. + Map, List> operationsToAuthOption = authTraitToOperations .entrySet() .stream() .sorted(Comparator.comparing(kvp -> kvp.getValue().get(0))) @@ -105,7 +108,7 @@ private Map, List> operationsToAuthOptions() { List serviceDefaults = serviceDefaultAuthOption(); // Handle operations with defaults - List operationsWithDefaults = authOptionToOperations.remove(serviceDefaults); + List operationsWithDefaults = authTraitToOperations.remove(serviceDefaults); if (operationsWithDefaults != null) { operationsToAuthOption.remove(operationsWithDefaults); } @@ -113,7 +116,11 @@ private Map, List> operationsToAuthOptions() { return operationsToAuthOption; } - private List toAuthOptions(OperationModel operation) { + /** + * Converts an {@link OperationModel} to a list of {@link AuthTrait} instances based on the authentication related traits + * defined in the {@link OperationModel}. + */ + private List toAuthTrait(OperationModel operation) { return operation.getAuth().stream() .map(authType -> AuthTrait.builder() .authType(authType) @@ -122,8 +129,6 @@ private List toAuthOptions(OperationModel operation) { .collect(Collectors.toList()); } - - /** * Similar to {@link #operationsToModeledMetadata()} computes a map from operations to codegen metadata objects. The service * default list of codegen metadata is keyed with {@link Collections#emptyList()}. From ee5a7c59bebfb26a5711f2410f1aacb2c3a4515b Mon Sep 17 00:00:00 2001 From: John Viegas Date: Wed, 8 Jan 2025 12:36:07 -0800 Subject: [PATCH 09/14] updated variable names --- .../auth/scheme/ModelAuthSchemeKnowledgeIndex.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java index 8b904666121e..1db9c7368d6a 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeKnowledgeIndex.java @@ -85,7 +85,7 @@ private Map, List> operationsToModeledMe private Map, List> operationsToAuthOptions() { // Group operations by their shared AuthTraits. // The map's keys are AuthTrait lists, and the values are lists of operation names. - Map, List> authTraitToOperations = + Map, List> authSchemesToOperations = intermediateModel.getOperations() .entrySet() .stream() @@ -97,7 +97,7 @@ private Map, List> operationsToAuthOptions() { // Convert the map to have operation names as keys and AuthTrait options as values, // sorted by the first operation name in each group. - Map, List> operationsToAuthOption = authTraitToOperations + Map, List> operationsToAuthTrait = authSchemesToOperations .entrySet() .stream() .sorted(Comparator.comparing(kvp -> kvp.getValue().get(0))) @@ -108,12 +108,12 @@ private Map, List> operationsToAuthOptions() { List serviceDefaults = serviceDefaultAuthOption(); // Handle operations with defaults - List operationsWithDefaults = authTraitToOperations.remove(serviceDefaults); + List operationsWithDefaults = authSchemesToOperations.remove(serviceDefaults); if (operationsWithDefaults != null) { - operationsToAuthOption.remove(operationsWithDefaults); + operationsToAuthTrait.remove(operationsWithDefaults); } - operationsToAuthOption.put(Collections.emptyList(), serviceDefaults); - return operationsToAuthOption; + operationsToAuthTrait.put(Collections.emptyList(), serviceDefaults); + return operationsToAuthTrait; } /** From 5a742a5cfa757caf55da04f19221d243aeed3125 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Wed, 15 Jan 2025 11:11:18 -0800 Subject: [PATCH 10/14] Add Endpoint Params in Auth Scheme params for MultiAuth Sigv4/4a and rename enableEndpointAuthSchemeParams as endpointBasedAuthSchemeParamsLegacy to avoid confusion --- .../tasks/AuthSchemeGeneratorTasks.java | 2 + .../customization/CustomizationConfig.java | 10 +-- .../auth/scheme/AuthSchemeParamsSpec.java | 5 +- .../poet/auth/scheme/AuthSchemeSpecUtils.java | 21 ++++- .../scheme/DefaultAuthSchemeParamsSpec.java | 12 +-- .../ModelAuthSchemeClassesKnowledgeIndex.java | 5 +- .../awssdk/codegen/poet/ClientTestModels.java | 5 ++ ...gv4a-value-auth-scheme-default-params.java | 90 ++++++++++++++++++- ...-auth-sigv4a-value-auth-scheme-params.java | 16 ++++ .../endpoint-rule-set.json | 89 ++++++++++++++++++ ...n-endpoint-auth-params-with-allowed.config | 2 +- ...ndpoint-auth-params-without-allowed.config | 2 +- .../codegen-resources/customization.config | 2 +- .../codegen-resources/customization.config | 2 +- .../codegen-resources/customization.config | 2 +- .../codegen-resources/customization.config | 2 +- 16 files changed, 243 insertions(+), 24 deletions(-) create mode 100644 codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AuthSchemeGeneratorTasks.java b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AuthSchemeGeneratorTasks.java index fbcec7931bd8..08b976ca5a05 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AuthSchemeGeneratorTasks.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/emitters/tasks/AuthSchemeGeneratorTasks.java @@ -48,6 +48,8 @@ protected List createTasks() { tasks.add(generateAuthSchemeInterceptor()); if (authSchemeSpecUtils.useEndpointBasedAuthProvider()) { tasks.add(generateEndpointBasedProvider()); + } + if (authSchemeSpecUtils.useEndpointParamsInAuthScheme()) { tasks.add(generateEndpointAwareAuthSchemeParams()); } return tasks; diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java index 3cbdd6d83f60..4f2af40748c2 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java @@ -284,7 +284,7 @@ public class CustomizationConfig { /** * Whether to generate auth scheme params based on endpoint params. */ - private boolean enableEndpointAuthSchemeParams = false; + private boolean endpointBasedAuthSchemeParamsLegacy = false; /** * List of endpoint params to be used for the auth scheme params @@ -812,12 +812,12 @@ public boolean useSraAuth() { return useSraAuth; } - public void setEnableEndpointAuthSchemeParams(boolean enableEndpointAuthSchemeParams) { - this.enableEndpointAuthSchemeParams = enableEndpointAuthSchemeParams; + public void setEndpointBasedAuthSchemeParamsLegacy(boolean endpointBasedAuthSchemeParamsLegacy) { + this.endpointBasedAuthSchemeParamsLegacy = endpointBasedAuthSchemeParamsLegacy; } - public boolean isEnableEndpointAuthSchemeParams() { - return enableEndpointAuthSchemeParams; + public boolean isEndpointBasedAuthSchemeParamsLegacy() { + return endpointBasedAuthSchemeParamsLegacy; } public void setAllowedEndpointAuthSchemeParams(List allowedEndpointAuthSchemeParams) { 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 a5a715c356f3..38f90a436129 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 @@ -131,7 +131,8 @@ private void addAccessorMethods(TypeSpec.Builder b) { .build()); } - if (authSchemeSpecUtils.generateEndpointBasedParams()) { + // Include Endpoint params in Auth Schemes to resolve the Endpoint for obtaining Signing properties in Multi Auth. + if (authSchemeSpecUtils.useEndpointParamsInAuthScheme()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { MethodSpec accessor = endpointRulesSpecUtils.parameterInterfaceAccessorMethod(name, model); @@ -184,7 +185,7 @@ private void addBuilderSetterMethods(TypeSpec.Builder b) { } - if (authSchemeSpecUtils.generateEndpointBasedParams()) { + if (authSchemeSpecUtils.useEndpointParamsInAuthScheme()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { ClassName parametersInterfaceName = authSchemeSpecUtils.parametersInterfaceName(); 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 c29a1d3bb66d..03c909ebf4b9 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 @@ -25,6 +25,7 @@ import java.util.Set; import software.amazon.awssdk.codegen.model.config.customization.CustomizationConfig; import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; +import software.amazon.awssdk.codegen.model.service.AuthType; import software.amazon.awssdk.codegen.utils.AuthUtils; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; @@ -125,7 +126,7 @@ public String paramMethodName(String name) { } public boolean generateEndpointBasedParams() { - return intermediateModel.getCustomizationConfig().isEnableEndpointAuthSchemeParams(); + return intermediateModel.getCustomizationConfig().isEndpointBasedAuthSchemeParamsLegacy(); } public boolean includeParam(String name) { @@ -147,6 +148,24 @@ public boolean includeParamForProvider(String name) { return true; } + public boolean hasMultiAuthSigvOrSigv4a() { + List authList = intermediateModel.getMetadata().getAuth(); + + return (authList.size() > 1 && + authList.stream().anyMatch(authType -> authType == AuthType.V4 || authType == AuthType.V4A)) + || + intermediateModel.getOperations() + .values() + .stream() + .flatMap(operationModel -> operationModel.getAuth().stream()) + .anyMatch(authType -> authType == AuthType.V4 || authType == AuthType.V4A); + } + + //Include Endpoint params in Auth Schemes to resolve the Endpoint for obtaining Signing properties in Multi Auth. + public boolean useEndpointParamsInAuthScheme() { + return generateEndpointBasedParams() || hasMultiAuthSigvOrSigv4a(); + } + public String serviceName() { return intermediateModel.getMetadata().getServiceName(); } 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 d8e981a3ee6b..69f1d8781659 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 @@ -60,7 +60,7 @@ public TypeSpec poetSpec() { .addMethod(builderMethod()) .addType(builderImplSpec()); - if (authSchemeSpecUtils.useEndpointBasedAuthProvider()) { + if (authSchemeSpecUtils.useEndpointBasedAuthProvider() || authSchemeSpecUtils.hasMultiAuthSigvOrSigv4a()) { b.addSuperinterface(authSchemeSpecUtils.parametersEndpointAwareDefaultImplName()); } @@ -84,7 +84,7 @@ private MethodSpec constructor() { b.addStatement("this.regionSet = builder.regionSet"); } - if (authSchemeSpecUtils.generateEndpointBasedParams()) { + if (authSchemeSpecUtils.useEndpointParamsInAuthScheme()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { String fieldName = authSchemeSpecUtils.paramMethodName(name); @@ -122,7 +122,7 @@ private TypeSpec builderImplSpec() { .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) .addSuperinterface(authSchemeSpecUtils.parametersInterfaceBuilderInterfaceName()); - if (authSchemeSpecUtils.useEndpointBasedAuthProvider()) { + if (authSchemeSpecUtils.useEndpointParamsInAuthScheme()) { b.addSuperinterface(authSchemeSpecUtils.parametersEndpointAwareDefaultImplName().nestedClass("Builder")); } @@ -153,7 +153,7 @@ private void addBuilderConstructors(TypeSpec.Builder b) { if (authSchemeSpecUtils.usesSigV4a()) { builderFromInstance.addStatement("this.regionSet = params.regionSet"); } - if (authSchemeSpecUtils.generateEndpointBasedParams()) { + if (authSchemeSpecUtils.useEndpointParamsInAuthScheme()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { builderFromInstance.addStatement("this.$1N = params.$1N", endpointRulesSpecUtils.variableName(name)); @@ -202,7 +202,7 @@ private void addFieldsAndAccessors(TypeSpec.Builder b) { .build()); } - if (authSchemeSpecUtils.generateEndpointBasedParams()) { + if (authSchemeSpecUtils.useEndpointParamsInAuthScheme()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { b.addField(endpointRulesSpecUtils.parameterClassField(name, model)); @@ -255,7 +255,7 @@ private void addBuilderFieldsAndSetter(TypeSpec.Builder b) { b.addMethod(builderSetterMethod("regionSet", TypeName.get(RegionSet.class))); } - if (authSchemeSpecUtils.generateEndpointBasedParams()) { + if (authSchemeSpecUtils.useEndpointParamsInAuthScheme()) { parameters().forEach((name, model) -> { if (authSchemeSpecUtils.includeParam(name)) { b.addField(endpointRulesSpecUtils.parameterBuilderFieldSpec(name, model)); diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java index 92c6047301c7..978f3d27cce3 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java @@ -35,8 +35,9 @@ public final class ModelAuthSchemeClassesKnowledgeIndex { private ModelAuthSchemeClassesKnowledgeIndex(IntermediateModel intermediateModel) { this.serviceConcreteAuthSchemeClasses = - getServiceConcreteAuthSchemeClasses(ModelAuthSchemeKnowledgeIndex.of(intermediateModel).operationsToMetadata(), - intermediateModel.getCustomizationConfig().isEnableEndpointAuthSchemeParams()); + getServiceConcreteAuthSchemeClasses( + ModelAuthSchemeKnowledgeIndex.of(intermediateModel).operationsToMetadata(), + intermediateModel.getCustomizationConfig().isEndpointBasedAuthSchemeParamsLegacy()); } /** diff --git a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java index c94e90937590..4d0437623dce 100644 --- a/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java +++ b/codegen/src/test/java/software/amazon/awssdk/codegen/poet/ClientTestModels.java @@ -341,10 +341,15 @@ public static IntermediateModel opsWithSigv4a() { File customizationModel = new File(ClientTestModels.class.getResource("client/c2j/ops-with-auth-sigv4a-value/customization.config") .getFile()); + + File endpointRuleSetModel = + new File(ClientTestModels.class.getResource("client/c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json").getFile()); + C2jModels models = C2jModels .builder() .serviceModel(getServiceModel(serviceModel)) .customizationConfig(getCustomizationConfig(customizationModel)) + .endpointRuleSetModel(getEndpointRuleSet(endpointRuleSetModel)) .build(); return new IntermediateModelBuilder(models).build(); 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 index 8fc91e2069fe..40291743ce9e 100644 --- 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 @@ -5,21 +5,37 @@ 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.services.database.endpoints.DatabaseEndpointProvider; import software.amazon.awssdk.utils.Validate; @Generated("software.amazon.awssdk:codegen") @SdkInternalApi -public final class DefaultDatabaseAuthSchemeParams implements DatabaseAuthSchemeParams { +public final class DefaultDatabaseAuthSchemeParams implements DatabaseAuthSchemeParams, DatabaseEndpointResolverAware { private final String operation; private final Region region; private final RegionSet regionSet; + private final Boolean useDualStackEndpoint; + + private final Boolean useFIPSEndpoint; + + private final String accountId; + + private final String operationContextParam; + + private final DatabaseEndpointProvider endpointProvider; + private DefaultDatabaseAuthSchemeParams(Builder builder) { this.operation = Validate.paramNotNull(builder.operation, "operation"); this.region = builder.region; this.regionSet = builder.regionSet; + this.useDualStackEndpoint = builder.useDualStackEndpoint; + this.useFIPSEndpoint = builder.useFIPSEndpoint; + this.accountId = builder.accountId; + this.operationContextParam = builder.operationContextParam; + this.endpointProvider = builder.endpointProvider; } public static DatabaseAuthSchemeParams.Builder builder() { @@ -41,18 +57,53 @@ public RegionSet regionSet() { return regionSet; } + @Override + public Boolean useDualStackEndpoint() { + return useDualStackEndpoint; + } + + @Override + public Boolean useFipsEndpoint() { + return useFIPSEndpoint; + } + + @Override + public String accountId() { + return accountId; + } + + @Override + public String operationContextParam() { + return operationContextParam; + } + + @Override + public DatabaseEndpointProvider endpointProvider() { + return endpointProvider; + } + @Override public DatabaseAuthSchemeParams.Builder toBuilder() { return new Builder(this); } - private static final class Builder implements DatabaseAuthSchemeParams.Builder { + private static final class Builder implements DatabaseAuthSchemeParams.Builder, DatabaseEndpointResolverAware.Builder { private String operation; private Region region; private RegionSet regionSet; + private Boolean useDualStackEndpoint; + + private Boolean useFIPSEndpoint; + + private String accountId; + + private String operationContextParam; + + private DatabaseEndpointProvider endpointProvider; + Builder() { } @@ -60,6 +111,11 @@ private static final class Builder implements DatabaseAuthSchemeParams.Builder { this.operation = params.operation; this.region = params.region; this.regionSet = params.regionSet; + this.useDualStackEndpoint = params.useDualStackEndpoint; + this.useFIPSEndpoint = params.useFIPSEndpoint; + this.accountId = params.accountId; + this.operationContextParam = params.operationContextParam; + this.endpointProvider = params.endpointProvider; } @Override @@ -80,6 +136,36 @@ public Builder regionSet(RegionSet regionSet) { return this; } + @Override + public Builder useDualStackEndpoint(Boolean useDualStackEndpoint) { + this.useDualStackEndpoint = useDualStackEndpoint; + return this; + } + + @Override + public Builder useFipsEndpoint(Boolean useFIPSEndpoint) { + this.useFIPSEndpoint = useFIPSEndpoint; + return this; + } + + @Override + public Builder accountId(String accountId) { + this.accountId = accountId; + return this; + } + + @Override + public Builder operationContextParam(String operationContextParam) { + this.operationContextParam = operationContextParam; + return this; + } + + @Override + public Builder endpointProvider(DatabaseEndpointProvider endpointProvider) { + this.endpointProvider = endpointProvider; + 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 index 37a202d5ee3a..666f4a0666bb 100644 --- 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 @@ -36,6 +36,14 @@ static Builder builder() { */ RegionSet regionSet(); + Boolean useDualStackEndpoint(); + + Boolean useFipsEndpoint(); + + String accountId(); + + String operationContextParam(); + /** * Returns a {@link Builder} to customize the parameters. */ @@ -60,6 +68,14 @@ interface Builder extends CopyableBuilder { */ Builder regionSet(RegionSet regionSet); + Builder useDualStackEndpoint(Boolean useDualStackEndpoint); + + Builder useFipsEndpoint(Boolean useFIPSEndpoint); + + Builder accountId(String accountId); + + Builder operationContextParam(String operationContextParam); + /** * Returns a {@link DatabaseAuthSchemeParams} object that is created from the properties that have been set on * the builder. diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json new file mode 100644 index 000000000000..10539cc070e1 --- /dev/null +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json @@ -0,0 +1,89 @@ +{ + "version": "1.2", + "serviceId": "query", + "parameters": { + "region": { + "type": "string", + "builtIn": "AWS::Region", + "required": true, + "documentation": "The region to send requests to" + }, + "useDualStackEndpoint": { + "type": "boolean", + "builtIn": "AWS::UseDualStack" + }, + "useFIPSEndpoint": { + "type": "boolean", + "builtIn": "AWS::UseFIPS" + }, + "AccountId": { + "type": "String", + "builtIn": "AWS::Auth::AccountId" + }, + "operationContextParam": { + "type": "string" + } + }, + "rules": [ + { + "conditions": [ + { + "fn": "aws.partition", + "argv": [ + { + "ref": "region" + } + ], + "assign": "partitionResult" + } + ], + "rules": [ + { + "conditions": [ + { + "fn": "isSet", + "argv": [ + { + "ref": "endpointId" + } + ] + } + ], + "rules": [ + { + "conditions": [ + { + "fn": "isSet", + "argv": [ + { + "ref": "useFIPSEndpoint" + } + ] + } + ], + "error": "FIPS endpoints not supported with multi-region endpoints", + "type": "error" + }, + { + "endpoint": { + "url": "https://{endpointId}.query.{partitionResult#dualStackDnsSuffix}", + "properties": { + "authSchemes": [ + { + "name": "sigv4a", + "signingName": "query", + "signingRegionSet": ["*"] + } + ] + } + }, + "type": "endpoint" + } + ], + "type": "tree" + } + ], + "type": "tree" + } + ] +} diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-with-allowed.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-with-allowed.config index 789a83e3d7ae..840fd32e2d90 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-with-allowed.config +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-with-allowed.config @@ -5,7 +5,7 @@ "skipEndpointTests": { "test case 4": "Does not work" }, - "enableEndpointAuthSchemeParams": true, + "endpointBasedAuthSchemeParamsLegacy": true, "allowedEndpointAuthSchemeParams": [ "defaultTrueParam", "defaultStringParam", diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-without-allowed.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-without-allowed.config index fb6ef28ddd4b..3e6de74df557 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-without-allowed.config +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-without-allowed.config @@ -5,5 +5,5 @@ "skipEndpointTests": { "test case 4": "Does not work" }, - "enableEndpointAuthSchemeParams": true + "endpointBasedAuthSchemeParamsLegacy": true } diff --git a/services/cloudfrontkeyvaluestore/src/main/resources/codegen-resources/customization.config b/services/cloudfrontkeyvaluestore/src/main/resources/codegen-resources/customization.config index 7a81b5b04886..0b915d55983c 100644 --- a/services/cloudfrontkeyvaluestore/src/main/resources/codegen-resources/customization.config +++ b/services/cloudfrontkeyvaluestore/src/main/resources/codegen-resources/customization.config @@ -1,5 +1,5 @@ { "enableGenerateCompiledEndpointRules": true, - "enableEndpointAuthSchemeParams": true + "endpointBasedAuthSchemeParamsLegacy": true } diff --git a/services/eventbridge/src/main/resources/codegen-resources/customization.config b/services/eventbridge/src/main/resources/codegen-resources/customization.config index c6fd4b4deae1..2944648f9690 100644 --- a/services/eventbridge/src/main/resources/codegen-resources/customization.config +++ b/services/eventbridge/src/main/resources/codegen-resources/customization.config @@ -1,5 +1,5 @@ { - "enableEndpointAuthSchemeParams": true, + "endpointBasedAuthSchemeParamsLegacy": true, "allowedEndpointAuthSchemeParams": [ "EndpointId" ], diff --git a/services/s3/src/main/resources/codegen-resources/customization.config b/services/s3/src/main/resources/codegen-resources/customization.config index ccb726181f1a..504d6d43b28d 100644 --- a/services/s3/src/main/resources/codegen-resources/customization.config +++ b/services/s3/src/main/resources/codegen-resources/customization.config @@ -313,7 +313,7 @@ ], "requiredTraitValidationEnabled": true, "s3ExpressAuthSupport" : "true", - "enableEndpointAuthSchemeParams": true, + "endpointBasedAuthSchemeParamsLegacy": true, "customClientContextParams":{ "CrossRegionAccessEnabled":{ "documentation":"Enables cross-region bucket access for this client", diff --git a/services/sesv2/src/main/resources/codegen-resources/customization.config b/services/sesv2/src/main/resources/codegen-resources/customization.config index 022b7919d2ee..196177825543 100644 --- a/services/sesv2/src/main/resources/codegen-resources/customization.config +++ b/services/sesv2/src/main/resources/codegen-resources/customization.config @@ -1,6 +1,6 @@ { "enableGenerateCompiledEndpointRules": true, - "enableEndpointAuthSchemeParams": true + "endpointBasedAuthSchemeParamsLegacy": true } From b5c623beb20a6bee8760ef3e3281979fb892ca47 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Wed, 15 Jan 2025 11:28:31 -0800 Subject: [PATCH 11/14] revert enableEndpointAuthSchemeParams as endpointBasedAuthSchemeParamsLegacy since Internal customers are using this customization --- .../config/customization/CustomizationConfig.java | 10 +++++----- .../codegen/poet/auth/scheme/AuthSchemeSpecUtils.java | 2 +- .../scheme/ModelAuthSchemeClassesKnowledgeIndex.java | 2 +- ...tomization-endpoint-auth-params-with-allowed.config | 2 +- ...ization-endpoint-auth-params-without-allowed.config | 2 +- .../resources/codegen-resources/customization.config | 2 +- .../resources/codegen-resources/customization.config | 2 +- .../resources/codegen-resources/customization.config | 2 +- .../resources/codegen-resources/customization.config | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java index 4f2af40748c2..3cbdd6d83f60 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/model/config/customization/CustomizationConfig.java @@ -284,7 +284,7 @@ public class CustomizationConfig { /** * Whether to generate auth scheme params based on endpoint params. */ - private boolean endpointBasedAuthSchemeParamsLegacy = false; + private boolean enableEndpointAuthSchemeParams = false; /** * List of endpoint params to be used for the auth scheme params @@ -812,12 +812,12 @@ public boolean useSraAuth() { return useSraAuth; } - public void setEndpointBasedAuthSchemeParamsLegacy(boolean endpointBasedAuthSchemeParamsLegacy) { - this.endpointBasedAuthSchemeParamsLegacy = endpointBasedAuthSchemeParamsLegacy; + public void setEnableEndpointAuthSchemeParams(boolean enableEndpointAuthSchemeParams) { + this.enableEndpointAuthSchemeParams = enableEndpointAuthSchemeParams; } - public boolean isEndpointBasedAuthSchemeParamsLegacy() { - return endpointBasedAuthSchemeParamsLegacy; + public boolean isEnableEndpointAuthSchemeParams() { + return enableEndpointAuthSchemeParams; } public void setAllowedEndpointAuthSchemeParams(List allowedEndpointAuthSchemeParams) { 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 03c909ebf4b9..aced87690b8a 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 @@ -126,7 +126,7 @@ public String paramMethodName(String name) { } public boolean generateEndpointBasedParams() { - return intermediateModel.getCustomizationConfig().isEndpointBasedAuthSchemeParamsLegacy(); + return intermediateModel.getCustomizationConfig().isEnableEndpointAuthSchemeParams(); } public boolean includeParam(String name) { diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java index 978f3d27cce3..b143501e0da9 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java @@ -37,7 +37,7 @@ private ModelAuthSchemeClassesKnowledgeIndex(IntermediateModel intermediateModel this.serviceConcreteAuthSchemeClasses = getServiceConcreteAuthSchemeClasses( ModelAuthSchemeKnowledgeIndex.of(intermediateModel).operationsToMetadata(), - intermediateModel.getCustomizationConfig().isEndpointBasedAuthSchemeParamsLegacy()); + intermediateModel.getCustomizationConfig().isEnableEndpointAuthSchemeParams()); } /** diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-with-allowed.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-with-allowed.config index 840fd32e2d90..789a83e3d7ae 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-with-allowed.config +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-with-allowed.config @@ -5,7 +5,7 @@ "skipEndpointTests": { "test case 4": "Does not work" }, - "endpointBasedAuthSchemeParamsLegacy": true, + "enableEndpointAuthSchemeParams": true, "allowedEndpointAuthSchemeParams": [ "defaultTrueParam", "defaultStringParam", diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-without-allowed.config b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-without-allowed.config index 3e6de74df557..fb6ef28ddd4b 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-without-allowed.config +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/query/customization-endpoint-auth-params-without-allowed.config @@ -5,5 +5,5 @@ "skipEndpointTests": { "test case 4": "Does not work" }, - "endpointBasedAuthSchemeParamsLegacy": true + "enableEndpointAuthSchemeParams": true } diff --git a/services/cloudfrontkeyvaluestore/src/main/resources/codegen-resources/customization.config b/services/cloudfrontkeyvaluestore/src/main/resources/codegen-resources/customization.config index 0b915d55983c..7a81b5b04886 100644 --- a/services/cloudfrontkeyvaluestore/src/main/resources/codegen-resources/customization.config +++ b/services/cloudfrontkeyvaluestore/src/main/resources/codegen-resources/customization.config @@ -1,5 +1,5 @@ { "enableGenerateCompiledEndpointRules": true, - "endpointBasedAuthSchemeParamsLegacy": true + "enableEndpointAuthSchemeParams": true } diff --git a/services/eventbridge/src/main/resources/codegen-resources/customization.config b/services/eventbridge/src/main/resources/codegen-resources/customization.config index 2944648f9690..c6fd4b4deae1 100644 --- a/services/eventbridge/src/main/resources/codegen-resources/customization.config +++ b/services/eventbridge/src/main/resources/codegen-resources/customization.config @@ -1,5 +1,5 @@ { - "endpointBasedAuthSchemeParamsLegacy": true, + "enableEndpointAuthSchemeParams": true, "allowedEndpointAuthSchemeParams": [ "EndpointId" ], diff --git a/services/s3/src/main/resources/codegen-resources/customization.config b/services/s3/src/main/resources/codegen-resources/customization.config index 504d6d43b28d..ccb726181f1a 100644 --- a/services/s3/src/main/resources/codegen-resources/customization.config +++ b/services/s3/src/main/resources/codegen-resources/customization.config @@ -313,7 +313,7 @@ ], "requiredTraitValidationEnabled": true, "s3ExpressAuthSupport" : "true", - "endpointBasedAuthSchemeParamsLegacy": true, + "enableEndpointAuthSchemeParams": true, "customClientContextParams":{ "CrossRegionAccessEnabled":{ "documentation":"Enables cross-region bucket access for this client", diff --git a/services/sesv2/src/main/resources/codegen-resources/customization.config b/services/sesv2/src/main/resources/codegen-resources/customization.config index 196177825543..022b7919d2ee 100644 --- a/services/sesv2/src/main/resources/codegen-resources/customization.config +++ b/services/sesv2/src/main/resources/codegen-resources/customization.config @@ -1,6 +1,6 @@ { "enableGenerateCompiledEndpointRules": true, - "endpointBasedAuthSchemeParamsLegacy": true + "enableEndpointAuthSchemeParams": true } From 2938bcb05645cd481db2d099e062d4899fa31539 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Wed, 15 Jan 2025 11:35:51 -0800 Subject: [PATCH 12/14] Typo errors --- .../codegen/poet/auth/scheme/AuthSchemeParamsSpec.java | 1 - .../awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java | 4 +++- .../auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java | 5 ++--- 3 files changed, 5 insertions(+), 5 deletions(-) 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 38f90a436129..e503082f5e71 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 @@ -131,7 +131,6 @@ private void addAccessorMethods(TypeSpec.Builder b) { .build()); } - // Include Endpoint params in Auth Schemes to resolve the Endpoint for obtaining Signing properties in Multi Auth. if (authSchemeSpecUtils.useEndpointParamsInAuthScheme()) { 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 aced87690b8a..dd6bff3b2960 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 @@ -28,6 +28,7 @@ import software.amazon.awssdk.codegen.model.service.AuthType; import software.amazon.awssdk.codegen.utils.AuthUtils; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; +import software.amazon.awssdk.utils.CollectionUtils; public final class AuthSchemeSpecUtils { private static final Set DEFAULT_AUTH_SCHEME_PARAMS = setOf("region", "operation"); @@ -148,10 +149,11 @@ public boolean includeParamForProvider(String name) { return true; } + // New Multi Auth determined by "auth" triat on Service model or operation model. public boolean hasMultiAuthSigvOrSigv4a() { List authList = intermediateModel.getMetadata().getAuth(); - return (authList.size() > 1 && + return (!CollectionUtils.isNullOrEmpty(authList) && authList.stream().anyMatch(authType -> authType == AuthType.V4 || authType == AuthType.V4A)) || intermediateModel.getOperations() diff --git a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java index b143501e0da9..92c6047301c7 100644 --- a/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java +++ b/codegen/src/main/java/software/amazon/awssdk/codegen/poet/auth/scheme/ModelAuthSchemeClassesKnowledgeIndex.java @@ -35,9 +35,8 @@ public final class ModelAuthSchemeClassesKnowledgeIndex { private ModelAuthSchemeClassesKnowledgeIndex(IntermediateModel intermediateModel) { this.serviceConcreteAuthSchemeClasses = - getServiceConcreteAuthSchemeClasses( - ModelAuthSchemeKnowledgeIndex.of(intermediateModel).operationsToMetadata(), - intermediateModel.getCustomizationConfig().isEnableEndpointAuthSchemeParams()); + getServiceConcreteAuthSchemeClasses(ModelAuthSchemeKnowledgeIndex.of(intermediateModel).operationsToMetadata(), + intermediateModel.getCustomizationConfig().isEnableEndpointAuthSchemeParams()); } /** From 3ee618d6a875f05f319f2ab15e44ec9901f77db5 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Thu, 16 Jan 2025 14:54:15 -0800 Subject: [PATCH 13/14] Handled comments --- .../awssdk/codegen/poet/auth/scheme/AuthSchemeSpecUtils.java | 2 +- .../codegen/poet/auth/scheme/DefaultAuthSchemeParamsSpec.java | 2 +- .../c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 dd6bff3b2960..10a624b1e993 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 @@ -149,7 +149,7 @@ public boolean includeParamForProvider(String name) { return true; } - // New Multi Auth determined by "auth" triat on Service model or operation model. + //Multi-Auth option determined by "auth" trait on Service model or operation model. public boolean hasMultiAuthSigvOrSigv4a() { List authList = intermediateModel.getMetadata().getAuth(); 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 69f1d8781659..098a97b121f5 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 @@ -60,7 +60,7 @@ public TypeSpec poetSpec() { .addMethod(builderMethod()) .addType(builderImplSpec()); - if (authSchemeSpecUtils.useEndpointBasedAuthProvider() || authSchemeSpecUtils.hasMultiAuthSigvOrSigv4a()) { + if (authSchemeSpecUtils.useEndpointParamsInAuthScheme()) { b.addSuperinterface(authSchemeSpecUtils.parametersEndpointAwareDefaultImplName()); } diff --git a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json index 10539cc070e1..604a37682f4d 100644 --- a/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json +++ b/codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/ops-with-auth-sigv4a-value/endpoint-rule-set.json @@ -1,6 +1,6 @@ { "version": "1.2", - "serviceId": "query", + "serviceId": "Database Service", "parameters": { "region": { "type": "string", From 711935d85bb1627180252af5a529cab85079d7b4 Mon Sep 17 00:00:00 2001 From: John Viegas Date: Fri, 17 Jan 2025 17:52:28 -0800 Subject: [PATCH 14/14] Added AuthSchemed Interceptor changes to populate AuthScheme Params from Endpoint Params --- .../scheme/AuthSchemeInterceptorSpec.java | 40 ++++----- ...-sigv4a-value-auth-scheme-interceptor.java | 34 +++++--- .../scheme/query-auth-scheme-interceptor.java | 5 +- .../multiauth/endpoint-rule-set.json | 87 +++++-------------- .../multiauth/service-2.json | 23 ++++- .../multiauth/Sigv4aMultiAuthTest.java | 38 +++++++- 6 files changed, 120 insertions(+), 107 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 c7fdbab7ffee..1d8914010661 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 @@ -63,6 +63,7 @@ import software.amazon.awssdk.metrics.MetricCollector; import software.amazon.awssdk.metrics.SdkMetric; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.Validate; @@ -146,20 +147,23 @@ private MethodSpec generateAuthSchemeParams() { .addParameter(SdkRequest.class, "request") .addParameter(ExecutionAttributes.class, "executionAttributes"); - if (!authSchemeSpecUtils.useEndpointBasedAuthProvider()) { + if (!authSchemeSpecUtils.useEndpointParamsInAuthScheme()) { 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("builder.region(region)"); + builder.addStatement("return $T.builder()" + + ".operation(operation)" + + ".region(region)" + + ".build()", + authSchemeSpecUtils.parametersInterfaceName()); + } else { + builder.addStatement("return $T.builder()" + + ".operation(operation)" + + ".build()", + authSchemeSpecUtils.parametersInterfaceName()); } - generateSigv4aRegionSet(builder); - builder.addStatement("return builder.build()"); return builder.build(); } @@ -196,7 +200,9 @@ 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. + if (authSchemeSpecUtils.hasMultiAuthSigvOrSigv4a()) { + generateSigv4aRegionSet(builder); + } builder.addStatement("return builder.build()"); return builder.build(); } @@ -452,19 +458,13 @@ private TypeName toTypeName(Object valueType) { private void generateSigv4aRegionSet(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, + "executionAttributes.getOptionalAttribute($T.AWS_SIGV4A_SIGNING_REGION_SET)\n" + + " .filter(regionSet -> !$T.isNullOrEmpty(regionSet))\n" + + " .ifPresent(nonEmptyRegionSet -> builder.regionSet($T.create(nonEmptyRegionSet)))", + AwsExecutionAttribute.class, + CollectionUtils.class, RegionSet.class ); - - builder.addStatement("builder.regionSet(regionSet)"); } } } 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 index b2117fd2af76..af88b25b4c29 100644 --- 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 @@ -20,6 +20,7 @@ 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.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; @@ -32,9 +33,12 @@ 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.services.database.endpoints.DatabaseEndpointParams; +import software.amazon.awssdk.services.database.endpoints.DatabaseEndpointProvider; +import software.amazon.awssdk.services.database.endpoints.internal.DatabaseResolveEndpointInterceptor; +import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.Validate; @@ -84,18 +88,24 @@ private SelectedAuthScheme selectAuthScheme(List !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); + builder.operation(operation); + if (builder instanceof DatabaseEndpointResolverAware.Builder) { + EndpointProvider endpointProvider = executionAttributes.getAttribute(SdkInternalExecutionAttribute.ENDPOINT_PROVIDER); + if (endpointProvider instanceof DatabaseEndpointProvider) { + ((DatabaseEndpointResolverAware.Builder) builder).endpointProvider((DatabaseEndpointProvider) endpointProvider); + } + } + executionAttributes.getOptionalAttribute(AwsExecutionAttribute.AWS_SIGV4A_SIGNING_REGION_SET) + .filter(regionSet -> !CollectionUtils.isNullOrEmpty(regionSet)) + .ifPresent(nonEmptyRegionSet -> builder.regionSet(RegionSet.create(nonEmptyRegionSet))); return builder.build(); } 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 0b30a534901e..48edb00b1855 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,11 +84,10 @@ private SelectedAuthScheme selectAuthScheme(List SelectedAuthScheme trySelectAuthScheme(AuthSchemeOption authOption, AuthScheme authScheme, IdentityProviders identityProviders, List> discardedReasons, MetricCollector metricCollector, ExecutionAttributes executionAttributes) { 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 index cf58fb6fe996..1c7594900358 100644 --- 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 @@ -46,6 +46,11 @@ }, "StringClientContextParam": { "type": "String" + }, + "ApiType": { + "required": true, + "documentation": "Parameter to determine whether current API is a control plane or dataplane API", + "type": "String" } }, "rules": [ @@ -66,81 +71,29 @@ { "conditions": [ { - "fn": "isSet", + "fn": "stringEquals", "argv": [ { - "ref": "Endpoint" - } + "ref": "ApiType" + }, + "OnlyRegion" ] - }, - { - "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" - }, + "endpoint": { + "url": "https://only-region.{Region}.on.aws", + "properties": { + "authSchemes": [ { - "conditions": [], - "endpoint": { - "url": { - "ref": "Endpoint" - }, - "properties": { - "authSchemes": [ - { - "name": "sigv4", - "signingRegion": "{Region}", - "signingName": "restjson" - } - ] - }, - "headers": {} - }, - "type": "endpoint" + "name": "sigv4", + "signingName": "onlyRegion", + "signingRegion": "us-east-2" } ] - } - ] + }, + "headers": {} + }, + "type": "endpoint" }, { "conditions": [ 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 index e0be3bee5ca7..407f7cee8044 100644 --- 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 @@ -21,7 +21,10 @@ "requestUri":"/2016-03-11/sigv4aoperation" }, "input":{"shape":"sigv4aShape"}, - "auth": ["aws.auth#sigv4a"] + "auth": ["aws.auth#sigv4a"], + "staticContextParams":{ + "ApiType":{"value":"NoAuthProperties"} + } }, "sigv4AndSigv4aOperation":{ "name":"sigv4a", @@ -30,7 +33,23 @@ "requestUri":"/2016-03-11/sigv4andsigv4aoperation" }, "input":{"shape":"sigv4aShape"}, - "auth": ["aws.auth#sigv4a", "aws.auth#sigv4"] + "auth": ["aws.auth#sigv4a", "aws.auth#sigv4"], + "staticContextParams":{ + "ApiType":{"value":"NoAuthProperties"} + } + }, + "operationWithOnlyRegionEndpointParams":{ + "name":"operationWithOnlyRegionEndpointParams", + "http":{ + "method":"GET", + "requestUri":"/operations-with-endpointparams", + "responseCode":200 + }, + "input":{"shape":"sigv4aShape"}, + "documentation":"

Operation with EndpointParams w

", + "staticContextParams":{ + "ApiType":{"value":"RegionDefinedInRules"} + } } }, "shapes": { 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 index 8aabc1dd39bf..4ee619975faa 100644 --- 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 @@ -91,10 +91,11 @@ void requestHasRegionSetParamsUpdatedToRegion() { MultiauthAuthSchemeParams resolvedAuthSchemeParams = paramsCaptor.getValue(); assertThat(resolvedAuthSchemeParams.regionSet()) .isEqualTo(RegionSet.create(Arrays.asList("us-west-2", "us-west-1"))); + assertThat(resolvedAuthSchemeParams.apiType()).isEqualTo("NoAuthProperties"); } @Test - void requestHasRegionSetSdkSystemSettings() { + void authSchemeParamsPassedAsNullIfClientIsNotConfiguredWithRegionSet() { MultiauthClient multiauthClient = MultiauthClient.builder() .httpClient(mockHttpClient) .authSchemeProvider(multiauthAuthSchemeProvider) @@ -109,8 +110,8 @@ void requestHasRegionSetSdkSystemSettings() { verify(multiauthAuthSchemeProvider).resolveAuthScheme(paramsCaptor.capture()); MultiauthAuthSchemeParams resolvedAuthSchemeParams = paramsCaptor.getValue(); - assertThat(resolvedAuthSchemeParams.regionSet()) - .isEqualTo(RegionSet.create(Region.US_WEST_2.toString())); + assertThat(resolvedAuthSchemeParams.regionSet()).isNull(); + assertThat(resolvedAuthSchemeParams.apiType()).isEqualTo("NoAuthProperties"); } @Test @@ -123,6 +124,7 @@ void errorWhenSigv4aDoesNotHasFallbackSigv4() { 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 @@ -140,4 +142,34 @@ void fallBackToSigv4WhenSigv4aIsNotAvailable() { SdkHttpRequest request = httpRequestCaptor.getAllValues().get(0).httpRequest(); assertThat(request.firstMatchingHeader("Authorization")).isPresent(); } + + + @Test + void authSchemesParamsUpdatedWithStaticContextAndDefaultEndpointParams() { + environmentVariableHelper.set(SdkSystemSetting.AWS_SIGV4A_SIGNING_REGION_SET, "us-west-2,us-west-1"); + + MultiauthClient multiauthClient = MultiauthClient.builder() + .httpClient(mockHttpClient) + .authSchemeProvider(multiauthAuthSchemeProvider) + .region(Region.EU_CENTRAL_1) + .build(); + + assertThatThrownBy(() -> multiauthClient.operationWithOnlyRegionEndpointParams(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"))); + assertThat(resolvedAuthSchemeParams.apiType()) + .isEqualTo("RegionDefinedInRules"); + assertThat(resolvedAuthSchemeParams.operation()) + .isEqualTo("operationWithOnlyRegionEndpointParams"); + assertThat(resolvedAuthSchemeParams.region()).isEqualTo(Region.EU_CENTRAL_1); + assertThat(resolvedAuthSchemeParams.useFips()).isFalse(); + assertThat(resolvedAuthSchemeParams.useDualStack()).isFalse(); + } }